/* * JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com) * * 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 jef.database.meta; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; import java.net.MalformedURLException; import java.net.URL; import java.sql.SQLException; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.persistence.Cacheable; import javax.persistence.Column; import javax.persistence.Table; import jef.accelerator.bean.BeanAccessor; import jef.accelerator.bean.FastBeanWrapperImpl; import jef.common.Entry; import jef.common.log.LogUtil; import jef.database.DbCfg; import jef.database.DbUtils; import jef.database.DebugUtil; import jef.database.Field; import jef.database.IQueryableEntity; import jef.database.OperateTarget; import jef.database.PojoWrapper; import jef.database.annotation.BindDataSource; import jef.database.annotation.EasyEntity; import jef.database.annotation.PartitionFunction; import jef.database.annotation.PartitionKey; import jef.database.annotation.PartitionTable; import jef.database.dialect.type.ColumnMapping; import jef.database.meta.def.IndexDef; import jef.database.meta.def.UniqueConstraintDef; import jef.database.routing.function.AbstractDateFunction; import jef.database.routing.function.HashMod1024MappingFunction; import jef.database.routing.function.MapFunction; import jef.database.routing.function.ModulusFunction; import jef.database.routing.function.RawFunc; import jef.tools.ArrayUtils; import jef.tools.JefConfiguration; import jef.tools.StringUtils; import jef.tools.reflect.BeanAccessorMapImpl; import jef.tools.reflect.BeanUtils; import jef.tools.reflect.FieldEx; import com.alibaba.fastjson.util.IOUtils; import com.github.geequery.orm.annotation.Comment; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.body.BodyDeclaration; import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.ModifierSet; import com.github.javaparser.ast.body.TypeDeclaration; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @SuppressWarnings("rawtypes") public final class TableMetadata extends AbstractMetadata { /** * 记录当前Schema所对应的DO类。 */ private Class<?> thisType; private BeanAccessor pojoAccessor; private Class<? extends IQueryableEntity> containerType; BeanAccessor containerAccessor; // ///////////////分表配置信息////////////////////// private PartitionTable partition;// 分表策略 // 记录在每个字段上的函数,用来进行分表估算的时的采样 private Multimap<String, PartitionFunction> partitionFuncs; private Entry<PartitionKey, PartitionFunction>[] effectPartitionKeys; private List<ColumnMapping> pkFields;// 记录主键列 private final Map<Field, String> fieldToColumn = new IdentityHashMap<Field, String>();// 提供Field到列名的转换 private final Map<String, String> lowerColumnToFieldName = new HashMap<String, String>();// 提供Column名称到Field的转换,不光包括元模型字段,也包括了非元模型字段但标注了Column的字段(key全部存小写) /** * 标准实体的构造 * * @param clz * @param annos */ TableMetadata(Class<? extends IQueryableEntity> clz, AnnotationProvider annos) { this.containerType = clz; this.containerAccessor = FastBeanWrapperImpl.getAccessorFor(clz); this.thisType = clz; this.pkFields = Collections.emptyList(); initByAnno(clz, annos); } /** * POJO类实体的构造 * * @param varClz * @param clz * @param annos */ TableMetadata(Class<PojoWrapper> varClz, Class<?> clz, AnnotationProvider annos) { this.containerType = varClz; this.containerAccessor = new BeanAccessorMapImpl(clz); this.thisType = clz; this.pojoAccessor = FastBeanWrapperImpl.getAccessorFor(clz); this.pkFields = Collections.emptyList(); initByAnno(clz, annos); } protected void initByAnno(Class<?> thisType, AnnotationProvider annos) { // schema初始化 Table table = annos.getAnnotation(javax.persistence.Table.class); if (table != null) { if (table.schema().length() > 0) { schema = MetaHolder.getMappingSchema(table.schema());// 重定向 } if (table.name().length() > 0) { tableName = table.name(); } for(javax.persistence.Index index: table.indexes()){ this.indexes.add(IndexDef.create(index)); } for(javax.persistence.UniqueConstraint unique: table.uniqueConstraints()){ this.uniques.add(new UniqueConstraintDef(unique)); } } if (tableName == null) { // 表名未指定,缺省生成 boolean needTranslate = JefConfiguration.getBoolean(DbCfg.TABLE_NAME_TRANSLATE, false); if (needTranslate) { tableName = DbUtils.upperToUnderline(thisType.getSimpleName()); } else { tableName = thisType.getSimpleName(); } } BindDataSource bindDs = annos.getAnnotation(BindDataSource.class); if (bindDs != null) { this.bindDsName = MetaHolder.getMappingSite(StringUtils.trimToNull(bindDs.value())); } Cacheable cache = annos.getAnnotation(Cacheable.class); this.cacheable = cache != null && cache.value(); EasyEntity entity = annos.getAnnotation(EasyEntity.class); if (entity != null) { this.useOuterJoin = entity.useOuterJoin(); } } /** * 得到分表配置 */ public PartitionTable getPartition() { return partition; } /** * 设置和解析数据分片(分库分表)配置 * * @param t */ synchronized void setPartition(PartitionTable t) { if (t == null) return; effectPartitionKeys = withFunction(t.key()); if (effectPartitionKeys.length == 0) { effectPartitionKeys = null; return; } this.partition = t; // 开始计算在同一个字段上的最小的分表参数单元 Multimap<String, PartitionFunction> fieldKeyFn = ArrayListMultimap.create(); for (Entry<PartitionKey, PartitionFunction> entry : getEffectPartitionKeys()) { PartitionKey key = entry.getKey(); String field = key.field(); if (entry.getValue() instanceof AbstractDateFunction) { Collection<PartitionFunction> olds = fieldKeyFn.get(field); if (olds != null) { for (PartitionFunction<?> old : olds) { if (old instanceof AbstractDateFunction) { int oldLevel = ((AbstractDateFunction) old).getTimeLevel(); int level = ((AbstractDateFunction) entry.getValue()).getTimeLevel();// 取最小的时间单位 if (level < oldLevel) { fieldKeyFn.remove(field, old); break;// 可以合并 } else { continue;// 无需合并 } } } } } fieldKeyFn.put(field, entry.getValue()); } partitionFuncs = fieldKeyFn; } public Class<?> getThisType() { return thisType; } public Class<? extends IQueryableEntity> getContainerType() { return containerType; } public List<IndexDef> getIndexDefinition() { return indexes; } public List<Field> getPKField() { return new AbstractList<Field>() { @Override public Field get(int index) { return pkFields.get(index).field(); } @Override public int size() { return pkFields.size(); } }; } public List<ColumnMapping> getPKFields() { return pkFields; } /** * 将一个Java Field加入到列定义中 * * @param field * @param column */ public void putJavaField(Field field, ColumnMapping type, String columnName, boolean isPk) { fields.put(field.name(), field); lowerFields.put(field.name().toLowerCase(), field); fieldToColumn.put(field, columnName); String lastFieldName = lowerColumnToFieldName.put(columnName.toLowerCase(), field.name()); if (lastFieldName != null && !field.name().equals(lastFieldName)) { throw new IllegalArgumentException(String.format("The field [%s] and [%s] in [%s] have a duplicate column name [%s].", lastFieldName, field.name(), containerType.getName(), columnName)); } if (isPk) { type.setPk(true); List<ColumnMapping> newPks = new ArrayList<ColumnMapping>(pkFields); newPks.add(type); Collections.sort(newPks, new Comparator<ColumnMapping>() { public int compare(ColumnMapping o1, ColumnMapping o2) { Integer i1 = -1; Integer i2 = -1; if (o1 instanceof Enum) { i1 = ((Enum<?>) o1.field()).ordinal(); } if (o2 instanceof Enum) { i2 = ((Enum<?>) o2.field()).ordinal(); } return i1.compareTo(i2); } }); this.pkFields = Arrays.<ColumnMapping> asList(newPks.toArray(new ColumnMapping[newPks.size()])); } schemaMap.put(field, type); super.updateAutoIncrementAndUpdate(type); if (type.isLob()) { lobNames = ArrayUtils.addElement(lobNames, field, jef.database.Field.class); } } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Entity: [").append(containerType.getName()).append("]\n"); for (ColumnMapping m : schemaMap.values()) { String fname = m.fieldName(); sb.append(" ").append(fname); StringUtils.repeat(sb, ' ', 10 - fname.length()); sb.append('\t').append(m.get()); sb.append("\n"); } sb.setLength(sb.length() - 1); return sb.toString(); } /** * 获取当前生效的分区策略 * 注意生效的策略默认等同于Annotation上的策略,但是实际上如果配置了/partition-conf.properties后 * ,生效字段受改配置影响 {@link #partitPolicy} * * @return */ public Entry<PartitionKey, PartitionFunction>[] getEffectPartitionKeys() { return effectPartitionKeys; } private Entry<PartitionKey, PartitionFunction>[] withFunction(PartitionKey[] key) { @SuppressWarnings("unchecked") Entry<PartitionKey, PartitionFunction>[] result = new Entry[key.length]; for (int i = 0; i < key.length; i++) { result[i] = new Entry<PartitionKey, PartitionFunction>(key[i], createFunc(key[i])); } return result; } private static PartitionFunction<?> createFunc(PartitionKey value) { if (value.functionClass() != PartitionFunction.class) { try { String[] params = value.functionConstructorParams(); if (params.length == 0) { return value.functionClass().newInstance(); } else { Class[] clz = new Class[params.length]; for (int i = 0; i < params.length; i++) { clz[i] = String.class; } Constructor cc = value.functionClass().getConstructor(clz); cc.setAccessible(true); return (PartitionFunction<?>) cc.newInstance((Object[]) params); } } catch (Exception e) { throw new IllegalArgumentException(e); } } switch (value.function()) { case DAY: return AbstractDateFunction.DAY; case HH24: return AbstractDateFunction.HH24; case MODULUS: if (value.functionConstructorParams().length == 0 || StringUtils.isEmpty(value.functionConstructorParams()[0])) { return ModulusFunction.getDefault(); } else { return new ModulusFunction(value.functionConstructorParams()[0]); } case HASH_MOD1024_RANGE: if (value.functionConstructorParams().length == 0 || StringUtils.isEmpty(value.functionConstructorParams()[0])) { return new HashMod1024MappingFunction(); } else { int num = 0; if (value.functionConstructorParams().length > 1) { num = StringUtils.toInt(value.functionConstructorParams()[1], 0); } return new HashMod1024MappingFunction(value.functionConstructorParams()[0], num); } case MONTH: return AbstractDateFunction.MONTH; case YEAR: return AbstractDateFunction.YEAR; case YEAR_LAST2: return AbstractDateFunction.YEAR_LAST2; case YEAR_MONTH: return AbstractDateFunction.YEAR_MONTH; case YEAR_MONTH_DAY: return AbstractDateFunction.YEAR_MONTH_DAY; case WEEKDAY: return AbstractDateFunction.WEEKDAY; case RAW: return new RawFunc(value.defaultWhenFieldIsNull(), value.length()); case MAPPING: if (value.functionConstructorParams().length == 0) { throw new IllegalArgumentException("You must config the 'functionConstructorParams' while using funcuon Map"); } int num = 0; if (value.functionConstructorParams().length > 1) { num = StringUtils.toInt(value.functionConstructorParams()[1], 0); } return new MapFunction(value.functionConstructorParams()[0], num); default: throw new IllegalArgumentException("Unknown KeyFunction:" + value.function()); } } public Multimap<String, PartitionFunction> getMinUnitFuncForEachPartitionKey() { return partitionFuncs; } /** * 添加一个非元模型的 Column映射字段(一般用于分表辅助) * * @param name * @param column */ public void addNonMetaModelFieldMapping(String field, Column column) { String name = column.name(); if (StringUtils.isEmpty(name)) { name = field; } lowerColumnToFieldName.put(name.toLowerCase(), field); } void setTableName(String tableName) { this.tableName = tableName; } void setSchema(String schema) { this.schema = schema; } /** * 根据一个指定的实际数据库,核对metaData中的字段,如果发现某些可选的字段数据库里没有,就从元模型中删除来适应数据库 * * @param db * @throws SQLException */ public synchronized void removeNotExistColumns(OperateTarget db) throws SQLException { Set<String> set = DebugUtil.getColumnsInLowercase(db, getTableName(true)); List<Field> removeColumn = new ArrayList<Field>(); for (Field field : fieldToColumn.keySet()) { String column = fieldToColumn.get(field).toLowerCase(); if (!set.contains(column)) { removeColumn.add(field); } } for (Field field : removeColumn) { schemaMap.remove(field); // FIXME, others are not removed LogUtil.show("The field [" + field.name() + "] was remove since column not exist in db."); } if (removeColumn.size() > 0) metaFields = null; } public IQueryableEntity newInstance() { if (pojoAccessor != null) { return new PojoWrapper(pojoAccessor.newInstance(), pojoAccessor, this, false); } else { return (IQueryableEntity) containerAccessor.newInstance(); } } public String getName() { return thisType.getName(); } public String getSimpleName() { return thisType.getSimpleName(); } public Field getFieldByLowerColumn(String columnLowercase) { return fields.get(lowerColumnToFieldName.get(columnLowercase)); } public PojoWrapper transfer(Object p, boolean isQuery) { if (p == null) return null; if (p instanceof IQueryableEntity) { throw new IllegalArgumentException(); } if (p.getClass() == this.thisType) { return new PojoWrapper(p, pojoAccessor, this, isQuery); } else { throw new IllegalArgumentException(p.getClass() + " != " + this.thisType); } } public EntityType getType() { return this.containerType == PojoWrapper.class ? EntityType.POJO : EntityType.NATIVE; } private List<Class> parents; public void addParent(Class<?> processingClz) { if (parents == null) { parents = new ArrayList<Class>(3); } parents.add(processingClz); } public boolean containsMeta(ITableMetadata type) { if (type == this) return true; if (parents == null) return false; for (Class clz : parents) { if (type.getThisType() == clz) { return true; } } return false; } @Override public BeanAccessor getContainerAccessor() { return containerAccessor; } TupleMetadata extendMeta; TupleMetadata extendContainer; @Override public TupleMetadata getExtendsTable() { return extendContainer; } @Override public Collection<ColumnMapping> getExtendedColumns() { return extendMeta == null ? Collections.<ColumnMapping> emptyList() : extendMeta.getColumnSchema(); } @Override public ColumnMapping getExtendedColumnDef(String field) { return extendMeta.getColumnDef(extendMeta.getField(field)); } @Override public Map<String, String> getColumnComments() { // 先根据源码解析来获取注解 Map<String, String> result = getFromSource(); // 再分析注解中的备注信息 { Comment comment = thisType.getAnnotation(Comment.class); if (comment != null) { result.put("#TABLE", comment.value()); } } for (ColumnMapping column : this.getColumns()) { FieldEx field = BeanUtils.getField(thisType, column.fieldName()); if (field == null) { continue; } Comment comment = field.getAnnotation(Comment.class); if (comment != null) { result.put(column.fieldName(), comment.value()); } } return result; } /** * 尝试从源码的注释中获得列的注解信息 * * @return */ private Map<String, String> getFromSource() { Map<String, String> result = new HashMap<String, String>(); Class<?> type = thisType; URL url = this.getClass().getResource("/" + type.getName().replace('.', '/') + ".java"); if (url == null) { url = getFixedPathSource(type); } if (url == null) return result; try { InputStream in = url.openStream(); try { CompilationUnit unit = JavaParser.parse(in, "UTF-8"); if (unit.getTypes().isEmpty()) return result; TypeDeclaration typed = unit.getTypes().get(0); if (typed instanceof ClassOrInterfaceDeclaration) { ClassOrInterfaceDeclaration clz = (ClassOrInterfaceDeclaration) typed; String table = getContent(clz.getComment()); if (table != null) result.put("#TABLE", table); for (BodyDeclaration body : typed.getMembers()) { if (body instanceof FieldDeclaration) { FieldDeclaration field = (FieldDeclaration) body; if (ModifierSet.isStatic(field.getModifiers())) { continue; } if (field.getVariables().size() > 1) { continue; } String name = field.getVariables().get(0).getId().getName(); String javaDoc = getContent(field.getComment()); if (javaDoc != null) result.put(name, javaDoc); } } } } finally { IOUtils.close(in); } } catch (ParseException e) { LogUtil.exception(e); } catch (IOException e) { LogUtil.exception(e); } return result; } private String getContent(com.github.javaparser.ast.comments.Comment comment) { if (comment == null) return null; String s = comment.getContent(); return s.replaceAll("\\s*\\*", "").trim(); } /** * 在开发环境的标准Maven目录场景情况下,寻找到源代码,实现注解信息读取 * * @param type * @return */ private URL getFixedPathSource(Class type) { String clzPath = "/" + type.getName().replace('.', '/') + ".class"; URL url = this.getClass().getResource(clzPath); if (url == null) return null; String path = url.getPath(); path = path.substring(0, path.length() - clzPath.length()); File source = null; if (path.endsWith("/target/test-classes")) { source = new File(path.substring(0, path.length() - 20), "src/test/java"); } else if (path.endsWith("/target/classes")) { source = new File(path.substring(0, path.length() - 15), "src/main/java"); } if (source == null) return null; File java = new File(source, type.getName().replace('.', '/') + ".java"); if (java.exists()) try { return java.toURI().toURL(); } catch (MalformedURLException e) { LogUtil.exception(e); return null; } return null; } }