/**
* Copyright 2014 Duan Bingnan
*
* 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.
*/
package org.pinus4j.entity.meta;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.pinus4j.exceptions.DBPrimaryKeyException;
import org.pinus4j.utils.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据库表bean. 对数据库表的抽象.
*
* @author duanbn
*/
public class DBTable implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 日志.
*/
public static final Logger LOG = LoggerFactory.getLogger(DBTable.class);
/**
* 表所在的集群.
*/
private String cluster;
/**
* 表名
*/
private String name;
/**
* 表下标.
*/
private int tableIndex = -1;
/**
* 分片字段.
*/
private String shardingBy;
/**
* 分表数
*/
private int shardingNum;
/**
* 是否需要被缓存
*/
private boolean isCache;
private String cacheVersion;
/**
* 主键
*/
private List<DBTablePK> primaryKeys = new ArrayList<DBTablePK>();
/**
* 表中的列.
*/
private List<DBTableColumn> columns = new ArrayList<DBTableColumn>();
/**
* 包含的索引.
*/
private List<DBTableIndex> indexes = new ArrayList<DBTableIndex>();
/**
* 判断是否是联合主键
*
* @return
*/
public boolean isUnionPrimaryKey() {
return this.primaryKeys.size() > 1;
}
/**
* 判断是否是sharding表
*
* @return
*/
public boolean isSharding() {
return StringUtil.isNotBlank(this.shardingBy) && this.shardingNum > 0;
}
/**
* 校验主键配置
*/
public void checkPrimaryKey() {
if (primaryKeys.isEmpty()) {
throw new DBPrimaryKeyException("必须指定至少一个字段为主键, cluster=" + this.cluster + ", name=" + this.name);
} else {
int pkAICount = 0;
for (DBTablePK primaryKey : primaryKeys) {
if (primaryKey.isAutoIncrement()) {
if (++pkAICount > 1) {
throw new DBPrimaryKeyException("联合主键只能有一个主键自增, cluster=" + this.cluster + ", name="
+ this.name);
}
if (DataTypeBind.getEnum(primaryKey.getType()) != DataTypeBind.INT
&& DataTypeBind.getEnum(primaryKey.getType()) != DataTypeBind.LONG) {
throw new DBPrimaryKeyException("自增主键必须是int或者long类型, cluster=" + this.cluster + ", name="
+ this.name);
}
}
}
}
}
/**
* 构造方法
*
* @param name 表名
*/
public DBTable(String name) {
this.name = name;
}
public String getNameWithIndex() {
if (tableIndex > -1) {
return this.name + tableIndex;
}
return name;
}
public void addPrimaryKey(DBTablePK primaryKey) {
this.primaryKeys.add(primaryKey);
}
public void addColumn(DBTableColumn column) {
this.columns.add(column);
}
public void addIndex(DBTableIndex index) {
this.indexes.add(index);
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public List<DBTablePK> getPrimaryKeys() {
return primaryKeys;
}
public void setPrimaryKeys(List<DBTablePK> primaryKeys) {
this.primaryKeys = primaryKeys;
}
public List<DBTableColumn> getColumns() {
return columns;
}
public void setColumns(List<DBTableColumn> columns) {
this.columns = columns;
}
public Map<String, DBTablePK> getPrimaryKeyMap() {
Map<String, DBTablePK> primaryKeyMap = new HashMap<String, DBTablePK>(primaryKeys.size());
for (DBTablePK parimaryKey : this.primaryKeys) {
primaryKeyMap.put(parimaryKey.getField(), parimaryKey);
}
return primaryKeyMap;
}
public Map<String, DBTableColumn> getColumnMap() {
Map<String, DBTableColumn> columnMap = new HashMap<String, DBTableColumn>(columns.size());
for (DBTableColumn column : this.columns) {
columnMap.put(column.getField(), column);
}
return columnMap;
}
public Map<String, DBTableIndex> getIndexMap() {
Map<String, DBTableIndex> indexMap = new HashMap<String, DBTableIndex>(indexes.size());
for (DBTableIndex index : this.indexes) {
indexMap.put(index.getIndexName(), index);
}
return indexMap;
}
/**
* 生成此表的创建SQL语句.
*
* @return SQL语句.
*/
public String[] getCreateSQL() {
List<String> sqls = new ArrayList<String>();
StringBuilder sql = new StringBuilder();
// 创建表
sql.append("CREATE TABLE `" + getNameWithIndex()).append("` (");
for (DBTableColumn column : this.columns) {
sql.append(_sqlFieldPhrase(column)).append(",");
}
sql.append(" PRIMARY KEY(" + _sqlPrimaryKey() + ")");
sql.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
sqls.add(sql.toString());
// 创建索引
for (DBTableIndex dbIndex : indexes) {
sql.setLength(0);
sql.append(_sqlCreateIndex(dbIndex));
sqls.add(sql.toString());
}
return sqls.toArray(new String[sqls.size()]);
}
/**
* 获取修改的sql语句.
*
* @param existTable 已经存在的表结构
* @param isDelete 多余的列是否删除.
* @return
*/
public String[] getAlterSQL(DBTable existTable, boolean isDelete) {
if (!existTable.getNameWithIndex().equals(getNameWithIndex())) {
return new String[0];
}
List<String> sqls = new ArrayList<String>();
// 同步主键
boolean isPrimaryKeyChanged = false;
Map<String, DBTablePK> dbPrimaryKeyMap = existTable.getPrimaryKeyMap();
DBTablePK primaryKey = null;
if (this.primaryKeys.size() != dbPrimaryKeyMap.size()) {
isPrimaryKeyChanged = true;
} else {
for (DBTablePK entityPrimaryKey : this.primaryKeys) {
primaryKey = dbPrimaryKeyMap.get(entityPrimaryKey.getField());
if (primaryKey == null) {
isPrimaryKeyChanged = true;
break;
}
}
}
if (isPrimaryKeyChanged) {
String updatePkSql = "ALTER TABLE `" + this.getNameWithIndex() + "` DROP PRIMARY KEY,ADD PRIMARY KEY ("
+ _sqlPrimaryKey() + ");";
sqls.add(updatePkSql);
}
// 同步字段
Map<String, DBTableColumn> dbColumnMap = existTable.getColumnMap();
DBTableColumn dbColumn = null;
for (DBTableColumn entityColumn : this.columns) {
dbColumn = dbColumnMap.get(entityColumn.getField());
if (dbColumn != null) {
dbColumnMap.remove(entityColumn.getField());
}
if (dbColumn == null) {
String addSql = "ALTER TABLE `" + this.getNameWithIndex() + "` ADD COLUMN "
+ _sqlFieldPhrase(entityColumn) + ";";
sqls.add(addSql);
} else if (!entityColumn.equals(dbColumn)) {
String modifySql = "ALTER TABLE `" + this.getNameWithIndex() + "` MODIFY "
+ _sqlFieldPhrase(entityColumn) + ";";
sqls.add(modifySql);
}
}
// 同步索引
Map<String, DBTableIndex> dbIndexMap = existTable.getIndexMap();
DBTableIndex dbIndex = null;
for (DBTableIndex entityIndex : this.indexes) {
dbIndex = dbIndexMap.get(entityIndex.getIndexName());
if (dbIndex != null) {
dbIndexMap.remove(entityIndex.getIndexName());
}
if (dbIndex == null) {
sqls.add(_sqlCreateIndex(entityIndex));
} else if (!entityIndex.equals(dbIndex)) {
sqls.add("DROP INDEX `" + dbIndex.getIndexName() + " ON " + this.getNameWithIndex() + "`");
sqls.add(_sqlCreateIndex(entityIndex));
}
}
// 删除多余的列
if (isDelete) {
for (String field : dbColumnMap.keySet()) {
sqls.add("ALTER TABLE `" + this.getNameWithIndex() + "` DROP COLUMN `" + field + "`;");
}
for (DBTableIndex index : dbIndexMap.values()) {
sqls.add("DROP INDEX `" + index.getIndexName() + "` ON `" + this.getNameWithIndex() + "`");
}
}
return sqls.toArray(new String[sqls.size()]);
}
/**
* 创建索引SQL语句.
*/
private String _sqlCreateIndex(DBTableIndex index) {
StringBuilder indexSql = new StringBuilder();
if (index.isUnique()) {
indexSql.append("CREATE UNIQUE INDEX");
} else {
indexSql.append("CREATE INDEX");
}
if (index.getFields() == null || index.getFields().isEmpty()) {
throw new IllegalArgumentException("索引注解格式错误,field不能为空");
}
StringBuilder indexFields = new StringBuilder();
for (String field : index.getFields()) {
indexFields.append('`').append(field).append('`').append(",");
}
indexFields.deleteCharAt(indexFields.length() - 1);
indexSql.append(" `").append(index.getIndexName()).append("` ON").append(" `").append(this.getNameWithIndex());
indexSql.append("` (").append(indexFields.toString()).append(");");
return indexSql.toString();
}
private String _sqlPrimaryKey() {
StringBuilder primaryKey = new StringBuilder();
for (DBTablePK pk : this.primaryKeys) {
primaryKey.append('`').append(pk.getField()).append('`').append(',');
}
primaryKey.deleteCharAt(primaryKey.length() - 1);
return primaryKey.toString();
}
/**
* 生成单列的SQL语句.
*
* @param column 列对象
* @return 单列的SQL语句.
*/
private String _sqlFieldPhrase(DBTableColumn column) {
StringBuilder pharse = new StringBuilder();
pharse.append('`').append(column.getField()).append("` ");
switch (DataTypeBind.getEnum(column.getType())) {
case UPDATETIME:
pharse.append("timestamp");
pharse.append(" NOT NULL");
pharse.append(" DEFAULT " + column.getDefaultValue());
// 修改为通过框架控制:
// pharse.append(" DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP");
break;
case DATETIME:
pharse.append("datetime");
if (!column.isCanNull()) {
pharse.append(" NOT NULL");
}
if (column.isHasDefault())
pharse.append(" DEFAULT " + column.getDefaultValue());
break;
default:
pharse.append(column.getType());
if (column.getLength() > 0) {
pharse.append("(" + column.getLength() + ")");
}
if (!column.isCanNull()) {
pharse.append(" NOT NULL");
}
if (column.isAutoIncrement()) {
pharse.append(" AUTO_INCREMENT");
}
if (column.getDefaultValue() != null)
pharse.append(" DEFAULT " + column.getDefaultValue());
break;
}
pharse.append(" COMMENT '").append(column.getComment()).append("'");
return pharse.toString();
}
public String getCluster() {
return cluster;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public int getTableIndex() {
return tableIndex;
}
public void setTableIndex(int tableIndex) {
this.tableIndex = tableIndex;
}
public int getShardingNum() {
return shardingNum;
}
public void setShardingNum(int shardingNum) {
this.shardingNum = shardingNum;
}
public String getShardingBy() {
return shardingBy;
}
public void setShardingBy(String shardingBy) {
this.shardingBy = shardingBy;
}
public boolean isCache() {
return isCache;
}
public void setCache(boolean isCache) {
this.isCache = isCache;
}
public String getCacheVersion() {
return cacheVersion;
}
public void setCacheVersion(String cacheVersion) {
this.cacheVersion = cacheVersion;
}
@Override
public String toString() {
return "DBTable [cluster=" + cluster + ", name=" + name + ", tableIndex=" + tableIndex + ", shardingBy="
+ shardingBy + ", shardingNum=" + shardingNum + ", isCache=" + isCache + ", primaryKeys=" + primaryKeys
+ ", columns=" + columns + ", indexes=" + indexes + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((cluster == null) ? 0 : cluster.hashCode());
result = prime * result + ((columns == null) ? 0 : columns.hashCode());
result = prime * result + ((indexes == null) ? 0 : indexes.hashCode());
result = prime * result + (isCache ? 1231 : 1237);
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((primaryKeys == null) ? 0 : primaryKeys.hashCode());
result = prime * result + ((shardingBy == null) ? 0 : shardingBy.hashCode());
result = prime * result + shardingNum;
result = prime * result + tableIndex;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DBTable other = (DBTable) obj;
if (cluster == null) {
if (other.cluster != null)
return false;
} else if (!cluster.equals(other.cluster))
return false;
if (columns == null) {
if (other.columns != null)
return false;
} else if (!columns.equals(other.columns))
return false;
if (indexes == null) {
if (other.indexes != null)
return false;
} else if (!indexes.equals(other.indexes))
return false;
if (isCache != other.isCache)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (primaryKeys == null) {
if (other.primaryKeys != null)
return false;
} else if (!primaryKeys.equals(other.primaryKeys))
return false;
if (shardingBy == null) {
if (other.shardingBy != null)
return false;
} else if (!shardingBy.equals(other.shardingBy))
return false;
if (shardingNum != other.shardingNum)
return false;
if (tableIndex != other.tableIndex)
return false;
return true;
}
}