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代碼,餘下的時間就能夠好好的玩耍啦。。。

相關文章
相關標籤/搜索