springboot集成FreeMarker实现自动生成增删改查代码

文章目录

    • 一,前提知识储备
      • 1,什么是FreeMarker?
      • 2,FreeMarker的基本用法
        • (1)FreeMarker依赖
        • (2)指定模板并生成
        • (3)指定字符串模板并生成
      • 3,测试数据库元数据的操作
        • (1)获取数据库基本信息
        • (2)获取数据库列表
        • (3)获取指定数据库中的表信息
        • (4)获取指定表中的字段信息
        • (5)测试参数元数据
        • (6)测试结果集元数据
    • 二,生成代码实战
      • 1,首先准备四个实体类
      • 2,自定义配置文件
      • 3,准备三个工具类
      • 4,准备生成代码的模板
      • 5,html页面(web页面进行生成)
      • 6,controller类
      • 7,核心类Generator和GeneratorFacade
      • 8,运行项目

一,前提知识储备

1,什么是FreeMarker?

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

2,FreeMarker的基本用法

下面使用测试类来说明FreeMarker的基本用法,其实FreeMarker就是一个模板引擎,和thymeleafbeetle等都是一个品种的,FreeMarker最初的目的只是为了渲染html页面的,不过它可以渲染任何文件,只要你遵循它指定好的语法即可!

(1)FreeMarker依赖

首先创建好一个基本的springboot工程,然后导入FreeMarker依赖,然后就可以开始测试了。

		<dependency>
            <groupId>org.freemarkergroupId>
            <artifactId>freemarkerartifactId>
            <version>2.3.28version>
        dependency>

(2)指定模板并生成

说明:文件路径加载器加载的是存放模板的文件夹的绝对路径,数据模型是Map,给里面存放数据即可,process方法的参数1是数据模型,参数2是Writer对象,执行此方法FreeMarker会自动渲染页面。

 @Test
   public void test0() throws Exception {
        //1,创建FreeMarker的配置类
        Configuration cfg=new Configuration();
        //2,指定模板加载器,将模板加入缓存中
        //文件路径加载器,获取到templates文件的路径
        String templates = this.getClass().getClassLoader().getResource("templates").getPath();
        FileTemplateLoader fileTemplateLoader=new FileTemplateLoader(new File(templates));
        cfg.setTemplateLoader(fileTemplateLoader);
        //3,获取模板
        Template template = cfg.getTemplate("test.ftl");
        //4,构造数据模型
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("username","测试人员");
        map.put("password",1234);

        List<String> list=new ArrayList<>();
        list.add("第一个");
        list.add("第二个");
        map.put("list",list);

        //5,文件输出
        /**
         * 处理模型
         *      参数一 数据模型
         *      参数二 writer对象(FileWriter(文件输出),printWriter(控制台输出))
         */
//        template.process(map,new FileWriter(new File("D:\\a.txt")));
        template.process(map,new PrintWriter(System.out));
    }

test.ftl模板

<#-- assign指令 在ftl模板中定义数据存入到root节点下 -->
<#assign name="傻子">
<#--然后就可以取出name的值-->
${name}

你好,${username}

<#--- if指令 -->
<#if password=1234>
简单密码
<#elseif password=123456>
一般密码
<#else>
复杂密码
</#if>

<#-- list指令 迭代循环 -->
<#list list as abc>
    ${abc}
</#list>

<#--模板包含,这样生成模板test.ftl的时候同时会包含模板test2.ftl-->
<#include "test2.ftl">

(3)指定字符串模板并生成

 //字符串模板
    public static void main(String[] args) throws Exception {
        //1,创建配置对象
        Configuration cfg=new Configuration();
        //2,指定加载器
        cfg.setTemplateLoader(new StringTemplateLoader());
        //3,创建字符串模板
        //字符串
        String templateString="欢迎您,${username}";
        //通过字符串创建模板
        Template template=new Template("templateNames",new StringReader(templateString),cfg);
        //4,构造数据
        Map<String,Object> map=new HashMap<String, Object>();
        map.put("username","测试人员");
        map.put("password",1234);

        List<String> list=new ArrayList<>();
        list.add("第一个");
        list.add("第二个");
        map.put("list",list);

        //5,处理模板
        template.process(map,new PrintWriter(System.out));
    }

3,测试数据库元数据的操作

(1)获取数据库基本信息

	 @Test
    public void test01() throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";//没有指定具体哪个数据库,现在获取的是整个连接
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        //获取数据库基本信息
        System.out.println(metaData.getUserName());
        System.out.println(metaData.supportsTransactions());//是否支持事务
        System.out.println(metaData.getDatabaseProductName());//数据库类型(MYSQL)
        connection.close();

        /**
         * 打印结果如下
         * root@localhost
         * true
         * MySQL
         */
    }

(2)获取数据库列表

	@Test
    public void test2() throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";//没有指定具体哪个数据库,现在获取的是整个连接
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        //获取数据库列表名称
        ResultSet resultSet = metaData.getCatalogs();
        while (resultSet.next()){
            System.out.println(resultSet.getString(1));
        }
        resultSet.close();
        connection.close();
        /**
         * 打印连接中的所有数据库名称
         * activiti
         * dage
         * dk
         * dk1
         * dk_front
         * dk_front1
         * information_schema
         * laji
         * light_master
         * mysql
         * performance_schema
         * sakila
         * solr
         * sys
         * test
         * workflow
         * world
         */
    }

(3)获取指定数据库中的表信息

打印出该数据库下的所有表名。

 @Test
    public void test3() throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://127.0.0.1:3306/laji?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=true";//指定了数据库
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        //获取数据库中表信息(mysql可以这样写,oracle会有一点区别)
        //参数1:当前操作的数据库 参数2:mysql可为空,oracle填写用户名(要大写) 参数3:null是查询所有表 非空是查询目标表 参数4:类型 table是表,view是视图
        ResultSet resultSet = metaData.getTables("laji", null, null, new String[]{"TABLE"});
        while (resultSet.next()){
        	//会打印出该数据库下的所有表名
            System.out.println(resultSet.getString("TABLE_NAME"));
        }
        resultSet.close();
        connection.close();
    }

(4)获取指定表中的字段信息

打印出指定表的所有字段名。

 public static void main(String[] args) throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        ResultSet city = metaData.getColumns("laji", null, "user", null);
        while (city.next()){
        	//会打印出指定表的所有字段名
            System.out.println(city.getString("COLUMN_NAME"));
        }
    }

(5)测试参数元数据

/**
     * 测试参数元数据
     * 通过preparedStatement获取
     * 目的:获取sql参数中的属性信息
     */
    @Test
    public void test4() throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        String sql="select * from user where id=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);//设第一个参数为int 1
        //获取参数元数据
        ParameterMetaData metaData = preparedStatement.getParameterMetaData();
        //得到参数的个数
        int count = metaData.getParameterCount();//打印 1 (只有一个id参数)

        System.out.println(count);
        preparedStatement.close();
        connection.close();
    }

(6)测试结果集元数据

 @Test
    public void test5() throws Exception{
        String driver="com.mysql.cj.jdbc.Driver";
        String url="jdbc:mysql://127.0.0.1:3306/laji?characterEncoding=utf8&serverTimezone=UTC";//指定了数据库
        String username="root";
        String password="1234";

        //获取连接
        Class.forName(driver);//注册驱动
        Connection connection = DriverManager.getConnection(url, username,password);

        String sql="select * from user where id=?";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setInt(1,1);//设第一个参数为int 1
        //查询
        ResultSet resultSet = preparedStatement.executeQuery();

        //获取结果集元数据
        ResultSetMetaData metaData = resultSet.getMetaData();
        //获取查询字段个数
        int count = metaData.getColumnCount();
        for(int i=1;i<=count;i++){
            //获取列名
            String columnName = metaData.getColumnName(i);//第i个列
            //获取字段类型  sql类型 varchar
            int columnType = metaData.getColumnType(i);
            //获取java类型  String
            String columnClassName = metaData.getColumnClassName(i);
            System.out.println(columnName+"---"+columnType+"---"+columnClassName);
        }

        preparedStatement.close();
        connection.close();
    }
    /**
     * 打印
     * id---4---java.lang.Integer
     * account---12---java.lang.String
     * password---12---java.lang.String
     * islogin---4---java.lang.Integer
     */

二,生成代码实战

1,首先准备四个实体类

DataBase实体

package com.ftx.demo.model;

/**
 * @author FanJiangFeng
 * @version 1.0.0
 * @ClassName DataBase.java
 * @Description TODO
 * @createTime 2020年04月23日 14:15:00
 */
public class DataBase {

    private static String mysqlUrl="jdbc:mysql://[ip]:[port]/[db]?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC";
    private static String oracleUrl="jdbc:oracle:thin:@[ip]:[port]:[db]";

    private String dbType;//数据库类型
    private String userName;
    private String passWord;
    private String driver;
    private String url;

    public DataBase(){}
    public DataBase(String dbType){
        this(dbType,"127.0.0.1","3306","");
    }
    public DataBase(String dbType,String db){
        this(dbType,"127.0.0.1","3306",db);
    }
    /**
     *
     * @param dbType    数据库类型 mysql/oracle
     * @param ip        ip
     * @param port      3306
     * @param db        数据库名称 test
     */
    public DataBase(String dbType,String ip,String port,String db){
        this.dbType=dbType;
        if("MYSQL".equals(dbType.toUpperCase())){
            this.driver="com.mysql.cj.jdbc.Driver";
            this.url=mysqlUrl.replace("[ip]",ip).replace("[port]",port).replace("[db]",db);
        }else{
            this.driver="oracle.jdbc.driver.OracleDriver";
            this.url=oracleUrl.replace("[ip]",ip).replace("[port]",port).replace("[db]",db);
        }
    }
}

Settings实体

public class Settings {
    private String project="example";
    private String pPackage="com.example.demo";
    private String projectComment;
    private String author;
    private String path1="com";
    private String path2="example";
    private String path3="demo";
    private String pathAll;
}

Table实体

//表实体
public class Table {
    private String name;//表名称
    private String name2;//处理后的表名称
    private String comment;//介绍
    private String key;//主键列
    private List<Column> columnList;

Column实体

public class Column {
    private String columnName;//列名称
    private String columnName2;//处理后的列名称
    private String columnType;//列类型
    private String columnDbType;//列在数据库中的类型

    //本工程暂不处理备注和主键
    private String columnComment;//列备注id
    private String columnKey;//是否是主键

2,自定义配置文件

自定义配置文件中配置数据库和Java对应的数据类型
springboot集成FreeMarker实现自动生成增删改查代码_第1张图片

3,准备三个工具类

FileUtils工具类
该工具类用到的主要是 查询整个目录的文件夹 和 递归获取某个目录下的所有文件夹 的方法

package com.ftx.demo.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

//文件处理工具类
public class FileUtils {
    //得到相对路径
    public static String getRelativePath(File baseDir,File file){
        if(baseDir.equals(file)){
            return "";
        }
        if(baseDir.getParentFile()==null){
            return file.getAbsolutePath().substring(baseDir.getAbsolutePath().length());
        }else{
            return file.getAbsolutePath().substring(baseDir.getAbsolutePath().length()+1);
        }
    }
    //查询整个目录下的所有文件
    public static List<File> searchAllFile(File dir) throws IOException {
        ArrayList arrayList=new ArrayList();
        searchFiles(dir,arrayList);
        return arrayList;

    }
    //递归获取某个目录下的所有文件
    public static void searchFiles(File dir,List<File> collector){
        if(dir.isDirectory()){
            File[] files = dir.listFiles();
            for(int i=0;i<files.length;i++){
                searchFiles(files[i],collector);
            }
        }else{
            collector.add(dir);
        }
    }
    //创建文件
    public static File mkdir(String dir,String file){
        if(dir==null){
            throw new  IllegalArgumentException("文件夹不许为空");
        }
        File result=new File(dir,file);
        if(result.getParentFile()!=null){
            result.getParentFile().mkdirs();
        }
        return result;
    }
}

PropertiesUtils工具类
此工具类说明:静态代码块预加载,将自定义的配置文件properties的内容全部加载到customMap中,然后在其他类中调用此类获取customMap中的键值对(键值对就是字都应以配置文件中所配置的内容)

package com.ftx.demo.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

//需要将自定义的配置信息写入到properties文件中,配置到相对于工程的properties文件下
public class PropertiesUtils {
    public static Map<String,String> customMap=new HashMap<>();

    //静态块,预加载,将自定义的配置文件properties的内容全部加载到customMap中
    static {
        File dir=new File("properties");
        try {
            List<File> files = FileUtils.searchAllFile(new File(dir.getAbsolutePath()));
            for(File file:files){
                if(file.getName().endsWith("properties")){
                    Properties prop=new Properties();
                    prop.load(new FileInputStream(file));
                    customMap.putAll((Map)prop);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //测试预加载是否成功(看是否打印出了properties配置文件的key和value)
    public static void main(String[] args) {
       for(String key:customMap.keySet()){
           System.out.println(key+"---"+customMap.get(key));
       }
    }
}

DataBaseUtils工具类

方法介绍:
1,获取数据库连接
2,获取数据库列表
3,获取数据库中的所有表和字段并构造实体类
4,根据表名的截取操作生成类名

package com.ftx.demo.util;
import com.ftx.demo.model.Column;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Table;
import org.junit.Test;
import javax.servlet.http.HttpSession;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class DataBaseUtils {
    //获取数据库连接
    public static Connection getConnection(DataBase db) throws Exception{
        //获取连接
        Class.forName(db.getDriver());//注册驱动
        Connection connection = DriverManager.getConnection(db.getUrl(), db.getUserName(),db.getPassWord());
        return connection;
    }

    //获取数据库列表
    public static List<String> getShemas(DataBase db) throws Exception{
        Connection connection = getConnection(db);
        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        //获取所有数据库列表
        ResultSet resultSet = metaData.getCatalogs();
        List<String> list=new ArrayList<>();
        while(resultSet.next()){
            list.add(resultSet.getString(1));
        }
        resultSet.close();
        connection.close();
        return list;
    }

    //获取数据库中的所有表和字段并构造实体类(相当于一键生成数据库中所有表的增删改查代码)
    public static List<Table> getDbInfo(DataBase db, HttpSession session) throws Exception{
        //获取连接
        Connection connection = getConnection(db);
        //获取元数据
        DatabaseMetaData metaData = connection.getMetaData();
        List<Table> list=new ArrayList<>();
        //获取当前数据库的所有表
        String dbss=(String) session.getAttribute("db");
        ResultSet tables = metaData.getTables(dbss, null, null, new String[]{"TABLE"});
        while (tables.next()){
            //表名
            String table_name = tables.getString("TABLE_NAME");
            //构造生成对应实体类的类名
            String className = removePrefix(table_name);
            //主键
            ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, table_name);
            //对主键遍历的原因(或许一张表有多个主键)
            String keys="";
            while (primaryKeys.next()){
                String keyName = primaryKeys.getString("COLUMN_NAME");
                keys+=keyName+",";
            }
            Table tab=new Table();
            tab.setName(table_name);
            tab.setName2(className);
            tab.setKey(keys);
            //处理表中的所有字段
            ResultSet columns = metaData.getColumns(dbss, null, table_name, null);
            List<Column> cols=new ArrayList<>();
            while (columns.next()){
                Column column=new Column();
                //列名称
                String column_name = columns.getString("COLUMN_NAME");
                //java实体的属性名
                String attName = column_name;
                //java类型和数据库类型
                String type_name = columns.getString("TYPE_NAME");
                String javaType = PropertiesUtils.customMap.get(type_name);
                column.setColumnName(column_name);
                column.setColumnName2(attName);
                column.setColumnDbType(type_name);
                column.setColumnType(javaType);
                cols.add(column);
            }
            tab.setColumnList(cols);
            list.add(tab);
            //关闭连接,释放资源
            columns.close();
            primaryKeys.close();
        }
        tables.close();
        connection.close();
        return list;

    }

    //根据表名的截取操作生成类名
    public static String removePrefix(String tableName){
        //从自定义的配置文件中拿到前缀的配置
        String prefixes = PropertiesUtils.customMap.get("tableRemovePrefixes");
        //这里就不字符串处理了,直接把表名当类名用了
        String replace = tableName;
        return replace;
    }

    // 测试 获取数据库中的所有表和字段并构造实体类 的方法是否可用
//    public static void main(String[] args) throws Exception {
//        DataBase db=new DataBase("MYSQL","laji");
//        db.setUserName("root");
//        db.setPassWord("1234");
//        List dbInfo = DataBaseUtils.getDbInfo(db);//        for(Table table:dbInfo){//            System.out.println(dbInfo);//        }//    }}

4,准备生成代码的模板

resources静态资源中添加生成代码的FreeMarker模板,如下图
springboot集成FreeMarker实现自动生成增删改查代码_第2张图片

package ${pPackage}.dao;

import java.util.List;
import java.util.Map;

public interface ${table.name?cap_first}Dao {

    /**
     * 查询
     */
    List<Map> get${table.name?cap_first}List();

    /**
     * 添加
     */
    int add${table.name?cap_first}(Map map);

    /**
     * 待修改
     */
    Map toEdit${table.name?cap_first}(${table.columnList[0].columnType} ${table.columnList[0].columnName});

    /**
     * 修改
     */
    int update${table.name?cap_first}(Map map);

    /**
     * 删除
     */
    int delete${table.name?cap_first}(${table.columnList[0].columnType} ${table.columnList[0].columnName});

}

package ${pPackage}.model;


public class ${table.name?cap_first} {  //?cap_first freemarker内置函数 首字母大写

<#list table.columnList as abc>
private ${abc.columnType} ${abc.columnName};
</#list>



<#list table.columnList as abc>
public ${abc.columnType} get${abc.columnName?cap_first}() {
        return this.${abc.columnName};
        }

public void set${abc.columnName?cap_first}(String ${abc.columnName}) {
        this.${abc.columnName} = ${abc.columnName};
        }
</#list>


        }

5,html页面(web页面进行生成)

index.html首页


    <table style="height: 100%;width: 100%;" border="3">
        <form action="">
        <tr>
            <td>数据库类型td>
            <td><select name="dbKind" id="dbKind">
                <option value="MYSQL">MYSQLoption>
                <option value="ORACLE">ORACLEoption>
            select>td>
        tr>
        <tr>
            <td>数据库IPtd>
            <td><input id="ip" type="text" name="ip" required><span style="color: red;">*必填项span>td>
        tr>
        <tr>
            <td>端口号td>
            <td><input id="port" type="text" name="port" required><span style="color: red;">*必填项span>td>
        tr>
        <tr>
            <td>用户名td>
            <td><input id="username" type="text" name="username" required><span style="color: red;">*必填项span>td>
        tr>
        <tr>
            <td>密码td>
            <td><input id="password" type="password" name="password" required><span style="color: red;">*必填项span>td>
        tr>
        <tr>
            <td><input id="test" type="button" value="测试连接"><span style="color: red;">*必选项span>td>
            <td>选择数据库  <select name="databases" id="databases">

            select>td>
            <td><input id="next" type="button" value="下一步">td>
        tr>
        form>
    table>

<script th:src="@{../jquery.min.js}">script>
<script>
    $("#test").click(function () {
          $.post(
              "/testConnection",
              {dbKind:$("#dbKind").val(),ip:$("#ip").val(),port:$("#port").val(),username:$("#username").val(),password:$("#password").val()},
              function (data) {
                 if(data.length>0){
                     alert("连接成功");
                     var temp="";
                     for(var i=0;i<data.length;i++){
                         temp+='data[i]+'">'+data[i]+'';
                     }
                     $("#databases").html(temp);
                 }else{
                     alert("连接失败");
                 }
              },
              "json"
          );
    });
    $("#next").click(function () {
        location.href="/enter1?databases="+$("#databases").val()+"&username="+$("#username").val()+"&password="+$("#password").val();
    });
script>

index1.html下一步的页面



    <table style="height: 100%;width: 100%;" border="3">
        <form action="/create" method="post">
        <tr>
            <td>模板td>
            <td><select name="dbKind" id="dbKind">
                <option value="springboot程序的模板">springboot程序的模板option>
            select>td>
        tr>
        <tr>
            <td>代码生成路径td>
            <td><input id="fileUrl" type="text" name="fileUrl" /><span style="color: red;">*必填项span>td>
        tr>
        <tr>
            <td>项目名td>
            <td><input id="projectEngName" type="text" name="projectEngName" required>td>
        tr>
        <tr>
            <td>包名td>
            <td><input id="packagename" type="text" name="packagename" required><span style="color: red;">*必填项(格式必须为com.ftx.demo这种)span>td>
        tr>
        <tr>
            <td>项目中文名称td>
            <td><input id="projectChinaName" type="text" name="projectChinaName" required>td>
        tr>
            <tr>
                <td>数据库td>
                <td><input id="db" type="text" name="db" th:value="*{databases}" readonly>td>
            tr>
            <tr>
                <td>用户名td>
                <td><input id="root" type="text" name="root" th:value="*{username}" readonly>td>
            tr>
            <tr>
                <td>密码td>
                <td><input id="psd" type="text" name="psd" th:value="*{password}" readonly>td>
            tr>
        <tr>
            <td><input id="test" type="submit" value="生成代码">td>
            <td><input type="button" value="关闭">td>
        tr>
        form>
    table>

result.html反馈结果页面

<h3 style="text-align:center;color: blue;" th:text="${success}">h3>

6,controller类

package com.ftx.demo.controller;
import com.ftx.demo.core.GeneratorFacade;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Settings;
import com.ftx.demo.util.DataBaseUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Map;

@Controller
public class GeneratorController {
    @RequestMapping("/enter")
    public String index(){
        return "index.html";
    }
    @RequestMapping("/enter1")
    public String index1(String databases, String username, String password, Model model){
        model.addAttribute("databases",databases);
        model.addAttribute("username",username);
        model.addAttribute("password",password);
        return "index1.html";
    }
    //点击测试连接按钮,填充选择数据库下拉框
    @RequestMapping("/testConnection")
    @ResponseBody
    public List<String> testConnection(String dbKind,String ip,String port,String username,String password) throws Exception{
        DataBase dataBase=new DataBase(dbKind,ip,port,"");
        dataBase.setUserName(username);
        dataBase.setPassWord(password);
        List<String> dbs = DataBaseUtils.getShemas(dataBase);
        if(dbs.size()>0){
           return dbs;
        }else{
            return null;
        }
    }

    //生成代码
    /**
     * 确认生成按钮传来的参数
     * @param dbKind   选择模板,这里没啥用,因为已经写死模板位置了  resources/springboot程序的模板  文件夹
     * @param fileUrl   代码生成路径
     * @param projectEngName    项目英文名称 没啥用
     * @param packagename      包名 com.ftx.demo
     * @param projectChinaName  项目中文名称,也没啥用
     * @param db    选择的数据库名
     * @param root  数据库的用户名root
     * @param psd   数据库的密码
     * @throws Exception
     *
     * 下面用sesson的目的很简单,不要想多了,是存了数据库名,到后来的方法要取出来,我这是把它当成全局变量使用了
     */
    @RequestMapping("/create")
    public String create(String dbKind, String fileUrl, String projectEngName, String packagename, String projectChinaName,
                       String db, String root, String psd, HttpSession session,Model model) throws Exception{

        Settings settings=new Settings();
        //包名
        settings.setpPackage(packagename);//com.ftx.demo
        String[] split = packagename.split("\\.");//split(".")无法分割字符串,必须加上\\
        settings.setPath1(split[0]);//com
        settings.setPath2(split[1]);//ftx
        settings.setPath3(split[2]);//demo

        //项目名(没啥用)
        settings.setProject(projectEngName);
        //默认只支持mysql数据库吧,oracle暂时先不处理,先写死为mysql
        session.setAttribute("db",db);
        DataBase dbs=new DataBase("MYSQL",db);
        dbs.setUserName(root);
        dbs.setPassWord(psd);
        GeneratorFacade generatorFacade=new GeneratorFacade(dbKind,fileUrl,settings,dbs);
        boolean b = generatorFacade.generatorByDataBase(session);
        if(b){
            model.addAttribute("success","代码已生成,回文件查看!");
            return "result";
        }else{
            model.addAttribute("success","代码生成失败!");
            return "result";
        }
    }
}

7,核心类Generator和GeneratorFacade

Generator
代码生成器的核心处理类,使用FreeMarker完成文件生成,数据模型+模板

package com.ftx.demo.core;
import com.ftx.demo.util.FileUtils;
import freemarker.cache.FileTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.File;
import java.io.FileWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

//代码生成器的核心处理类,使用FreeMarker完成文件生成,数据模型+模板
//数据:数据模型 模板的位置  生成文件的路径
public class Generator {
    private String templatePath;//模板路径
    private String outPath;//代码生成路径
    private Configuration cfg;

    public Generator(String templatePath, String outPath)throws Exception {
        this.outPath = outPath;
        //实例化Configuration对象
        cfg=new Configuration();
        //指定模板加载器
        //在代码中动态加载jar、资源文件的时候,首先应该是使用Thread.currentThread().getContextClassLoader()。
        // 如果你使用Test.class.getClassLoader(),可能会导致和当前线程所运行的类加载器不一致(因为Java天生的多线程)
        String templates = Thread.currentThread().getContextClassLoader().getResource("").getPath()+"\\springboot程序的模板\\";
        this.templatePath = templates;
        FileTemplateLoader fileTemplateLoader=new FileTemplateLoader(new File(templates));
        cfg.setTemplateLoader(fileTemplateLoader);
    }
    /**
     * 代码生成
     *      1,扫描模板路径下的所有模板
     *      2,对每个模板进行文件生成(数据模板)
     *      3,参数:数据模板
     */
    public  void scanAndGenerator(Map<String,Object> dataModel)throws Exception{
        //根据模板路径找到此路径下的所有模板文件
        List<File> fileList = FileUtils.searchAllFile(new File(templatePath));
        //对每个模板进行文件生成
        for(File file:fileList){
            //参数1:数据模型  参数2:文件模板
            excuteGenerator(dataModel,file);
        }
    }
    //对模板进行文件生成
    //参数1:数据模型  参数2:文件模板
    private void excuteGenerator(Map<String, Object> dataModel, File file) throws Exception {
        //1,文件路径处理
        /**
         * file:D:/模板存在的文件夹/${path1}/${path2}/${path3}/${classname}.java  绝对路径
         * replace的目的:得到文件名,文件名之前的路径都不要了,只留包名之后的内容
         */
        //  得到模板文件的这样的路径 ${path1}/${path2}/${path3}/${className}.java
        String one=file.getAbsolutePath();
        int i = one.indexOf("$");
        String templateFileName = one.substring(i);
        //把${path1}/${path2}/${path3}/${className}.java 替换成 com/ftx/demoUser.java (数据模型中的内容)
        String outFileName = processString(templateFileName, dataModel);
        //2,读取文件模板
        //上面把模板整个文件夹都加载到了模板加载器,所以这里拿模板只需要传入该文件夹下的文件名即可
        Template template = cfg.getTemplate(templateFileName);//相对路径 ${path1}/${path2}/${path3}/${classname}.java
        template.setOutputEncoding("utf-8");//指定生成文件的字符集编码
        //3,创建文件
        File file1 = FileUtils.mkdir(this.outPath, outFileName);
        //4,模板处理(文件生成)
        FileWriter fileWriter=new FileWriter(file1);
        System.err.println(dataModel);
        template.process(dataModel,fileWriter);
        fileWriter.close();
    }
    /**
     * 把${path1}/${path2}/${path3}/${className}.java 替换成 com/ftx/demoUser.java (数据模型中的内容)
     * FreeMarker的字符串模板 替换
     */
    public String processString(String templateString,Map dataModel) throws Exception{
        StringWriter stringWriter=new StringWriter();
        Template template=new Template("ts",new StringReader(templateString),cfg);
        template.process(dataModel,stringWriter);
        return stringWriter.toString();
    }
    /**
     * 测试代码生成主类scanAndGenerator是否管用
     */
    public static void main(String[] args) throws Exception {
        String templatePath="D:\\工作\\学习资料\\FreeMarker\\模板";
        String outPath="D:\\工作\\学习资料\\FreeMarker\\生成路径";
        Generator generator=new Generator(templatePath,outPath);
        Map<String,Object> dataModel=new HashMap<>();
        dataModel.put("username","张三");
        generator.scanAndGenerator(dataModel);
    }
}

GeneratorFacade
准备数据模型,调用核心处理类Generator类完成代码生成工作

package com.ftx.demo.core;
import com.ftx.demo.model.DataBase;
import com.ftx.demo.model.Settings;
import com.ftx.demo.model.Table;
import com.ftx.demo.util.DataBaseUtils;
import com.ftx.demo.util.PropertiesUtils;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GeneratorFacade {
    //模板位置
    private String templatePath;
    //代码生成路径
    private String outPath;
    //工程配置对象
    private Settings settings;
    //数据库对象
    private DataBase db;
    private Generator generator;

    public GeneratorFacade(String templatePath, String outPath, Settings settings, DataBase db) throws Exception {
        this.templatePath = templatePath;
        this.outPath = outPath;
        this.settings = settings;
        this.db = db;
        generator=new Generator(templatePath,outPath);
    }

    /**
     * 准备数据模型
     * 调用核心处理类完成代码生成工作
     */
    public boolean generatorByDataBase(HttpSession session) throws Exception{
        List<Table> tables = DataBaseUtils.getDbInfo(db,session);
        for(Table table:tables){
            //对每个table进行代码生成
            Map<String, Object> dataModel = getDataModel(table);
            /**
             * 得到的数据模型如下
             * {NUMBER=Long, CHAR=String, project=test, BIGINT=Long, TEXT=String, className=r, VARCHAR2=String,
             * INT=Integer, NVARCHAR2=String, DATE=java.util.Date, DATETIME=java.util.Date, path1=com, path2=ftx,
             * path3=demo, pPackage=com.ftx.demo, VARCHAR=String, testKey=testValue, DOUBLE=Double, tableRemovePrefixes="tb_,co_",
             * table=Table{name='user', name2='r', comment='null', key='userid,userno,id,Host,User,id,',
             * columnList=[Column{columnName='id', columnName2='id', columnType='Integer', columnDbType='INT', columnComment='null',
             * columnKey='null'}, Column{columnName='account', columnName2='account', columnType='String', columnDbType='VARCHAR',
             * columnComment='null', columnKey='null'}, Column{columnName='password', columnName2='password', columnType='String',
             * columnDbType='VARCHAR', columnComment='null', columnKey='null'}, Column{columnName='islogin', columnName2='islogin',
             * columnType='Integer', columnDbType='INT', columnComment='null', columnKey='null'}]}}
             */

            //调用代码生成方法,把数据模型传过去,进行生成
            generator.scanAndGenerator(dataModel);
        }
        return true;
    }
    /**
     * 根据table对象获取数据模型
     * @param table
     * @return
     */
    private Map<String,Object> getDataModel(Table table) {
        Map<String,Object> map=new HashMap<>();
        //自定义配置
        map.putAll(PropertiesUtils.customMap);
        //元数据
        map.put("table",table);
        //settings
        map.put("project",this.settings.getProject());
        map.put("pPackage",this.settings.getpPackage());
        map.put("path1",this.settings.getPath1());
        map.put("path2",this.settings.getPath2());
        map.put("path3",this.settings.getPath3());
        //类名
        map.put("className",table.getName2());
        return map;
    }
}

8,运行项目

springboot集成FreeMarker实现自动生成增删改查代码_第3张图片
springboot集成FreeMarker实现自动生成增删改查代码_第4张图片
springboot集成FreeMarker实现自动生成增删改查代码_第5张图片
springboot集成FreeMarker实现自动生成增删改查代码_第6张图片
springboot集成FreeMarker实现自动生成增删改查代码_第7张图片
springboot集成FreeMarker实现自动生成增删改查代码_第8张图片
springboot集成FreeMarker实现自动生成增删改查代码_第9张图片
springboot集成FreeMarker实现自动生成增删改查代码_第10张图片
工程说明

demo工程启动之后,测试连接成功之后选择生成的数据库,然后会生成该数据库中所有表的增删改查,暂时本工程只支持自动生成model实体类和dao接口,要其他的话你只需要增加FreeMarker模板即可!

你可能感兴趣的:(FreeMarker,数据库)