手写代码生成器

前言

最近工作的时候研究了一下线上生成器的代码,自己仿造着写了一个

1.新建一个数据库,添加两张表
在这里插入图片描述
2.建立对应的列,创建合适的约束,写好注释
在这里插入图片描述
在这里插入图片描述
3.开始搭建工程,我使用的是SpringBoot快速搭建,引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--velocity代码生成使用模板 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity</artifactId>
            <version>1.7</version>
        </dependency>

        <!--mybatis -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.4</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!--druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.28</version>
        </dependency>
        <!-- common -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>commons-configuration</groupId>
            <artifactId>commons-configuration</artifactId>
            <version>1.10</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <!-- 引入模板引擎-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

4.编写yml文件

server:
  port: 8009

spring:
  thymeleaf:
    mode: LEGACYHTML5
    cache: false
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/seckill?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
    username: root
    password: 123456
    initialSize: 1
    minIdle: 3
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 30000

5.新建一个工具包,里面放模板文件
在这里插入图片描述
6.根据自己的喜好去编写,之后生成的类、页面或Mapper配置都是按你编写的来生成的,不过是其中的一些关键值不一样,这就是模板生成器,放一个domain示例

package ${package}.domain;

import java.io.Serializable;
import java.util.Date;

#if(${hasBigDecimal})
import java.math.BigDecimal;
#end


/**
 * ${comments}
 *
 * @author ${author}
 * @email ${email}
 * @date ${datetime}
 */
public class ${className}DO implements Serializable {
    private static final long serialVersionUID = 1L;

    #foreach ($column in $columns)
        //$column.comments
        private $column.attrType $column.attrname;
    #end

    #foreach ($column in $columns)
        /**
         * 设置:${column.comments}
         */
        public void set${column.attrName}($column.attrType $column.attrname) {
            this.$column.attrname = $column.attrname;
        }

        /**
         * 获取:${column.comments}
         */
        public $column.attrType get${column.attrName}() {
            return $column.attrname;
        }
    #end
}

7.编写之后需要的一些配置信息,generator.properties

#代码生成器,配置信息
#包名
package=com.generator.system
#作者
author=ZNJ
#Email
email=1250622989@qq.com
#自动去除表前缀,默认是 true 
autoRemovePre=true
#表前缀(类名不会包含表前缀)
tablePrefix=sys_

#类型转换,配置信息
tinyint=Integer
smallint=Integer
mediumint=Integer
int=Integer
integer=Integer
bigint=Long
float=Float
double=Double
decimal=BigDecimal
bit=Boolean

char=String
varchar=String
tinytext=String
text=String
mediumtext=String
longtext=String

date=Date
datetime=Date
timestamp=Date

8.准备工作完成,开始正式撸代码,编写Controller的代码

@Controller
public class GenerateController {
    @Autowired
    GeneratorService generatorService;

    @RequestMapping("/index")
    String welcome() {
        return "index";
    }

    @RequestMapping("/batchCode")
    public void batchCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        String[] tableNames = new String[] {"seckill","success_killed"};
        byte[] data = generatorService.generatorCode(tableNames);
        response.reset();
        response.setHeader("Content-Disposition", "attachment; filename=\"bootdo.zip\"");
        response.addHeader("Content-Length", "" + data.length);
        response.setContentType("application/octet-stream; charset=UTF-8");

        IOUtils.write(data, response.getOutputStream());
    }
}

/index:开始访问这个路径,去显示一个页面,然后点击超链接来访问生成代码的方法。
/batchCode:具体的生成代码的方法,注意,我这里为了简单直接写死了需要生成的表名,实际开发中不可。

9.编写Service中的代码

@Service
public class GeneratorServiceImpl implements GeneratorService {
    @Autowired
    GeneratorMapper generatorMapper;

    @Override
    public byte[] generatorCode(String[] tableNames) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zip = new ZipOutputStream(outputStream);
        for(String tableName : tableNames){
            //查询表信息
            Map<String, String> table = generatorMapper.get(tableName);
            //查询列信息
            List<Map<String, String>> columns = generatorMapper.listColumns(tableName);
            //生成代码
            try {
                GenUtils.generatorCode(table, columns, zip);
            } catch (ConfigurationException e) {
                e.printStackTrace();
            }
        }
        IOUtils.closeQuietly(zip);
        return outputStream.toByteArray();
    }
}

10.编写Mapper中的代码

 @Select("select table_name tableName, engine, table_comment tableComment, create_time createTime from information_schema.tables \r\n"
            + "	where table_schema = (select database()) and table_name = #{tableName}")
    Map<String, String> get(String tableName);

    @Select("select column_name columnName, data_type dataType, column_comment columnComment, column_key columnKey, extra from information_schema.columns\r\n"
            + " where table_name = #{tableName} and table_schema = (select database()) order by ordinal_position")
    List<Map<String, String>> listColumns(String tableName);

11.util中的方法
具体的生成规则,还是要根据各位的需求去生成,大致的改变即可

 public static List<String> getTemplates() {
        List<String> templates = new ArrayList<String>();
        templates.add("generator/domain.java.vm");
        templates.add("generator/Dao.java.vm");
        //templates.add("templates/common/generator/Mapper.java.vm");
        templates.add("generator/Mapper.xml.vm");
        templates.add("generator/Service.java.vm");
        templates.add("generator/ServiceImpl.java.vm");
        templates.add("generator/Controller.java.vm");
        templates.add("generator/list.html.vm");
        templates.add("generator/add.html.vm");
        templates.add("generator/edit.html.vm");
        templates.add("generator/list.js.vm");
        templates.add("generator/add.js.vm");
        templates.add("generator/edit.js.vm");
        //templates.add("templates/common/generator/menu.sql.vm");
        return templates;
    }

    public static String columnToJava(String columnName) {
        //WordUtils.capitalizeFully(columnName, new char[]{'_'})
        //in_stock_pp ====>In_Stock_Pp  instockpp ===>Instockpp
        return WordUtils.capitalizeFully(columnName, new char[]{'_'}).replace("_", "");
    }

    /**
     * 获取配置信息
     */
    public static Configuration getConfig() throws ConfigurationException {
        return new PropertiesConfiguration("generator.properties");
    }

    public static void generatorCode(Map<String, String> table, List<Map<String, String>> columns, ZipOutputStream zip) throws ConfigurationException {
        //配置信息
        Configuration config = getConfig();
        //表信息
        TableDO tableDO = new TableDO();
        tableDO.setTableName(table.get("tableName"));
        tableDO.setComments(table.get("tableComment"));
        //这里直接用表名作为java类名,有需要可以进行定制化
        String className = columnToJava(tableDO.getTableName());
        tableDO.setClassName(className);

        //StringUtils.uncapitalize将首字母大写的转为小写
        //INstock ==>iNstock
        tableDO.setClassname(StringUtils.uncapitalize(className));
        //列信息
        List<ColumnDO> columsList = new ArrayList<>();
        for (Map<String, String> column : columns) {
            ColumnDO columnDO = new ColumnDO();
            columnDO.setColumnName(column.get("columnName"));
            columnDO.setDataType(column.get("dataType"));
            columnDO.setComments(column.get("columnComment"));
            columnDO.setExtra(column.get("extra"));

            //列名转换成Java属性名
            String attrName = columnToJava(columnDO.getColumnName());
            columnDO.setAttrName(attrName);
            columnDO.setAttrname(StringUtils.uncapitalize(attrName));

            //列的数据类型,转换成Java类型
            String attrType = config.getString(columnDO.getDataType(), "unknowType");
            columnDO.setAttrType(attrType);

            //是否主键
            if ("PRI".equalsIgnoreCase(column.get("columnKey")) && tableDO.getPk() == null) {
                tableDO.setPk(columnDO);
            }

            columsList.add(columnDO);
        }
        tableDO.setColumns(columsList);

        //没主键,则第一个字段为主键
        if (tableDO.getPk() == null) {
            tableDO.setPk(tableDO.getColumns().get(0));
        }

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        //封装模板数据
        Map<String, Object> map = new HashMap<>(16);
        map.put("tableName", tableDO.getTableName());
        map.put("comments", tableDO.getComments());
        map.put("pk", tableDO.getPk());
        map.put("className", tableDO.getClassName());
        map.put("classname", tableDO.getClassname());
        map.put("pathName", config.getString("package").substring(config.getString("package").lastIndexOf(".") + 1));
        map.put("columns", tableDO.getColumns());
        map.put("package", config.getString("package"));
        map.put("author", config.getString("author"));
        map.put("email", config.getString("email"));
        map.put("datetime", DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN));
        VelocityContext context = new VelocityContext(map);

        //获取模板列表
        List<String> templates = getTemplates();
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);
            //添加到zip
            try {
                zip.putNextEntry(new ZipEntry(getFileName(template, tableDO.getClassname(), tableDO.getClassName(), config.getString("package").substring(config.getString("package").lastIndexOf(".") + 1))));
                IOUtils.write(sw.toString(), zip, "UTF-8");
                IOUtils.closeQuietly(sw);
                zip.closeEntry();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取文件名
     */
    public static String getFileName(String template, String classname, String className, String packageName) {
        String packagePath = "main" + File.separator + "java" + File.separator;
        //String modulesname=config.getString("packageName");
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator;
        }

        if (template.contains("domain.java.vm")) {
            return packagePath + "domain" + File.separator + className + "DO.java";
        }

        if (template.contains("Dao.java.vm")) {
            return packagePath + "dao" + File.separator + className + "Dao.java";
        }

//		if(template.contains("Mapper.java.vm")){
//			return packagePath + "dao" + File.separator + className + "Mapper.java";
//		}

        if (template.contains("Service.java.vm")) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (template.contains("ServiceImpl.java.vm")) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains("Controller.java.vm")) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        if (template.contains("Mapper.xml.vm")) {
            return "main" + File.separator + "resources" + File.separator + "mapper" + File.separator + packageName + File.separator + className + "Mapper.xml";
        }

        if (template.contains("list.html.vm")) {
            return "main" + File.separator + "resources" + File.separator + "templates" + File.separator
                    + packageName + File.separator + classname + File.separator + classname + ".html";
            //				+ "modules" + File.separator + "generator" + File.separator + className.toLowerCase() + ".html";
        }
        if (template.contains("add.html.vm")) {
            return "main" + File.separator + "resources" + File.separator + "templates" + File.separator
                    + packageName + File.separator + classname + File.separator + "add.html";
        }
        if (template.contains("edit.html.vm")) {
            return "main" + File.separator + "resources" + File.separator + "templates" + File.separator
                    + packageName + File.separator + classname + File.separator + "edit.html";
        }

        if (template.contains("list.js.vm")) {
            return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator
                    + "appjs" + File.separator + packageName + File.separator + classname + File.separator + classname + ".js";
            //		+ "modules" + File.separator + "generator" + File.separator + className.toLowerCase() + ".js";
        }
        if (template.contains("add.js.vm")) {
            return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator
                    + "appjs" + File.separator + packageName + File.separator + classname + File.separator + "add.js";
        }
        if (template.contains("edit.js.vm")) {
            return "main" + File.separator + "resources" + File.separator + "static" + File.separator + "js" + File.separator
                    + "appjs" + File.separator + packageName + File.separator + classname + File.separator + "edit.js";
        }

//		if(template.contains("menu.sql.vm")){
//			return className.toLowerCase() + "_menu.sql";
//		}

        return null;
    }

12.启动类和欢迎页面

@ServletComponentScan
@MapperScan("com.jinyi.generatecode.dao")
@SpringBootApplication
public class GeneratecodeApplication {
    public static void main(String[] args) {
        SpringApplication.run(GeneratecodeApplication.class, args);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<a href="/batchCode">生成</a>
</body>
</html>

12.启动后,点击生成,会压缩一个文件包,里面就有我们需要的文件
在这里插入图片描述
在这里插入图片描述
生成的domain代码,以上代码仅供参考

package com.generator.system.domain;

import java.io.Serializable;
import java.util.Date;



/**
 * 秒杀库存表
 *
 * @author ZNJ
 * @email 1250622989@qq.com
 * @date 2019-06-14 18:21:15
 */
public class SeckillDO implements Serializable {
    private static final long serialVersionUID = 1L;

            //商品库存ID
        private Long seckillId;
            //商品名称
        private String name;
            //库存数量
        private Integer number;
            //秒杀开始时间
        private Date startTime;
            //秒杀结束时间
        private Date endTime;
            //创建时间
        private Date createTime;
    
            /**
         * 设置:商品库存ID
         */
        public void setSeckillId(Long seckillId) {
            this.seckillId = seckillId;
        }

        /**
         * 获取:商品库存ID
         */
        public Long getSeckillId() {
            return seckillId;
        }
            /**
         * 设置:商品名称
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * 获取:商品名称
         */
        public String getName() {
            return name;
        }
            /**
         * 设置:库存数量
         */
        public void setNumber(Integer number) {
            this.number = number;
        }

        /**
         * 获取:库存数量
         */
        public Integer getNumber() {
            return number;
        }
            /**
         * 设置:秒杀开始时间
         */
        public void setStartTime(Date startTime) {
            this.startTime = startTime;
        }

        /**
         * 获取:秒杀开始时间
         */
        public Date getStartTime() {
            return startTime;
        }
            /**
         * 设置:秒杀结束时间
         */
        public void setEndTime(Date endTime) {
            this.endTime = endTime;
        }

        /**
         * 获取:秒杀结束时间
         */
        public Date getEndTime() {
            return endTime;
        }
            /**
         * 设置:创建时间
         */
        public void setCreateTime(Date createTime) {
            this.createTime = createTime;
        }

        /**
         * 获取:创建时间
         */
        public Date getCreateTime() {
            return createTime;
        }
    }

由《Ext JS源码分析与开发实例宝典》作者:彭仁夔团队开发而成 90%以上代码可以生成验证、生成三层代码生成完善的权限系统、生成数据库等功能 功能之强大用了就知道! 两年前,我负责指导Java方向实训,在最后评审准备过程,.Net组负责人要求Java和.Net的归结一起统一评审,Java指导老师们的第一反应是Java和.Net开始怎么能相提并论? 为什么不能并论?是因为Java开发阳春白雪?在我们的潜意识中,Java开发就是让程序员(学生)一行一行地编写代码,编写HTML、CSS等,而.Net开发只要把控件拖到页面上即可,Java开发的效率一定比.Net低很多很多。 那么能不能提高Java的开发效率呢?作者经过分析并总结思考,发现业务系统有一定的共性所有操作,即增删改查,既然有共性,那么能不能抽象出来? 可以思考一下,通过继承、组件化等重用设计方式是可以抽象代码的共性,但是不同表(实体)增删改查的内容是不同的,通过泛型也很难抽象出来的。如对于A表的查询和对于B表的查询都需要使用到SQL语句,怎么去剥离出来? 此时,我们就需要采用另外一种方式,把它们抽象出来更高层级的模板,然后把不同数据传入该模板的占位符,这样就变成不同的代码,这种方式就是代码生成技术。 在软件的设计过程中,代码生成及重用设计仿若陌路人,互相排斥。重用设计中很多的共性是不能抽象出来的,而一味追求生成,造成大量的雷同代码,是重用设计人员或真正开发人员最不能容忍的事件。 如果把它们两个结合起来,那就是完全不同的效果,首先通过重用设计抽象其能抽象的代码,其不能抽象的共性通过生成技术生成,开发效率就会成倍增加。以前我们都是在X或y轴上分别进行,其最大的点也不过是线,如果两者结合起来,其变成一个平面,把我们的重用从点提高到面。如下图所示: 面。如下图所示, Y重用 现在的软件开发不再仅仅是编写代码,重用等,而是需求调研、架构设计、数据库、编码开发、测试部署等过程的有机组合,组合的好与坏,其结果相差万里,这就是著名的米格-25效应。 在开发过程怎么去把这些最大效应化呢?回想一下我们的开发流程: 需求开发,形成文档 采用powerDesigner等进行实体及实体关系的设计 通过Pdm创建数据库 在开发工具中创建搭建项目 通过相关的工具(hibernate tools,myeclipse)生成实体 设计及编写Dao 设计及编写Service 设计及编写Action及相关页面 这样的线性流程有着极大的问题,首先架构师或数据库专家不是圣人,设计数据库,Dao,Service接口之后,就不需要修改,在编码过程中,会进行大量的修改,特别是那种那只懂数据库的专家在设计之后,开发人员怨声载道。另外设计的dao,service接口及实现很多都用不着,用得着的根本就没有设计。因为需要的dao,service很多都是由页面根据取数操作的不同而进行不同的编写。 开发过程的修改,就会带来原前置流程步骤的修改,所以软件开发完成之后,不会有很多公司其PDM与数据库、实体是一一对应的。而有一些是必须要修改,如在实体中修改属性,一定要到数据库中修改,到dao,service,action,page一一进行修改,如果修改的是外键属性,修改的代码更为复杂。开发人员最痛苦的事情就无穷无尽的修改,领导简单的一句话:不就修改一个属性吗?你可能就要改一个下午。 能不能有这样的方法呢?修改一处其它处跟着改,这时就需要把原来的线性流程乾变换,改成为无前置的试,即卫星城的方式,中心城就是模型,可以称为Domain、域、实体,在实体的基础是加上一些设计,如下图所示: 现在只要中间模型发生变化,其变化会通过其背后的关系推向卫星城,让其发生相对应的变化,就像蝴蝶效应一样。 此时,我们可以站在一个更高的层面去思考开发,开发不仅仅是代码,开发不仅仅是处理代码之间关系,还需要处理与数据存储的关系,与测试部署关系等。 这些关系我们需不需要去体现出来,肯定需要。我们可以把这些关系看作是软件中区别于业务逻辑属性的特殊属性,这种属性主要包括: 与数据存储之间的关系 代码结构组织的方面(如采用三层) 与设计及设计文档的关系 与开发模型(如瀑布)的关系 与需求及需求变更的关系 与测试部署之间的关系 除了这些特殊关系,代码中还有实体之间的关系,业务关系等。 我们可以发现软件不单纯是一个对象,而且是一组关系。它们相互关联,相互牵制。 续而推之,我们编写的类也是关系,是处理一类事物的抽象关系。泛型是处理类的抽象关系。所有东西都是关系体。 那么能不能在现有的面向对象的理论上推进一步呢?世界不是一个个单独的对象,而是不同的对象相互发生关系的有机结构体(可以称为关系体),只有这样,世界才多姿多彩。就算是一个个对象,它也不是静止的,它的内部也是由不同分子原子发生作用的结果,可以讲对象也是一个关系体。 软件是用来对世界进行描述的一种方式,最初我们采用过程的编程,后来与世界的描述不相符,所以就出现了面向对象,而现在面向对象并不能很好地解析如抽象类,Service类等。要解释这些东西,最好的方式是通过关系来表达。也就是面向对象的编程方式可以更进一步地改进为面向关系编程方式。 面向关系编程需要首先需要找到软件模型与需求、架构,数据,测试,部署之间的变化关系。这些关系有业务层面的,也有开发层面的。这种关系可以抽离出来,形成模板。 接下来把业务模型+关系模板通过工具生产出来,就成为一个有血有肉的软件。Mgicode(码奇)生成器就从这里出发,帮我们抽象软件背后的关系,提高开发效率。 所以通过Mgicode生成器,你可以获得如下好处: 根据设计(pdm,数据库等)快速生成可运行的项目。 修改实体之后,会帮助你找到所有需要修改的东西,并且给出最佳的修改方案。 统一代码编程风格(如同一项目中选择几种不同日历控制) 把自己的项目模板化(关系化),方便之后使用 把模板化的项目放到Mgicode市场获得一些外快。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值