Freemarker 实战: 代码生成器

Freemarker 实战: 代码生成器

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

这里只是生成MySQL的代码,其余数据库的话能够自行扩展mysql

开发环境

Maven管理, Freemarker + MyBatis 整合开发git

生成的代码架构:SSM框架redis

Maven 配置spring

<parent>
    <artifactId>x_utils</artifactId>
    <groupId>com.sanq.product.x_utils</groupId>
    <version>1.0-SNAPSHOT</version>
</parent>
<!--依赖-->
<dependencies>
  <dependency>
      <groupId>com.sanq.product.x_utils</groupId>
      <artifactId>util_common</artifactId>
      <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
      <groupId>com.sanq.product.x_utils</groupId>
      <artifactId>util_redis</artifactId>
      <version>1.0-SNAPSHOT</version>
  </dependency>
  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
  </dependency>
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
  </dependency>
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
  </dependency>
  <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
  </dependency>
  <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId> logback-classic</artifactId>
  </dependency>
</dependencies>

<build>
  <finalName>generate</finalName>
  <!--这里配置是为了在打包的时候 这些文件不会被忽略-->
  <resources>
      <resource>
          <directory>src/main/java</directory>
          <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
              <include>**/*.ftl</include>
          </includes>
          <filtering>false</filtering>
      </resource>
      <resource>
          <directory>src/main/resources</directory>
          <includes>
              <include>**/*.properties</include>
              <include>**/*.xml</include>
              <include>**/*.ftl</include>
          </includes>
          <filtering>false</filtering>
      </resource>
  </resources>
</build>
复制代码

关于配置中 x_utils, 你们能够查看代码: 代码查看,sql

题外话数据库

X_Util模块描述安全

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

pom.xml配置完成bash

Mybatis 配置

config.properties中配置数据源等session

# 数据源的配置
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配置

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

  <!--加载config文件-->
	<properties resource="config.properties"/> 
  <!--别名-->
	<typeAliases>  
	    <typeAlias alias="tables" type="com.sanq.product.generate.entity.Tables" />
	    <typeAlias alias="fields" type="com.sanq.product.generate.entity.Fields" />
	</typeAliases> 

  <!--数据源-->
	<environments default="development">
		<environment id="development">
			<transactionManager type="jdbc"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
  <!--mapper映射配置--->
	<mappers>
		<mapper resource="com/sanq/product/generate/mappers/TableMapper.xml" />
		<mapper resource="com/sanq/product/generate/mappers/FieldMapper.xml" />
	</mappers>
</configuration>
复制代码

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")
  </#if>
  private ${field.javaType!""} ${field.javaField!""};
  </#list>
  </#if>

  <#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!""};
  }

  </#list>
  </#if>
}

复制代码

在这里用到了3个指令

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

mybatis.ftl

<select id="findById" resultMap="${table.javaName}Map"
      parameterType="<#list table.fields as field><#if field.columnKey == "PRI">${field.javaType}</#if></#list>">
  SELECT
  <include refid="${table.name}_columns"/>
  FROM ${table.name} ${table.name?substring(0,1)}
  WHERE
  <#list table.fields as field>
      <#if field.columnKey == "PRI">
          ${table.name?substring(0,1)}.${field.columnName} = ${r"#{" + field.javaField + "}"} </#if> </#list> LIMIT 1 </select> 复制代码

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

${r"#{" + field.javaField + "}"} 复制代码

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

查询全部表的sql 使用这个

SHOW TABLE STATUS
复制代码

展现效果

这里咱们须要如下字段 property为实体Tables中的属性

<resultMap type="tables" id="tableEntityMap">
		<result column="Name" property="name"/>
		<result column="Comment" property="comment"/>
	</resultMap>

复制代码

下来查询表的字段

SELECT  * FROM information_schema.COLUMNS where TABLE_NAME = 'tb_users' AND table_schema = 'test' order by column_key desc;
复制代码

展现效果

这里须要注意 在查询表字段的时候须要咱们以前在config.properties配置的schema加上, 否则它会查询出全部库中表名为tb_users的字段.

这里咱们须要如下字段 property为实体Fields中的属性

<resultMap type="fields" id="fieldEntityMap">
  <result column="table_name" property="tableName"/>
  <result column="column_name" property="columnName"/>
  <result column="data_type" property="jdbcType"/>
  <result column="column_key" property="columnKey"/>
  <result column="column_comment" property="columnComment"/>
</resultMap>
复制代码

下面是在实体中的操做

Tables.java

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

  private String javaName;

  private List<Fields> 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<String,Object> 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<String, Object>();
  }

  public List<Tables> getTables() {

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

      SqlSession session = mDaoSupport.getSession();

      /**获取全部的表*/
      TableMapper tm = session.getMapper(TableMapper.class);
      List<Tables> tables = tm.findAllTables();

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

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

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

      generate(tableses);
  }

  private void generate(List<Tables> 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);
    }
}
复制代码

到此, 代码编写正式完成

生成结果

在这里插入图片描述
在这里插入图片描述

能够看到代码已经生成成功

实战结束

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

好好打磨下这个实战案例,能够基于本身的习惯生成java代码,余下的时间就能够好好的玩耍啦。。。

相关文章
相关标签/搜索