扩展MetaBuilder类,解决表没有主键无法生成Model,已修复bug

扩展MetaBuilder类,用于解决老项目中的数据库表没有主键,导致使用Generator生成Model类时报错的问题。

主要是重写MetaBuilder.build()方法,增加处理数据库表没有主键的那些表。

JFinal版本:3.4,之前分享的代码片段有bug,已解决


1. 重写类_MetaBuilder

/**
 * 扩展自定义 MetaBuilder
 * 
 * @author Fancye
 *
 */
public class _MetaBuilder extends MetaBuilder {

// protected String[] removedTableNamePrefixes = null;// 前缀
   protected String[] removedTableNameSuffixes = null;// 后缀
   protected String[] skipTableSuffixes = null;
   
// public String[] getRemovedTableNamePrefixes() {
//    return removedTableNamePrefixes;
// }

// public void setRemovedTableNamePrefixes(String[] removedTableNamePrefixes) {
//    this.removedTableNamePrefixes = removedTableNamePrefixes;
// }

   public String[] getRemovedTableNameSuffixes() {
      return removedTableNameSuffixes;
   }

   public void setRemovedTableNameSuffixes(String... removedTableNameSuffixes) {
      this.removedTableNameSuffixes = removedTableNameSuffixes;
   }

   public String[] getSkipTableSuffixes() {
      return skipTableSuffixes;
   }

   public void setSkipTableSuffixes(String... skipTableSuffixes) {
      this.skipTableSuffixes = skipTableSuffixes;
   }

   public _MetaBuilder(DataSource dataSource) {
      super(dataSource);
   }

   public List<TableMeta> build() {
      System.out.println("Build TableMeta ...");
      try {
         conn = dataSource.getConnection();
         dbMeta = conn.getMetaData();
         
         List<TableMeta> ret = new ArrayList<TableMeta>();
         buildTableNames(ret);
         for (TableMeta tableMeta : ret) {
            buildColumnMetas(tableMeta);
            buildPrimaryKeyIsNull(tableMeta);
         }
         return ret;
      }
      catch (SQLException e) {
         throw new RuntimeException(e);
      }
      finally {
         if (conn != null)
            try {conn.close();} catch (SQLException e) {throw new RuntimeException(e);}
      }
   }

   /**
    * 如果没有主键,使用所有字段作为联合主键
    * @param tableMeta
    */
   private void buildPrimaryKeyIsNull(TableMeta tableMeta) throws SQLException {
      ResultSet rs = dbMeta.getPrimaryKeys(conn.getCatalog(), null, tableMeta.name);

      String primaryKey = "";
      int index = 0;
      while (rs.next()) {
         if (index++ > 0) {
            primaryKey += ",";
         }
         primaryKey += rs.getString("COLUMN_NAME");
      }
      if (StrKit.isBlank(primaryKey)) {
         System.out.println(tableMeta.name + "表没有主键,使用以下字段作为联合主键");
         String columns = "";
         for (ColumnMeta cm : tableMeta.columnMetas) {
            // 使用表字段联合主键
//          tableMeta.primaryKey += cm.attrName + ",";
            columns += cm.attrName + ",";
         }
         columns = columns.substring(0, columns.length() - 1);
         System.out.println(tableMeta.name + "[" + columns + "]");
         tableMeta.primaryKey = columns;
      } else {
         tableMeta.primaryKey = primaryKey;
      }

      rs.close();
   }
   
   /**
    * 构造 colName 所对应的 attrName,mysql 数据库建议使用小写字段名或者驼峰字段名
    * Oralce 反射将得到大写字段名,所以不建议使用驼峰命名,建议使用下划线分隔单词命名法
    */
   protected String buildAttrName(String colName) {
      if (dialect instanceof OracleDialect) {
         colName = colName.toLowerCase();
      }
      return uperToLowerCase(StrKit.toCamelCase(colName));
   }
   
   /**
    * 构造 modelName,mysql 的 tableName 建议使用小写字母,多单词表名使用下划线分隔,不建议使用驼峰命名
    * oracle 之下的 tableName 建议使用下划线分隔多单词名,无论 mysql还是 oralce,tableName 都不建议使用驼峰命名
    */
   protected String buildModelName(String tableName) {
      // 移除表名前缀仅用于生成 modelName、baseModelName,而 tableMeta.name 表名自身不能受影响
      if (removedTableNamePrefixes != null) {
         for (String prefix : removedTableNamePrefixes) {
            if (tableName.startsWith(prefix)) {
               tableName = tableName.replaceFirst(prefix, "");
               break;
            }
         }
      }
      
      if(removedTableNameSuffixes != null) {
         for (String suffix : removedTableNameSuffixes) {
            if (tableName.endsWith(suffix)) {
               tableName = tableName.substring(0, tableName.length() - suffix.length());
               break;
            }
         }
      }
      
      // 将 oralce 大写的 tableName 转成小写,再生成 modelName
      if (dialect instanceof OracleDialect) {
         tableName = tableName.toLowerCase();
      }
      
      return StrKit.firstCharToUpperCase(StrKit.toCamelCase(tableName));
   }
   
   /**
    * 如果是oracle数据库,使用此方法覆盖源代码实现方式
    */
// protected String buildModelName(String tableName) {
//    // 移除表名前缀仅用于生成 modelName、baseModelName,而 tableMeta.name 表名自身不能受影响
//    if (removedTableNamePrefixes != null) {
//       for (String prefix : removedTableNamePrefixes) {
//          if (tableName.startsWith(prefix)) {
//             tableName = tableName.replaceFirst(prefix, "");
//             break;
//          }
//       }
//    }
//    
//    // 将 oralce 大写的 tableName 转成小写,再生成 modelName
//    if (dialect instanceof OracleDialect) {
//       tableName = tableName.toLowerCase();
//    }
//    
//    return StrKit.firstCharToUpperCase(StrKit.toCamelCase(tableName));
// }
   
   private String uperToLowerCase(String colName) {
      char[] arrays = colName.toCharArray();
      if(Character.isUpperCase(arrays[0])) {
         return colName.toLowerCase();
      } else {
         return colName;
      }
   }
   
   /**
    * 通过继承并覆盖此方法,跳过一些不希望处理的 table,定制更加灵活的 table 过滤规则
    * 此处定制以某些后缀名(skipTableSuffixes)结束的table将不会处理
    * @return 返回 true 时将跳过当前 tableName 的处理
    */
   protected boolean isSkipTable(String tableName) {
      boolean isSkip = false;
      if(skipTableSuffixes != null) {
         for(String suffix : skipTableSuffixes) {
            if (tableName.endsWith(suffix)) {
               isSkip = true;
               break;
            }
         }
      }
      
      return isSkip;
   }
   
}

2. 在Model生成器主类中替换MetaBuilder

Generator gernerator = new Generator(getDataSource(), baseModelPackageName, baseModelOutputDir, modelPackageName, modelOutputDir);
// 扩展自定义 MetaBuilder
_MetaBuilder myMetaBuilder = new _MetaBuilder(getDataSource());
gernerator.setMetaBuilder(myMetaBuilder);


评论区

JFinal

2019-03-28 09:46

表没有主键生成 model 的话, model.save()、model.update()、model.delete() 等很多 API 都无法工作

因为 model 是 Active Record 模式,这个模式的核心就是一个 model 对应一条数据库的 record,对应的起来的机制就是 “主键”, 失去主键就没有了这个机制,很多 API 自然就无法工作

当然,不排除有同学生成 model 后不使用那些 API,感谢分享

Fancye

2019-03-28 11:12

@JFinal 是将所有字段作为联合主键的,因此model.save() 等方法是可以起作用的。只是效率肯定没有那么好!

马小酱

2019-03-28 14:46

所有字段作为联合主键?有点非主流用法啊.....也许你业务特殊吧,但是我觉得特殊的业务真的很少

Fancye

2019-03-29 18:30

@马小酱 是历史遗留项目,太多表都没有主键,如果去该数据库增加一个id作为自增长主键的话,需要改动太大。新项目或者此种情况的表不多的话,还是建议增加主键字段。

热门分享

扫码入社