手把手教你从数据库生成实体类

Posted hebaibai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你从数据库生成实体类相关的知识,希望对你有一定的参考价值。

根据上面获取的数据开始创建java文件

终于开始要创建java文件了。

但是~在创建java文件的时候要先吧之前获取的数稍微处理一下,将sql中的格式转换为java中的格式。比如属性名称,数据类型,class名称之类的,现在开始~

将表名称转换为合适的class名称

就是首字母大写,驼峰式的命名规范。例如将user_log或者USER_LOG转换为UserLog。

我们可以这么写:

    /**
     * 类名称转换
     *
     * @param tableName
     * @return
     */
    public static String entityName(String tableName) {
        String lowerCaseName = tableName.toLowerCase();
        StringBuilder newName = new StringBuilder();
        char[] chars = lowerCaseName.toCharArray();
        boolean change = false;
        for (int i = 0; i < chars.length; i++) {
            char aChar = chars[i];
            if (aChar == ‘_‘ && !change) {
                change = true;
                continue;
            }
            //首字母大写
            if (i == 0) {
                aChar = Character.toUpperCase(aChar);
            }
            if (change) {
                aChar = Character.toUpperCase(aChar);
                change = false;
            }
            newName.append(aChar);
        }
        return newName.toString();
    }

这样就得到了我们需要的class的名称了。

将字段名称转换为java中的属性名称

这里就是将上一步操作的首字母大写去掉就好了,下面是代码:

    /**
     * 属性名称转换
     *
     * @param name
     * @return
     */
    public static String fieldName(String name) {
        name = name.toLowerCase();
        StringBuilder newName = new StringBuilder();
        char[] chars = name.toCharArray();
        boolean change = false;
        for (int i = 0; i < chars.length; i++) {
            char aChar = chars[i];
            if (aChar == ‘_‘ && !change) {
                change = true;
                continue;
            }
            if (change) {
                aChar = Character.toUpperCase(aChar);
                change = false;
            }
            newName.append(aChar);
        }
        return newName.toString();
    }

接下来是将sql中的数据类型转换为java中的数据类型。

sql数据类型转换

这里用map做了一个映射,有自己特定要求的可以自己修改。


public class ColumnFieldTypeMapping {

    private Map<String, Class> sqlFieldTypeMapping = new HashMap<>();

    {
        sqlFieldTypeMapping.put("VARCHAR", String.class);
        sqlFieldTypeMapping.put("CHAR", String.class);
        sqlFieldTypeMapping.put("TEXT", String.class);
        sqlFieldTypeMapping.put("MEDIUMTEXT", String.class);
        sqlFieldTypeMapping.put("LONGTEXT", String.class);
        sqlFieldTypeMapping.put("TINYTEXT", String.class);
        sqlFieldTypeMapping.put("BIT", Boolean.class);

        sqlFieldTypeMapping.put("INT", int.class);
        sqlFieldTypeMapping.put("BIGINT", long.class);
        sqlFieldTypeMapping.put("DOUBLE", double.class);
        sqlFieldTypeMapping.put("TINYINT", int.class);
        sqlFieldTypeMapping.put("FLOAT", float.class);
        sqlFieldTypeMapping.put("DECIMAL", BigDecimal.class);

        sqlFieldTypeMapping.put("INT UNSIGNED", int.class);
        sqlFieldTypeMapping.put("BIGINT UNSIGNED", int.class);
        sqlFieldTypeMapping.put("DECIMAL UNSIGNED", BigDecimal.class);

        sqlFieldTypeMapping.put("DATETIME", Date.class);
        sqlFieldTypeMapping.put("TIME", Date.class);
        sqlFieldTypeMapping.put("DATE", Date.class);
        sqlFieldTypeMapping.put("TIMESTAMP", Date.class);
    }

    /**
     * 根据sql数据类型获取Java数据类型
     *
     * @param columnType
     * @return
     */
    public Class getFieldType(String columnType) {
        Class aClass = sqlFieldTypeMapping.get(columnType);
        if (aClass == null) {
            return sqlFieldTypeMapping.get(columnType.toUpperCase());
        }
        return null;
    }
}

写到这里,所有参与生成java文件的信息就已经获取完成了。

这时候我们需要把他们组装起来,用来放进freemarker中来解析并生成java文件中的内容。

组装参数

这里可能我以后用这个代码干别的事情所以我建了两个类,一个是ClassModel.java,一个是EntityModel.java

EntityModel继承了ClassModel。我们主要用的是EntityModel.java。下面是代码:


import java.util.*;

/**
 * 用于生成java Entity文件的类
 */
public class ClassModel {

    /**
     * java 中不需要引包的类型
     */
    private static List<Class> baseClass = Arrays.asList(
            int.class,
            double.class,
            float.class,
            long.class,
            short.class,
            byte.class,
            char.class,
            boolean.class,
            String.class
    );

    /**
     * 类注释
     */
    private String classDoc;

    /**
     * 类名
     */
    private String className;

    /**
     * 类 包名
     */
    private String packageName;

    /**
     * K:属性名称
     * V:属性类型
     */
    private Map<String, Class> fields = new HashMap<>();

    /**
     * 属性的注释
     */
    private Map<String, String> fieldDoc = new HashMap<>();
    ;

    private List<Class> imports = new ArrayList<>();

    /**
     * 添加需要导入的包
     *
     * @param importClass
     */
    public void addImport(Class importClass) {
        if (baseClass.indexOf(importClass) != -1) {
            return;
        }
        if (imports.indexOf(importClass) == -1) {
            imports.add(importClass);
        }
    }

    /**
     * 添加属性
     *
     * @param fieldName  属性名称
     * @param fieldClass 属性类型
     */
    public void addfield(String fieldName, Class fieldClass) {
        if (!fields.containsKey(fieldName)) {
            fields.put(fieldName, fieldClass);
        }
    }

    /**
     * 添加属性注释
     *
     * @param fieldName 属性名称
     * @param fieldDoc  属性注释
     */
    public void addfieldDoc(String fieldName, String fieldDoc) {
        if (!this.fieldDoc.containsKey(fieldName)) {
            this.fieldDoc.put(fieldName, fieldDoc);
        }
    }

    public List<Class> getImports() {
        return imports;
    }

    public void setImports(List<Class> imports) {
        this.imports = imports;
    }

    public String getClassDoc() {
        return classDoc;
    }

    public void setClassDoc(String classDoc) {
        this.classDoc = classDoc;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    public Map<String, Class> getFields() {
        return fields;
    }

    public void setFields(Map<String, Class> fields) {
        this.fields = fields;
    }

    public Map<String, String> getFieldDoc() {
        return fieldDoc;
    }

    public void setFieldDoc(Map<String, String> fieldDoc) {
        this.fieldDoc = fieldDoc;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("            "classDoc"="").append(classDoc).append(‘"‘);
        sb.append(",             "className"="").append(className).append(‘"‘);
        sb.append(",             "packageName"="").append(packageName).append(‘"‘);
        sb.append(",             "fields"=").append(fields);
        sb.append(",             "fieldDoc"=").append(fieldDoc);
        sb.append(",             "imports"=").append(imports);
        sb.append(‘}‘);
        return sb.toString();
    }
}

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据库映射
 */
public class EntityModel extends ClassModel {

    /**
     * 数据库名称
     */
    private String tableName;

    /**
     * 数据库中的Id字段名称
     */
    private List<String> idColumnNames = new ArrayList<>();

    /**
     * 类属性名对应数据库字段映射
     * key: class 属性名称
     * value:数据库字段名
     */
    private Map<String, String> fieldSqlName = new HashMap<>();

    /**
     * 添加class 属性映射和 数据库 字段映射
     *
     * @param fieldName
     * @param sqlName
     */
    public void addfieldSqlName(String fieldName, String sqlName) {
        if (!fieldSqlName.containsKey(fieldName)) {
            fieldSqlName.put(fieldName, sqlName);
        }
    }

    /**
     * 添加id字段名
     *
     * @param idColumnName
     */
    public void addIdColumnName(String idColumnName) {
        idColumnNames.add(idColumnName);
    }

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public Map<String, String> getFieldSqlName() {
        return fieldSqlName;
    }

    public void setFieldSqlName(Map<String, String> fieldSqlName) {
        this.fieldSqlName = fieldSqlName;
    }

    public List<String> getIdColumnNames() {
        return idColumnNames;
    }

    public void setIdColumnNames(List<String> idColumnNames) {
        this.idColumnNames = idColumnNames;
    }
}

在这里将从数据库中得到的数据都组装好,就可以使用freemarker来生成Java文件的内容了。下面是代码:

/**
 * 根据建表语句组装EntityModel
 *
 * @param createTableSql
 * @return
 */
EntityModel makeModelBySql(String createTableSql) {
    Formatter formatter = new Formatter();
    EntityModel model = new EntityModel();
    String tableComment = SqlUtils.getTableComment(createTableSql);
    String tableName = SqlUtils.getTableName(createTableSql);
    String id = SqlUtils.getId(createTableSql);
    model.addIdColumnName(id);
    model.setClassName(NameConvert.entityName(tableName));
    model.setTableName(tableName);
    //注释是null的时候用数据库表名作为注释
    model.setClassDoc(tableComment == null ? tableName : tableComment);
    List<String> line = SqlUtils.getColumnSqls(createTableSql);
    for (String oneLine : line) {
        String columnName = SqlUtils.getByPattern(oneLine, "`(.*)`", 1);
        String comment = SqlUtils.getByPattern(oneLine, "COMMENT ‘(.*)‘", 1);
        String columnType = SqlUtils.getByPattern(oneLine, "`" + columnName + "` ([A-Za-z]*)", 1);
        String fieldName = NameConvert.fieldName(columnName);
        Class fieldClass = columnFieldTypeMapping.getFieldType(columnType);
        if (fieldClass == null) {
            formatter.format("table:%s columnName:%s sql类型:%s 没有映射类型", tableName, columnName, columnType);
            throw new UnsupportedOperationException(formatter.toString());
        }
        model.addfield(fieldName, fieldClass);
        //字段注释是null的时候用数据库字段名作为注释
        model.addfieldDoc(fieldName, comment == null ? columnName : comment);
        model.addfieldSqlName(fieldName, columnName);
        model.addImport(fieldClass);
    }
    return model;
}

这样一个我们需要的参数就组装好了。现在开始编写freemarker用的代码。

freemarker工具类

用来加载freemarker模板和处理模板中的参数。FreeMarkerUtils.java,代码如下:


import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;

import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.Scanner;

public class FreeMarkerUtils {

    /**
     * freemarker工具,
     *
     * @param subjectParams
     * @param templetPath
     * @return
     * @throws Exception
     */
    public static String getJavaClass(Object subjectParams, String templetPath) throws Exception {
        StringTemplateLoader loader = new StringTemplateLoader();
        Scanner scanner = new Scanner(Thread.currentThread().getContextClassLoader().getResourceAsStream(templetPath));
        StringBuilder builder = new StringBuilder();
        while (scanner.hasNext()) {
            builder.append(scanner.nextLine()).append("
");
        }
        String name = System.currentTimeMillis() + "";
        loader.putTemplate(name, builder.toString());
        //第一步:实例化Freemarker的配置类
        Configuration conf = new Configuration();
        conf.setObjectWrapper(new DefaultObjectWrapper());
        conf.setLocale(Locale.CHINA);
        conf.setDefaultEncoding("utf-8");
        conf.setTemplateLoader(loader);
        //处理空值为空字符串
        conf.setClassicCompatible(true);
        Template template = conf.getTemplate(name);
        Writer out = new StringWriter(2048);
        template.process(subjectParams, out);
        String javaClass = out.toString();
        return javaClass;
    }
}

现在有了工具类之后,还不能立即开始生成java文件,因为还要继续设置java的package和生成文件的路径,这时候我们可以修改之前写的config.xml

修改config.xml

<xml>
    <jdbc.url></jdbc.url>
    <jdbc.username></jdbc.username>
    <jdbc.password></jdbc.password>
    <basePath>/home/hjx/work/demo/src/main/java</basePath>
    <entityPackage>top.hejiaxuan.demo.entity</entityPackage>
</xml>

这里添加了两个参数:basePathentityPackage。一个是要生成java的文件的路径,一个是java文件的包名。

然后我们再写一个写出文件的工具类FileUtils.java

编写FileUtils.java


import java.io.*;

public class FileUtils {

    /**
     * 写入文件
     *
     * @param path    文件路径
     * @param content 文件内容
     */
    public static void write(String path, String content) {
        File file = new File(path);
        File parentFile = file.getParentFile();
        try {
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write(content);
            fileWriter.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这样就万事具备,就差生成文件啦。下面就开始啦~~~

开始生成java文件

在生成文件前,我们还需要把basePathentityPackage从配置文件里取出来,这一步我就不写了~~

static final String DOT = ".";

static final String FILE_TYPE = ".java";

static final String ENTITY_TEMPLET_PATH = "EntityTemp.ftl";

/**
 * 用于生成一个类文件
 *
 * @param entityModel
 * @return
 */
boolean makeOneClass(EntityModel entityModel) {
    entityModel.setPackageName(entityPackage);
    String filePath = basePath + "/" + entityPackage.replace(DOT, "/") + "/" + entityModel.getClassName() + FILE_TYPE;
    try {
        String javaClassString = FreeMarkerUtils.getJavaClass(entityModel, ENTITY_TEMPLET_PATH);
        FileUtils.write(filePath, javaClassString);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

好啦~~~大功告成。

额~~~

好像少点啥~~~

模板文件没有放出来~~~

编写EntityTemp.ftl

package ${packageName};

<#--导入的包-->
<#list imports as import>
import ${import.name};
</#list>

<#--类名-->
<#if classDoc?length gt 0>
/**
 * ${classDoc}
 * @author hejiaxuan
 */
</#if>
public class ${className} {

<#--属性名称-->
<#list fields?keys as key>
    <#assign  fieldDocStr = fieldDoc[key]>
    <#if fieldDocStr?length gt 0>
    /**${fieldDocStr}*/
    </#if>
    <#if idColumnNames?seq_contains(fieldSqlName[key])>
    </#if>
    private ${fields[key].simpleName} ${key};

</#list>
<#list fields?keys as key>
    <#assign  fieldClass = fields[key].simpleName>
<#--setter-->
    public void set${key?cap_first}(${fieldClass} ${key}) {
        this.${key} = ${key};
    }

<#--getter-->
    public ${fieldClass} <#if fieldClass="boolean">is<#else>get</#if>${key?cap_first}() {
        return this.${key};
    }

</#list>

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("[");
<#list fields?keys as key>
        sb.append("${key}:").append(${key}).append(";    ");
</#list>
        sb.append("]");
        return sb.toString();
    }
}

最后

? 这里面我只是贴出来了一些要用到的代码片段,没有将所有的代码全部写出来。其实写工具就是一个慢慢实现自己思路的过程,有思路的话一切都很简单。

? 如果有人需要项目全部代码的话请到https://github.com/hjx601496320/entityMaker自行查看。

以上是关于手把手教你从数据库生成实体类的主要内容,如果未能解决你的问题,请参考以下文章

手把手教你从零写一个简单的 VUE

手把手教你从零写一个简单的 VUE

手把手教你从零开始做一个好看的 APP

手把手教你从 Core Data 迁移到 Realm

手把手教你从零搭建深度学习项目

手把手教你实现自己的abp代码生成器