Fork me on GitHub
13 June 2019

前几年写了篇文章:Java Web开发时快速生成模版代码,是通过”git patch”文件方式来生成项目代码,虽然比复制粘贴的方式方便不少,但是还有比较繁琐,最近发现”IntelliJ IDEA (Ultimate Edition)”可根据数据库快速生成”POJO”代码,如下图所示:

alt

alt

既然”POJO”代码可以生成,那”Controller”, “Service”之类的代码也应该可以通过类似的方式生成。

我们可以修改Generate POJOs.groovy文件,改成我们需要的模版即可。

本例以我写的 chaos-server 框架为基础。

示例项目为 “Spring Boot” 项目, 数据库方面使用 “JPA”, 目录结构为

├── controller
├── repository
│   └── entity
└── service
    └── impl

示例代码:

import com.intellij.database.model.DasTable
import com.intellij.database.model.DasColumn
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil


/*
 * Available context bindings:
 *   SELECTION   Iterable<DasObject>
 *   PROJECT     project
 *   FILES       files helper
 */
basePackageName = "net.cofcool.test.server"
packageName = basePackageName + ".repository.entity"
repositoryPackageName = basePackageName + ".repository"
servicePackageName = basePackageName + ".service"
serviceImplPackageName = basePackageName + ".service.impl"
ControllerPackageName = basePackageName + ".controller"

allTables=new HashMap<>()

schemeName = "test"

typeMapping = [
        (~/(?i)int/)                      : "Integer",
        (~/(?i)long/)                     : "Long",
        (~/(?i)number/)                   : "String",
        (~/(?i)float|double|decimal|real/): "Double",
        (~/(?i)datetime|timestamp/)       : "java.sql.Timestamp",
        (~/(?i)date/)                     : "java.sql.Date",
        (~/(?i)time/)                     : "java.sql.Time",
        (~/(?i)/)                         : "String"
]

// 生成 entity
FILES.chooseDirectoryAndSave("Choose entity directory", "Choose where to store generated files") { dir ->
    SELECTION.filter { it instanceof DasTable && it.getKind() == ObjectKind.TABLE }.each {
        generate(it, dir)
    }
}

// 生成 repository
FILES.chooseDirectoryAndSave("Choose repository directory", "Choose where to store generated files") { dir ->
  allTables.each { className, idType ->
    new File(dir, className + "Repository.java").withPrintWriter { out ->
      generateRepository(out, className, idType)
    }
  }
}

// 生成 service
FILES.chooseDirectoryAndSave("Choose service directory", "Choose where to store generated files") { dir ->
  allTables.each { className, idType ->
    new File(dir, className + "Service.java").withPrintWriter { out ->
      generateService(out, className, idType)
    }
  }
}

// 生成 service 实现
FILES.chooseDirectoryAndSave("Choose service impl directory", "Choose where to store generated files") { dir ->
  allTables.each { className, idType ->
    new File(dir, className + "ServiceImpl.java").withPrintWriter { out ->
      generateServiceImpl(out, className, idType)
    }
  }
}

// 生成 controller
FILES.chooseDirectoryAndSave("Choose  controller directory", "Choose where to store generated files") { dir ->
    allTables.each { className, idType ->
        new File(dir, className + "Controller.java").withPrintWriter { out ->
            generateController(out, className, idType)
        }
    }
}


def generate(table, dir) {
    className = javaName(table.getName(), true)
    def fields = calcFields(table, className)
    allTables.put(className, table)
    new File(dir, className + ".java").withPrintWriter { out ->
        generate(out, className, fields,table)
    }
}

def generate(out, className, fields, table) {
    out.println "package $packageName ;"
    out.println ""
    out.println '''
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
'''
    out.println ""
    out.println ""
    out.println ""
    generateComment(out, table.getComment())
    out.println ""
    out.println "@Table(name =\"" + table.getName() + "\", schema = \""  + schemeName +  "\")"
    out.println "@Entity"
    out.println "@Getter"
    out.println "@Setter"
    out.println "@NoArgsConstructor"
    out.println "@ToString"
    out.println "public class $className  implements Serializable {"
    out.println ""
    out.println ""
    out.println genSerialID()
    out.println ""
    fields.each() {
        out.println "    /**"
        out.println "     * " + it.comment
        out.println "     */"

        if (it.annos.size() >0)
        {
            it.annos.each() {
                out.println "    ${it}"
            }
        }
        out.println "    private ${it.type} ${it.name};"
        out.println ""
    }
    out.println ""

    out.println "}"
}

def generateRepository(out, className, idType) {
    out.println "package $repositoryPackageName ;"
    out.println ""
    out.println "import net.cofcool.test.server.repository.entity.$className;"
    out.println "import org.springframework.data.jpa.repository.JpaRepository;"
    out.println "import org.springframework.data.jpa.repository.JpaSpecificationExecutor;"
    out.println ""
    out.println ""
    generateComment(out)
    out.println "public interface " + className + "Repository extends JpaRepository<$className, $idType>, JpaSpecificationExecutor<$className> {"
    out.println ""
    out.println "}"
}

private void generateComment(out, comment = null) {
    out.println "/**"
    out.println " * " + comment
    out.println " *"
    out.println " * <p>Date: " + new java.util.Date().toString() + "</p>"
    out.println " */"
}

// controller 使用的注释是为了生成 YApi 文档,并不是标准的 Java Doc 格式
def generateController(out, className, table) {
    def lit = toLowerCaseFirstOne(className)
    out.println "package $ControllerPackageName;"
    out.println ""
    out.println '''
import javax.annotation.Resource;
import net.cofcool.chaos.server.common.core.Message;
import net.cofcool.chaos.server.common.core.Page;
import net.cofcool.chaos.server.common.core.Result.ResultState;
import net.cofcool.chaos.server.common.util.ValidationGroups.Delete;
import net.cofcool.chaos.server.common.util.ValidationGroups.Insert;
import net.cofcool.chaos.server.common.util.ValidationGroups.Select;
import net.cofcool.chaos.server.common.util.ValidationGroups.Update;
import net.cofcool.chaos.server.core.annotation.Api;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
'''
    out.println "import net.cofcool.test.server.repository.entity.$className;"
    out.println "import $servicePackageName" + "." + className + "Service;"
    out.println ""
    out.println ""
    out.println ""
    out.println "/**"
    out.println " * @description: " + table.getComment()
    out.println " * @menu " + table.getComment()
    out.println " *"
    out.println " * <p>Date: " + new java.util.Date().toString() + "</p>"
    out.println " */"
    out.println "@Api"
    out.println "@RestController"
    out.println "@RequestMapping(value = \"/$lit\", method = RequestMethod.POST)"
    out.println "public class " + className + "Controller {"
    out.println ""
    out.println "    @Resource"
    out.println "    private " + className + "Service " + lit + "Service;"
    out.println ""
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "分页查询"
    out.println "     * @param: [Page<" + className + ">]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message"
    out.println "     * @data: "+ new java.util.Date().toString()
    out.println "     */"
    out.println "    @PostMapping(\"/query\")"
    out.println "    public Message query(@RequestBody Page<$className> page) {"
    out.println "        return " + lit + "Service.query(page, page.getCondition(" + className + ".class)).result();"
    out.println "    }"
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "添加"
    out.println "     * @param: [" + className + "]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message<$className>"
    out.println "     */"
    out.println "    @PostMapping(\"/add\")"
    out.println "    public Message<$className> add(@RequestBody @Validated(Insert.class) $className entity) {"
    out.println "        return " + lit + "Service.add(entity).result();"
    out.println "    }"
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "修改"
    out.println "     * @param: [" + className + "]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message<$className>"
    out.println "     * @data: "+ new java.util.Date().toString()
    out.println "     */"
    out.println "    @PostMapping(\"/update\")"
    out.println "    public Message update(@RequestBody @Validated(Update.class) $className entity) {"
    out.println "        return " + lit + "Service.update(entity).result();"
    out.println "    }"
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "详情"
    out.println "     * @param: [" + className + "]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message<$className>"
    out.println "     * @data: "+ new java.util.Date().toString()
    out.println "     */"
    out.println "    @PostMapping(\"/detail\")"
    out.println "    public Message detail(@RequestBody @Validated(Select.class) $className entity) {"
    out.println "        return " + lit + "Service.queryById(entity).result();"
    out.println "    }"
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "全部查询"
    out.println "     * @param: [" + className + "]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message<List<$className>>"
    out.println "     * @data: "+ new java.util.Date().toString()
    out.println "     */"
    out.println "    @PostMapping(\"/queryAll\")"
    out.println "    public Message queryAll(@RequestBody $className entity) {"
    out.println "        return " + lit + "Service.queryAll(entity).result();"
    out.println "    }"
    out.println ""
    out.println "    /**"
    out.println "     * @description: " + table.getComment() + "删除"
    out.println "     * @param: [" + className + "]"
    out.println "     * @menu " + table.getComment()
    out.println "     * @return: Message<$className>"
    out.println "     * @data: "+ new java.util.Date().toString()
    out.println "     */"
    out.println "    @PostMapping(\"/delete\")"
    out.println "    public ResultState delete(@RequestBody @Validated(Delete.class) $className entity) {"
    out.println "        return " + lit + "Service.delete(entity);"
    out.println "    }"
    out.println ""
    out.println ""
    out.println "}"
}

def toLowerCaseFirstOne(s){
    if(Character.isLowerCase(s.charAt(0)))
        return s;
    else
        return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
}

def generateService(out, className, idType) {
    out.println "package $servicePackageName;"
    out.println ""
    out.println "import net.cofcool.test.server.repository.entity.$className;"
    out.println "import net.cofcool.test.server.api.BaseService;"
    out.println ""
    out.println ""
    out.println ""
    generateComment(out)
    out.println "public interface " + className + "Service extends BaseService<$className> {"
    out.println ""
    out.println "}"
}

def generateServiceImpl(out, className, idType) {
    out.println "package $serviceImplPackageName;"
    out.println ""
    out.println '''
import net.cofcool.chaos.server.common.core.Page;
import net.cofcool.chaos.server.data.jpa.support.Paging;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import net.cofcool.test.server.api.BaseServiceImpl;
'''
    out.println "import $packageName" + "." + className + ";"
    out.println "import $repositoryPackageName" + "." + className + "Repository;"
    out.println "import $servicePackageName" + "." + className + "Service;"
    out.println ""
    out.println ""
    out.println ""
    generateComment(out)
    out.println "@Service"
    out.println "public class " + className + "ServiceImpl extends BaseServiceImpl<$className, $idType, $className" + "Repository>  implements " + className + "Service {"
    out.println "    @Override"
    out.println "    protected Object queryWithSp(Specification<$className> sp, Page<$className> condition, $className entity) {"
    out.println "        return getJpaRepository().findAll(sp, Paging.getPageable(condition));"
    out.println "    }"
    out.println ""
    out.println "    @Override"
    out.println "    protected $idType getEntityId($className entity) {"
    out.println "        return entity.getId();"
    out.println "    }"
    out.println ""
    out.println "}"
}

def calcFields(table, javaName) {
    DasUtil.getColumns(table).reduce([]) { fields, col ->
        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
        def comm = [
                name : columnName(col.getName()),
                type : typeStr,
                annos: ["@Column(name = \"" + col.getName() + "\" )"],
                comment: col.getComment()
        ]
        if (table.getColumnAttrs(col).contains(DasColumn.Attribute.PRIMARY_KEY)) {
            comm.annos += ["@Id"]
            comm.annos += ["@GeneratedValue(strategy = GenerationType.IDENTITY)"]
        }
        fields += [comm]
    }
}


def javaName(str, capitalize) {
    def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
            .collect { Case.LOWER.apply(it).capitalize() }
            .subList(1, 2) // 去除表前缀, 例如 user_
            .join("")
            .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
    capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

def columnName(str) {
    def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
            .collect { Case.LOWER.apply(it).capitalize() }
            .join("")
            .replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
    s.length() == 1? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

static String genSerialID()
{
    return "    private static final long serialVersionUID =  " + Math.abs(new Random().nextLong())+"L;"
}