Freemarker 实战: 代码生成器

Freemarker 实战: 代码生成器

在上一节介绍了 Freemarker的一些基础的用法, 那么在这一节呢, 通过简易代码生成器来实际感受下Freemarker在开发中的魅力

这里只是生成MySQL的代码,其他数据库的话可以自行扩展

开发环境

Maven管理, Freemarker + MyBatis 整合开发

生成的代码架构:SSM框架

Maven 配置


    x_utils
    com.sanq.product.x_utils
    1.0-SNAPSHOT



  
      com.sanq.product.x_utils
      util_common
      1.0-SNAPSHOT
  
  
      com.sanq.product.x_utils
      util_redis
      1.0-SNAPSHOT
  
  
      junit
      junit
      3.8.1
  
  
      org.mybatis
      mybatis
  
  
      mysql
      mysql-connector-java
      runtime
  
  
      org.freemarker
      freemarker
  
  
      ch.qos.logback
       logback-classic
  



  generate
  
  
      
          src/main/java
          
              **/*.properties
              **/*.xml
              **/*.ftl
          
          false
      
      
          src/main/resources
          
              **/*.properties
              **/*.xml
              **/*.ftl
          
          false
      
  

关于配置中 x_utils, 大家可以查看代码: 代码查看,

题外话

X_Util模块描述

  • Common 对开发中经常使用的一些工具类进行封装
  • Redis 对Redis的操作进行封装
  • GenerateCode 代码生成器的源代码(早期开发, 只是修改ftl文件,没有修改FreemarkerUtil,可以参考)

pom.xml配置完成

Mybatis 配置

config.properties中配置数据源等

# 数据源的配置
driver = com.mysql.jdbc.Driver
url = jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
username = root
password = root


#table_schema 
# 因为在查询表的时候需要提供库名, 所以在这里配置
table_schema = test

# 生成的文件包名
packageName = com.sanq.product.freemarker

mybatis.xml配置





  
     
  
      
        
        
     

  
    
        
            
            
                
                
                
                
            
        
    
  
    
        
        
    

mybatis.xml配置完成

具体实现

因为开发环境中没有和Spring整合, 所以在使用的时候就不能通过Spring来管理, 需要写个工具类来获取SqlSession

工具类我们采用单例模式来实现, 单例模式的双重校验锁静态内部类枚举都是线程安全, 推荐使用

这里采用双重校验锁的形式

DaoSupport.java

public class DaoSupport {
  private DaoSupport() {}
  private static DaoSupport instance;

  public static DaoSupport getInstance() {
    if(null == instance) {
      synchronized (DaoSupport.class) {
        if(null == instance) 
          instance = new DaoSupport();
      }
    }
    return instance;
  }

  private static final String RESOURCE = "mybatis.xml"; //配置文件
  //这里是获取SqlSession
  public SqlSession getSession(){
      SqlSession session = null;
      try{
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder()   //
            .build(Resources.getResourceAsReader(RESOURCE));
        session = sessionFactory.openSession();
      }catch(Exception ex){
          ex.printStackTrace();
      }
      return session;
  }
}

DaoSupport完成

关于FreemarkerUtil这里不再给出, 在freemarker入门中已经提供

开发要规范, 在表中字段会存在下划线 所以在这里我们需要把下划线去掉并且将下划线跟随的首字母大写。

这里使用静态内部类

StringUtil.java

public class StringUtil {
  private StringUtil() {}

  public static String getInstance() {
    return Holder.INSTANCE;
  }

  private static class Holder {
    public static final StringUtil INSTANCE  = new StringUtil();
  }

  //替换字符串中指定字符,并将紧跟在它后面的字母大写
  public String replaceChar(String strVal, String tag) {
    StringBuffer sb = new StringBuffer();  
    sb.append(strVal.toLowerCase());  
    int count = sb.indexOf(tag);  
    while(count!=0){  
        int num = sb.indexOf(tag,count);  
        count = num+1;  
        if(num!=-1){  
          char ss = sb.charAt(count);  
          char ia = (char) (ss - 32);  
          sb.replace(count,count+1,ia+"");  
        }  
    }  
    String ss = sb.toString().replaceAll(tag,"");  
    return ss;
  }

  //将首字母大写
  public String firstUpperCase(String strVal) {
    StringBuffer sb = new StringBuffer();
    if(null != strVal && !"".equals(strVal)) {
      sb.append(String.valueOf(strVal.charAt(0)).toUpperCase());
      for(int i = 1; i < strVal.length(); i++) {
        sb.append(strVal.charAt(i));
      }
    }
    return sb.toString();
  }
}

StringUtil.java完成

以上都是工具类, 下面才是重点

所谓的代码生成 只不过是偷懒的一种方式, 因为在开发过程中, CRUD方法一遍遍的写, 毫无技术性可言, 所以我们就通过代码生成器来生成这些方法, 简化我们的开发过程, 让我们能够更加专注于业务的操作。

不过代码生成只是其中的一种方式, 也可以通过BaseService, BaseMapper来封装。这个就因人而异了。

Freemarker方面, 重点给大家展示下entity.ftl和mapper.ftl, 其他的都是按照自己的习惯来写就可以。

entity.ftl

package ${entityPackage!""};

import java.io.Serializable;
import java.util.*;
import java.math.BigDecimal;
import org.springframework.format.annotation.DateTimeFormat;

public class ${table.javaName?cap_first!""}  implements Serializable {

  /**
    *   version: ${table.comment!""}
    *----------------------
    *   author:sanq.Yan
    *   date:${nowDate?string("yyyy-MM-dd")} <#--这里是获取当前时间, 并格式化-->
    */
  private static final long serialVersionUID = 1L;

  <#if table.fields?? && table.fields?size gt 0>
  <#list table.fields as field>
  /**${field.columnComment!""}*/
  <#if field.javaType == "Date">
  @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  
  private ${field.javaType!""} ${field.javaField!""};
  
  

  <#if table.fields?? && table.fields?size gt 0>
  <#list table.fields as field>
  public ${field.javaType!""} get${field.javaField?cap_first!""}() {
    return ${field.javaField};
  }
  public void set${field.javaField?cap_first!""}(${field.javaType!""} ${field.javaField!""}) {
    this.${field.javaField!""} = ${field.javaField!""};
  }

  
  
}

在这里用到了3个指令

  1. string
  • ?cap_first: 将首字母大写
  • !"" : 如果为null或者不存在该属性 就显示空字符串
  1. if
  • 判断当前list不为null and list.size() > 0
  1. list
  • 循环迭代

mybatis.ftl


这里和上面是一样的,不过需要注意的是 ftl文件中如果有包含 # 这种特殊字符, 需要对其进行转义才会成功输出

${r"#{" + field.javaField + "}"}

到此 ftl文件也已经完成, 下面进行正文

查询所有表的sql 使用这个

SHOW TABLE STATUS
实战:代码生成器查询所有表展示效果.png

这里我们需要以下字段 property为实体Tables中的属性


        
        
    

下来查询表的字段

SELECT  * FROM information_schema.COLUMNS where TABLE_NAME = 'tb_users' AND table_schema = 'test' order by column_key desc;
实战:代码生成器查询表中字段展示效果.png

这里我们需要以下字段 property为实体Fields中的属性


  
  
  
  
  

下面是在实体中的操作

Tables.java

public class Tables implements Serializable {
  private String name;
  private String comment;

  private String javaName;

  private List fields;

  private StringUtil mStringInstance = StringUtil.getInstance();

  //这里是对表名进行转换 换成Java命名规范的名称
  public String getJavaName() {
    int i = this.name.indexOf("_");
    this.javaName = i > 0 ? mStringInstance.replaceChar(this.name , "_") : this.name; 

    return this.javaName;
  }

  //getting and setting
}

Fields.java

public class Fields implements Serializable {

  private String tableName;

  private String columnName;

  private String javaField;

  private String jdbcType;

  private String javaType;

  private String columnKey;

  private String columnComment;

  private StringUtil mStringInstance = StringUtil.getInstance();

  public String getJavaField() {
    /** 处理字段中的特殊字符 */
    int i = this.columnName.indexOf("_");
    this.javaField = i > 0 ? mStringInstance.replaceChar(this.columnName,
        "_") : this.columnName;

    return this.javaField;
  }

  public void setJavaField(String javaField) {
    this.javaField = javaField;
  }

  //转换成java类型
  public String getJavaType() {

    /** 处理字段类型 */
    if (this.jdbcType.equalsIgnoreCase("varchar")
        || this.jdbcType.equalsIgnoreCase("char")
        || this.jdbcType.equalsIgnoreCase("text")
        || this.jdbcType.equalsIgnoreCase("longtext")) {
      setJavaType("String");
    } else if (this.jdbcType.equalsIgnoreCase("int")
        || this.jdbcType.equalsIgnoreCase("tinyint")
        || this.jdbcType.equalsIgnoreCase("smallint")
        || this.jdbcType.equalsIgnoreCase("mediumint")) {
      setJavaType("Integer");
    } else if (this.jdbcType.equalsIgnoreCase("date")
        || this.jdbcType.equalsIgnoreCase("time")
        || this.jdbcType.equalsIgnoreCase("datetime")
        || this.jdbcType.equalsIgnoreCase("timestamp")) {
      setJavaType("Date");
    } else if (this.jdbcType.equalsIgnoreCase("double")) {
      setJavaType("Double");
    } else if (this.jdbcType.equalsIgnoreCase("long")
        || this.jdbcType.equalsIgnoreCase("bigint")) {
      setJavaType("Long");
    } else if(this.jdbcType.equalsIgnoreCase("decimal")) {
      setJavaType("BigDecimal");
    } else if(this.jdbcType.equalsIgnoreCase("float")) {
      setJavaType("Float");
    }
    return javaType;
  }
  //getting and setting
}

在App.java中 书写执行程序 推荐使用junit

public class App {
  DaoSupport mDaoSupport;
  FreemarkerUtil mFreemarkerUtil;
  StringUtil mStringUtil;
  String mFilePath;
  String mPackName;
  Map mRoot;

  @Before
  public void before() {
      mDaoSupport = DaoSupport.getInstance();
      mFreemarkerUtil = FreemarkerUtil.getInstance();
      mStringUtil = StringUtil.getInstance();
      mFilePath =  PropUtil.getPropVal("packageName").replace(".", File.separator);
      mPackName = PropUtil.getPropVal("packageName");
      mRoot = new HashMap();
  }

  public List getTables() {

      String tableSchema = mDaoSupport.getPropVal("table_schema");

      SqlSession session = mDaoSupport.getSession();

      /**获取所有的表*/
      TableMapper tm = session.getMapper(TableMapper.class);
      List tables = tm.findAllTables();

      if(tables != null && tables.size() > 0) {
          /**获取表中所有的字段*/
          FieldMapper fm = session.getMapper(FieldMapper.class);
          Map map = null;
          for(Tables table : tables) {
              map = new HashMap();
              map.put("tableSchema", tableSchema);
              map.put("tableName", table.getName());

              List fields = fm.findFieldByTable(map);
              table.setFields(fields);
          }
      }
      return tables;
  }

  @Test
  public void generateCode() {
      List tableses = getTables();

      generate(tableses);
  }

  private void generate(List tableses) {
      //com.sanq.product.freemarker

      //划分文件生成目录
      String controllerPackage = mFilePath + File.separator + "controller";
      String entityPackage = mFilePath + File.separator + "entity";
      String entityVoPackage = entityPackage + File.separator + "vo";
      String servicePackage = mFilePath + File.separator + "service";
      String serviceImplPackage = mFilePath + File.separator + "service" + File.separator + "impl";
      String mapperPackage = mFilePath + File.separator + "mapper";

      //将数据传递给Freemarker
      mRoot.put("tables", tableses);
      mRoot.put("packageName", mPackName.replace(File.separator,"."));
      mRoot.put("controllerPackage", controllerPackage.replace(File.separator,"."));
      mRoot.put("entityPackage", entityPackage.replace(File.separator,"."));
      mRoot.put("entityVoPackage", entityVoPackage.replace(File.separator,"."));
      mRoot.put("servicePackage", servicePackage.replace(File.separator,"."));
      mRoot.put("serviceImplPackage", serviceImplPackage.replace(File.separator,"."));
      mRoot.put("mapperPackage", mapperPackage.replace(File.separator,"."));
      mRoot.put("nowDate", new Date());

      //这里生成文件
      String tableJavaName;
      for(Tables table : tableses) {

          tableJavaName = mStringUtil.firstUpperCase(table.getJavaName());

          mRoot.put("table", table);

          try {
                generateFile("java/entity.ftl", entityPackage, tableJavaName + ".java");
                generateFile("java/entityVo.ftl", entityVoPackage, tableJavaName + "Vo.java");
                generateFile("java/controller.ftl", controllerPackage, tableJavaName + "Controller.java");
                generateFile("java/mapper.ftl", mapperPackage, tableJavaName + "Mapper.java");
                generateFile("java/mapper_xml.ftl", mapperPackage, tableJavaName + "Mapper.xml");
                generateFile("java/service.ftl", servicePackage, tableJavaName + ".Service.java");
                generateFile("java/service_impl.ftl", serviceImplPackage, tableJavaName + ".ServiceImpl.java");

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //生成文件, 如果文件夹不存在, 创建
    public void generateFile(String ftl, String path, String fileName) {
        File file = new File("D:"+ File.separator + "tmp" + File.separator + path);
        if (!file.exists())
            file.mkdirs();

        mFreemarkerUtil.out(ftl, mRoot, "D:"+ File.separator + "tmp" + File.separator + path + File.separator + fileName);
    }
}

到此, 代码编写正式完成

生成结果

实战:代码生成器代码生成展示效果1.png
实战:代码生成器代码生成展示效果2.png
实战:代码生成器代码生成展示效果3.png

可以看到代码已经生成成功

实战结束

到此, 实战完成。 内容不是很多。

好好打磨下这个实战案例,可以基于自己的习惯生成java代码,余下的时间就可以好好的玩耍啦。。。

你可能感兴趣的:(Freemarker 实战: 代码生成器)