基于SpringBoot、Mybatis-Generator实现数据库表自动生成全套后台代码

news2024/11/18 0:39:59

背景

我们在日常开发过程,大多数都是使用主流MVC架构,如下图所示。

从图中可以看出,我们主要的业务代码基本都是从Controller->Service->Dao/Mapper,由Dao/Mapper则通过Mybatis连接数据库连接池的方式与数据库进行指令数据交互。

因此,可以知道一张业务表一般可以对应上述一套后台代码,如果业务表众多就会对应多套后台代码,很明显这是重复且麻烦的代码生成工作。基于此,我们考虑能否实现一个与Ideaj代码生成工具有相似功能的工具类,根据我们定义的后台代码模板,来帮助我们个性化生成我们想要的后台代码。

技术路线

我们基于Springboot Gradle构建工程,将工程分为两部分模块。

(1)代码生成器模块:主要基于Freemarker和Velocity模板引擎,对需要的后台代码内容和风格自定义ftl模板。在Generator的主程序中连接数据库,加载模板文件,执行引擎方法,生成数据库表对应的后台代码到指定包路径中;

(2)服务发布模块:基于已生成的后台代码,使用SpringBoot进行发布,发布后通过浏览器访问,验证生成的后台代码是否满足指定业务表的增删改查(全量和分页查询)。

模块解析

(1)公共配置依赖:主要为build.gradle和application.properties配置

build.gradle:

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.1.3.RELEASE'
}

group = 'com.example.mybatis'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
tasks.withType(JavaCompile) {
    options.encoding = "utf-8"
}

repositories {
    mavenCentral()
}

configurations {
    compileOnly { //仅在编译时依赖,此处为lombok使用,相关注解在编译时生成getter、setter等
        extendsFrom annotationProcessor
    }
}

dependencies {
    compile 'com.alibaba:druid:1.2.8'
    compile 'mysql:mysql-connector-java:5.1.47'
    compile (group: 'org.mybatis.spring.boot', name: 'mybatis-spring-boot-starter', version: '1.1.1'){
        exclude group:'org.springframework.boot', module:'spring-boot-autoconfigure'
        exclude group:'org.springframework.boot', module:'spring-boot-starter-logging'
        exclude group:'org.springframework.boot', module: 'spring-boot-starter'
    }
    compile (group:'com.baomidou', name:'mybatis-plus-boot-starter', version:'3.5.1'){
        exclude group:'org.springframework.boot', module:'spring-boot-starter-jdbc'
        exclude group:'org.springframework.boot', module:'spring-boot-autoconfigure'
        exclude group:'org.springframework.boot', module: 'spring-boot-starter'
    }
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-parent', version: '2.0.7.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter', version: '2.0.7.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.0.7.RELEASE'
    compileOnly 'org.projectlombok:lombok:1.18.6'
    compile group: 'junit', name: 'junit', version: '4.12'
    compile (group:'com.github.pagehelper', name:'pagehelper-spring-boot-starter', version:'1.3.1'){
        exclude group:'org.mybatis.spring.boot', module: 'mybatis-spring-boot-starter'
        exclude group:'org.springframework.boot', module:'spring-boot-autoconfigure'
        exclude group:'org.springframework.boot', module: 'spring-boot-starter'
    }
    compile 'org.apache.commons:commons-io:1.3.2'
    compile group: 'io.springfox', name: 'springfox-swagger2', version:'2.9.2'
    compile group: 'io.springfox', name: 'springfox-swagger-ui', version:'2.9.2'
    compile('com.baomidou:mybatis-plus-generator:3.5.1')
    compile 'org.apache.velocity:velocity-engine-core:2.2'
    compile 'org.freemarker:freemarker:2.3.28'
}

application.properties:

spring.application.name=mybatis-generator-application
server.port=8080

# druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# druid参数调优(可选)
# 初始化大小,最小,最大
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
# 配置获取连接等待超时的时间
spring.datasource.maxWait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.timeBetweenEvictionRunsMillis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis=300000
# 测试连接
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
# 配置监控统计拦截的filters
spring.datasource.filters=stat
# asyncInit是1.1.4中新增加的配置,如果有initialSize数量较多时,打开会加快应用启动时间
spring.datasource.asyncInit=true

#数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/mysql?serverTimeZone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=

#分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect 属性来指定分页插件使用哪种方言。
pagehelper.helper-dialect=mysql
#分页合理化参数,默认值为 false 。当该参数设置为 true 时, pageNum<=0 时会查询第一页, pageNum>pages (超过总数时),会查询最后一页。
pagehelper.reasonable=true
#支持通过Mapper接口参数传递page参数,默认值为falset
pagehelper.support-methods-arguments=true
#默认值为 false ,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit =0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
pagehelper.pageSizeZero=true
#为了支持 startPage(Object params) 方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值
pagehelper.params=count=countSql

公共包

BaseController.java:

package com.common;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class BaseController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return null;
    }

    /**

     * 响应返回结果

     * 参数result 类型可自行更换

     * 如:

     * boolean result -> return result == true ? success() : error();

     * int result -> return result > 0 ? success() : error();

     */

    protected AjaxResult toAjax(boolean result) {

        return result == true ? success() : error();

    }

    protected AjaxResult toAjax(int result) {
        return result > 0 ? success() : error();
    }

    /**

     * 返回成功

     */

    public AjaxResult success() {

        return AjaxResult.success();

    }

    /**

     * 返回失败

     */

    public AjaxResult error() {

        return AjaxResult.error();

    }

    /**

     * 返回成功消息

     */

    public AjaxResult success(String message)

    {

        return AjaxResult.success(message);

    }

    /**

     * 返回失败消息

     */

    public AjaxResult error(String message)

    {

        return AjaxResult.error(message);

    }

    /**

     * 返回错误码消息

     */

    public AjaxResult error(int code, String message)

    {

        return AjaxResult.error(code, message);

    }

}

AjaxResult.java:

package com.common;




import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;

/**
 * 操作消息提醒
 *
 *
 */
public class AjaxResult extends HashMap<String, Object>
{
    private static final long serialVersionUID = 1L;

    /** 状态码 */
    public static final String CODE_TAG = "code";

    /** 返回内容 */
    public static final String MSG_TAG = "msg";

    /** 数据对象 */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult()
    {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     */
    public AjaxResult(int code, String msg)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg 返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data)
    {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (null != data)
        {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success()
    {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data)
    {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg)
    {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error()
    {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg)
    {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data)
    {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg)
    {
        return new AjaxResult(code, msg, null);
    }

    /**
     * 方便链式调用
     *
     * @param key 键
     * @param value 值
     * @return 数据对象
     */
    @Override
    public AjaxResult put(String key, Object value)
    {
        super.put(key, value);
        return this;
    }
}

BaseEntity.java:

package com.common;

import java.io.Serializable;

public class BaseEntity implements Serializable {

    private int page;

    private int limit;

    public int getPage() {
        return page;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    @Override
    public String toString() {
        return "BaseEntity{" +
                "page=" + page +
                ", limit=" + limit +
                '}';
    }
}

HttpStatus.java:

package com.common;

/**
 * 返回状态码
 *
 *
 */
public class HttpStatus
{
    /**
     * 操作成功
     */
    public static final int SUCCESS = 200;

    /**
     * 对象创建成功
     */
    public static final int CREATED = 201;

    /**
     * 请求已经被接受
     */
    public static final int ACCEPTED = 202;

    /**
     * 操作已经执行成功,但是没有返回数据
     */
    public static final int NO_CONTENT = 204;

    /**
     * 资源已被移除
     */
    public static final int MOVED_PERM = 301;

    /**
     * 重定向
     */
    public static final int SEE_OTHER = 303;

    /**
     * 资源没有被修改
     */
    public static final int NOT_MODIFIED = 304;

    /**
     * 参数列表错误(缺少,格式不匹配)
     */
    public static final int BAD_REQUEST = 400;

    /**
     * 未授权
     */
    public static final int UNAUTHORIZED = 401;

    /**
     * 访问受限,授权过期
     */
    public static final int FORBIDDEN = 403;

    /**
     * 资源,服务未找到
     */
    public static final int NOT_FOUND = 404;

    /**
     * 不允许的http方法
     */
    public static final int BAD_METHOD = 405;

    /**
     * 资源冲突,或者资源被锁
     */
    public static final int CONFLICT = 409;

    /**
     * 不支持的数据,媒体类型
     */
    public static final int UNSUPPORTED_TYPE = 415;

    /**
     * 系统内部错误
     */
    public static final int ERROR = 500;

    /**
     * 接口未实现
     */
    public static final int NOT_IMPLEMENTED = 501;
}

(2)代码生成器模块:该模块核心是定义ftl文件,然后主程序加载并执行模板引擎。

Generator.java

package com.generator;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.baomidou.mybatisplus.generator.fill.Property;

import java.io.File;
import java.util.*;
/**
 * mybatis-plus
 */
public class Generator {
    //输出文件的作者注释
    public static final String auth = "test";
    //项目路径
    public static final String finalProjectPath = "F:\\workspaces4ideaj";
    //输出.java文件的根目录
    public static final String javaRootPath = "/src/main/java";
    //输出java文件的包目录
    public static final String parent = "com.test";
    //输出mapper.xml文件的包目录
    public static final String mapperPath = "/src/main/resources/mapper";
    //数据库信息
    public static final String url = "jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8&useSSL=false";
    public static final String username = "root";
    public static final String password = "";

    public static void main(String[] args) {
        //创建生成器,连接数据库
        FastAutoGenerator.create(url, username, password)
                //全局配置
                .globalConfig(builder -> {
                    builder.author(auth)
                            //swagger模式
                            .enableSwagger()
                            //关闭覆盖已生成文件功能,解决自定义模板被覆盖问题
                            //.fileOverride()
                            .outputDir(finalProjectPath + javaRootPath) // 指定输出目录
                            .disableOpenDir();
                })
                .packageConfig((scanner, builder) ->
                        builder
                        .other("vo")
                        .pathInfo(Collections.singletonMap(OutputFile.mapperXml, finalProjectPath + mapperPath))
                        .parent(parent + "." + scanner.apply("Please enter a package name:")
                        ))
                //策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(
                   getTables(scanner.apply("Please enter a table name separated by dot, all input all: "))
                 )
                // 设置过滤表前缀
                .addTablePrefix("sys_")
                .controllerBuilder()
                .serviceBuilder()
                .mapperBuilder()
                .entityBuilder()
                .disableSerialVersionUID()
                .enableLombok() // 开启Lombok
                .enableTableFieldAnnotation()
                .addTableFills(new Column("create_time", FieldFill.INSERT))
                .addTableFills(new Property("update_time", FieldFill.INSERT_UPDATE))
                .idType(IdType.AUTO)
                .build()
                )
        // 自定义注入模
        // .vm为velocity引擎的,.ftl为freemarker引擎的
            .injectionConfig(consumer -> {
            Map<String, String> customFile = new HashMap<>();
            customFile.put("Controller.java", "/templates/ftl/controller.java.ftl");
            customFile.put("Service.java", "/templates/ftl/service.java.ftl");
            customFile.put("ServiceImpl.java", "/templates/ftl/serviceImpl.java.ftl");
            customFile.put("Mapper.java", "/templates/ftl/mapper.java.ftl");
            customFile.put(".java", "/templates/ftl/entity.java.ftl");
            customFile.put("Mapper.xml", "/templates/ftl/mapper.xml.ftl");
            customFile.put("Vo.java", "/templates/ftl/vo.java.ftl");
            consumer.customFile(customFile);
        })
            /*//自定义模板模式
                .templateConfig(builder -> {
                    //禁止所有模板
                    builder.disable(TemplateType.ENTITY);
                    builder.disable(TemplateType.XML);
                    builder.disable(TemplateType.MAPPER);
                    builder.disable(TemplateType.SERVICE);
                    builder.disable(TemplateType.SERVICEIMPL);
                    builder.disable(TemplateType.CONTROLLER);
                })
             */
        //使用Framemarker引擎模板,默认Velocity引擎模板
        .templateEngine(new EnhanceFreemarkerTemplateEngine()).execute();
    }
    //处理输入为all所有表
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
    /**
     * 代码生成器支持自定义[DTO\VO等]模版
     */
    public final static class EnhanceFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
        @Override
        protected void outputCustomFile(Map<String, String> customFile, TableInfo tableInfo, Map<String, Object> objectMap) {
            String entityName = tableInfo.getEntityName();
            String otherPath = this.getPathInfo(OutputFile.other);
            String controller = this.getPathInfo(OutputFile.controller);
            String service = this.getPathInfo(OutputFile.service);
            String serviceImpl = this.getPathInfo(OutputFile.serviceImpl);
            String mapper = this.getPathInfo(OutputFile.mapper);
            String entity = this.getPathInfo(OutputFile.entity);
            String mapperXml = this.getPathInfo(OutputFile.mapperXml);
            customFile.forEach((key, value) -> {
                //指定输出文件路径
                if ("Controller.java".equals(key)) {
                    String fileName = String.format(controller + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if ("Service.java".equals(key)) {
                    String fileName = String.format(service + File.separator + "I" + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if ("ServiceImpl.java".equals(key)) {
                    String fileName = String.format(serviceImpl + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if ("Mapper.java".equals(key)) {
                    String fileName = String.format(mapper + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if (".java".equals(key)) {
                    String fileName = String.format(entity + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if ("Mapper.xml".equals(key)) {
                    String fileName = String.format(mapperXml + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                // 过滤掉非vo或dto对象自定义文件
                if ("Vo.java".equals(key)) {
                    String fileName = String.format(otherPath + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
                if ("Dto.java".equals(key)) {
                    String fileName = String.format(otherPath + File.separator + entityName + "%s", key);
                    outputFile(new File(fileName), objectMap, value);
                }
            });
        }
    }
}

src/main/resources/mybatis-generator.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>

    <!-- 本地数据库驱动程序jar包的全路径 -->
    <!--<classPathEntry location="F:\repository\mysql\mysql-connector-java\5.1.44\mysql-connector-java-5.1.44.jar"/>-->

    <context id="context" targetRuntime="MyBatis3Simple">
        <!--java代码编码格式-->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!--格式化java代码-->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!--格式化xml代码-->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

        <commentGenerator>
            <!-- 是否取消注释 -->
            <property name="suppressAllComments" value="true"/>
            <!-- 是否生成注释代时间戳-->
            <property name="suppressDate" value="true"/>
        </commentGenerator>

        <!-- 数据库的相关配置 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true"
                        userId="root"
                        password=""/>
        <javaTypeResolver>
            <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- 实体类生成的位置 -->
        <javaModelGenerator targetPackage="com.core" targetProject="src/main/java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- *Mapper.xml 文件的位置 -->
        <sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!-- Mapper 接口文件的位置,注意type的值这里是熟悉的xml格式 -->
        <!--
         type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
                1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
                2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
                3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
            注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
        -->
        <javaClientGenerator targetPackage="com.core" targetProject="src/main/java" type="XMLMAPPER">
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>

        <!-- 相关表的配置 -->
        <table tableName="t_order" enableCountByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false"
               enableUpdateByExample="false" selectByExampleQueryId="true"/>
    </context>
</generatorConfiguration>

src/main/resources/templates/ftl/文件

controller.java.ftl:

package ${package.Controller};
import ${package.Entity}.${entity};
import ${package.Other}.${entity}Vo;
import ${package.Service}.${table.serviceName};
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.*;
import io.swagger.annotations.*;
import com.common.AjaxResult;
import com.common.BaseController;
import java.util.List;
import java.util.Map;
<#if superControllerClassPackage??>
    import ${superControllerClassPackage};
</#if>
/**
* ${table.comment!} 前端控制器
* @author ${author}
* @since ${date}
*/
@Api(tags = "${table.comment!}文档")
@RestController
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
public class ${table.controllerName} extends BaseController{

   @Resource(name="${entity?lower_case}Service")
   private ${table.serviceName} ${entity?lower_case}Service;

/**
*
* 分页查詢列表
* @return page
*/
@ApiOperation(value = "分页查询列表", httpMethod = "GET", notes = "分页查询列表")
@ApiImplicitParam(paramType = "body", name = "${entity?lower_case}Vo", value = "对象", dataType = "${entity}Vo")
@ApiResponses({
@ApiResponse(code = 200, message = "响应对象", response = ${entity}.class)
})
@GetMapping("/queryListByPagging")
public AjaxResult queryListByPagging(${entity}Vo ${entity?lower_case}Vo) {
  Map<String,Object> map = ${entity?lower_case}Service.select${entity}ListByPagging(${entity?lower_case}Vo);
  return AjaxResult.success(map);
}
/**
*
* 查詢列表
* @return page
*/
@ApiOperation(value = "查询列表", httpMethod = "GET", notes = "查询列表")
@ApiImplicitParam(paramType = "body", name = "${entity?lower_case}Vo", value = "对象", dataType = "${entity}Vo")
@ApiResponses({
@ApiResponse(code = 200, message = "响应对象", response = ${entity}.class)
})
@GetMapping("/queryList")
public AjaxResult queryList(${entity}Vo ${entity?lower_case}Vo) {
  List<${entity}> list = ${entity?lower_case}Service.select${entity}List(${entity?lower_case}Vo);
  return AjaxResult.success(list);
}
/**
* 新增
*/
@ApiOperation(value = "新增", httpMethod = "POST")
@ApiImplicitParam(paramType = "body", name = "${entity?lower_case}", value = "对象", dataType = "${entity}")
@PostMapping("/insert${entity}")
public AjaxResult insert${entity}(@RequestBody ${entity} ${entity?lower_case}){
  return toAjax(${entity?lower_case}Service.insert${entity}(${entity?lower_case}));
}
/**
* 修改
*/
@ApiOperation(value = "修改", httpMethod = "POST")
@ApiImplicitParam(paramType = "body", name = "${entity?lower_case}", value = "对象", dataType = "${entity}")
@PostMapping("/update${entity}")
public AjaxResult update${entity}(@RequestBody ${entity} ${entity?lower_case}){
return toAjax(${entity?lower_case}Service.update${entity}(${entity?lower_case}));
}
/**
* 删除
*/
@ApiOperation(value = "删除", httpMethod = "POST")
@ApiImplicitParam(paramType = "query", name = "id", value = "主键id")
@PostMapping("/delete${entity}")
public AjaxResult delete${entity}(@RequestParam("id") Integer id){
return toAjax(${entity?lower_case}Service.delete${entity}(id));
}
}

service.java.ftl:

package ${package.Service};
import ${package.Entity}.${entity};
import ${package.Other}.${entity}Vo;
import ${superServiceClassPackage};
import java.util.List;
import java.util.Map;
/**
* ${table.comment!} 服务类
* @author ${author}
* @since ${date}
*/
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
/**
* 查询${table.comment!}数量
*/
public Integer select${entity}Count(${entity}Vo ${entity?lower_case}Vo);
/**
* 查詢${table.comment!}列表
* @return page
*/
List<${entity}> select${entity}List(${entity}Vo ${entity?lower_case}Vo);
/**
* 分页查詢${table.comment!}列表
* @return page
*/
Map<String,Object> select${entity}ListByPagging(${entity}Vo ${entity?lower_case}Vo);
/**
* 新增${table.comment!}
*/
int insert${entity}(${entity} ${entity?lower_case});
/**
* 修改${table.comment!}
*/
int update${entity}(${entity} ${entity?lower_case});
/**
* 删除${table.comment!}
*/
int delete${entity}(Integer id);
}

serviceImpl.java.ftl:

package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import ${package.Other}.${entity}Vo;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
/**
* ${table.comment!}服务实现类
* @author ${author}
* @since ${date}
*/
@Slf4j
@Service("${entity?lower_case}Service")
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {
@Autowired
private ${table.mapperName} ${entity?lower_case}Mapper;
/**
 * 查询${table.comment!}数量
 */
@Override
public Integer select${entity}Count(${entity}Vo ${entity?lower_case}Vo) {
return ${entity?lower_case}Mapper.queryCount(${entity?lower_case}Vo);
}
/**
*
* 查詢${table.comment!}列表
*
* @return page
*/
@Override
public List<${entity}> select${entity}List(${entity}Vo ${entity?lower_case}Vo) {
return ${entity?lower_case}Mapper.queryList(${entity?lower_case}Vo);
}
/**
* 分页查詢${table.comment!}列表
* @return Map<String,Object>
*/
public Map<String,Object> select${entity}ListByPagging(${entity}Vo ${entity?lower_case}Vo){
    Integer count = select${entity}Count(${entity?lower_case}Vo);
    Map<String,Object> map = new HashMap<String,Object>();
    List<${entity}> list = null;
    if(null != count && count > 0){
        PageInfo pageInfo = null;
        try {
            PageHelper.startPage(${entity?lower_case}Vo.getPage(), ${entity?lower_case}Vo.getLimit());
            list = select${entity}List(${entity?lower_case}Vo);
        }finally {
            PageHelper.clearPage();
        }
    }
    map.put("count", count);
    map.put("list", list);
    return map;
}
/**
* 新增${table.comment!}
*
* @return 结果
*/
@Override
public int insert${entity}(${entity} ${entity?lower_case}) {
return ${entity?lower_case}Mapper.insert(${entity?lower_case});
}
/**
* 修改${table.comment!}
*
* @return 结果
*/
@Override
public int update${entity}(${entity} ${entity?lower_case}) {
return ${entity?lower_case}Mapper.updateById(${entity?lower_case});
}
/**
* 删除${table.comment!}
*
* @return 结果
*/
@Override
public int delete${entity}(Integer id) {
return ${entity?lower_case}Mapper.deleteById(id);
}
}

entity.java.ftl:

package ${package.Entity};
<#list table.importPackages as pkg>
    import ${pkg};
</#list>
<#if swagger>
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
</#if>
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* ${table.comment!}
* @author ${author}
* @since ${date}
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
<#if chainModel>
    @Accessors(chain = true)
</#if>
<#if table.convert>
    @TableName("${schemaName}${table.name}")
</#if>
<#if swagger>
    @ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
public class ${entity} implements Serializable {
private static final long serialVersionUID = 1L;
<#-- ----------  BEGIN 字段循环遍历  ---------->
<#list table.fields as field>
    <#if field.keyFlag>
        <#assign keyPropertyName="${field.propertyName}"/>
    </#if>
    <#if field.comment!?length gt 0>
        <#if swagger>
            @ApiModelProperty("${field.comment}")
        <#else>
            /**
            * ${field.comment}
            */
        </#if>
    </#if>
    <#if field.keyFlag>
    <#-- 主键 -->
        <#if field.keyIdentityFlag>
            @TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
        <#elseif idType??>
            @TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
        <#elseif field.convert>
            @TableId("${field.annotationColumnName}")
        </#if>
    <#-- 普通字段 -->
    <#elseif field.fill??>
    <#-- -----   存在字段填充设置   ----->
        <#if field.convert>
            @TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
        <#else>
            @TableField(fill = FieldFill.${field.fill})
        </#if>
    <#elseif field.convert>
        @TableField("${field.annotationColumnName}")
    </#if>
<#-- 乐观锁注解 -->
    <#if field.versionField>
        @Version
    </#if>
<#-- 逻辑删除注解 -->
    <#if field.logicDeleteField>
        @TableLogic
    </#if>
    private ${field.propertyType} ${field.propertyName};
</#list>
<#------------  END 字段循环遍历  ---------->
<#if !entityLombokModel>
    <#list table.fields as field>
        <#if field.propertyType == "boolean">
            <#assign getprefix="is"/>
        <#else>
            <#assign getprefix="get"/>
        </#if>
        public ${field.propertyType} ${getprefix}${field.capitalName}() {
        return ${field.propertyName};
        }
        <#if chainModel>
            public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
        <#else>
            public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
        </#if>
        this.${field.propertyName} = ${field.propertyName};
        <#if chainModel>
            return this;
        </#if>
        }
    </#list>
</#if>
<#if entityColumnConstant>
    <#list table.fields as field>
        public static final String ${field.name?upper_case} = "${field.name}";
    </#list>
</#if>
<#if activeRecord>
    @Override
    public Serializable pkVal() {
    <#if keyPropertyName??>
        return this.${keyPropertyName};
    <#else>
        return null;
    </#if>
    }
</#if>
<#if !entityLombokModel>
    @Override
    public String toString() {
    return "${entity}{" +
    <#list table.fields as field>
        <#if field_index==0>
            "${field.propertyName}=" + ${field.propertyName} +
        <#else>
            ", ${field.propertyName}=" + ${field.propertyName} +
        </#if>
    </#list>
    "}";
    }
</#if>
}

vo.java.ftl:

package ${package.Other};
<#list table.importPackages as pkg>
    import ${pkg};
</#list>
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import io.swagger.annotations.ApiModel;
import com.common.BaseEntity;
/**
* <p>
    *   ${table.comment!}
    * </p>
*
* @author ${author}
* @since ${date}
*/
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
public class ${entity}Vo extends BaseEntity {
private static final long serialVersionUID = 1L;
<#list table.fields as field>
private ${field.propertyType} ${field.propertyName};
</#list>

@Override
public String toString() {
return "TOrderVo{" +
<#list table.fields as field>
    "${field.propertyName}=" + ${field.propertyName} + "," +
</#list>
"}";
}

}

mapper.java.ftl:

package ${package.Mapper};
import ${package.Entity}.${entity};
import ${package.Other}.${entity}Vo;
import ${superMapperClassPackage};
import java.util.List;
<#if mapperAnnotation>
    import org.apache.ibatis.annotations.Mapper;
</#if>
/**
* ${table.comment!} Mapper 接口
* @author ${author}
* @since ${date}
*/
<#if mapperAnnotation>
    @Mapper
</#if>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {

/**
* 查詢${table.comment!}数量
* @return Integer
*/
Integer queryCount(${entity}Vo ${entity?lower_case}Vo);

/**
* 查詢${table.comment!}列表
* @return List<${entity}>
*/
List<${entity}> queryList(${entity}Vo ${entity?lower_case}Vo);
}

mapper.xml.ftl:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
    <#if enableCache>
        <!-- 开启二级缓存 -->
        <cache type="${cacheClassName}"/>
    </#if>

    <!-- 通用查询映射结果 -->
    <resultMap id="ResultMap" type="${package.Entity}.${entity}">
        <#list table.fields as field>
            <#if field.keyFlag><#--生成主键排在第一位-->
                <id column="${field.name}" property="${field.propertyName}" />
            </#if>
        </#list>
        <#list table.commonFields as field><#--生成公共字段 -->
            <result column="${field.name}" property="${field.propertyName}" />
        </#list>
        <#list table.fields as field>
            <#if !field.keyFlag><#--生成普通字段 -->
                <result column="${field.name}" property="${field.propertyName}" />
            </#if>
        </#list>
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="BaseColumnList">
        <#list table.commonFields as field>
            ${field.columnName},
        </#list>
        ${table.fieldNames}
    </sql>
    <!-- 通用查询过滤列 -->
    <sql id="Base_Column_Query_List">
        <#list table.fields as field>
            <if test="${field.propertyName} != null and ${field.propertyName} != ''">
                AND ${field.name} = #${r"{"}${field.propertyName}${r"}"}
            </if>
        </#list>
        <#list table.commonFields as field><#--生成公共字段 -->
            <if test="${field.propertyName} != null and ${field.propertyName} != ''">
                AND ${field.name} = #${r"{"}${field.propertyName}${r"}"}
            </if>
        </#list>
    </sql>

    <select id="queryCount" resultType="java.lang.Integer">
        select COUNT(1)
        from ${table.name}
        <where>
            <include refid="Base_Column_Query_List"/>
        </where>
    </select>
    <select id="queryList" resultMap="ResultMap">
        select
        <include refid="BaseColumnList"/>
        from ${table.name}
        <where>
            <include refid="Base_Column_Query_List"/>
        </where>
    </select>
</mapper>

然后启动mysql服务,创建一张t_bill表,并新增4条记录。

CREATE TABLE t_bill(

bill_id INT NOT NULL AUTO_INCREMENT,

bill_title VARCHAR(100) NOT NULL,

bill_author VARCHAR(40) NOT NULL,

bill_date DATE,

PRIMARY KEY ( bill_id )

);

mysql> select * from t_bill;

+---------+------------+-------------+-----------+

| bill_id | bill_title | bill_author | bill_date |

+---------+------------+-------------+-----------+

| 1 | haha | test | NULL |

| 2 | haha | test | NULL |

| 3 | haha | test | NULL |

| 4 | haha55 | test | NULL |

+---------+------------+-------------+-----------+

4 rows in set (0.00 sec)

执行Generator工具类,输入需要创建的子包名、表名,执行结束即可看到工程中自动生成该表的后台代码:

(3)服务发布模块:该模块启动Springboot工程,将生成的后台代码发布到tomcat后通过浏览器访问。

BootApplicationl.java:

package com;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@SpringBootApplication
@EnableSwagger2
@MapperScan(value="com.test")
public class BootApplication {
    public static void main(String[] args) {
        SpringApplication.run(BootApplication.class, args);
    }
}

然后可通过浏览器访问查询/分页查询方法服务

http://localhost:8080/t-bill/queryList

返回

{"msg":"操作成功","code":200,"data":[{"billId":1,"billTitle":"haha","billAuthor":"test","billDate":null},{"billId":2,"billTitle":"haha","billAuthor":"test","billDate":null},{"billId":3,"billTitle":"haha","billAuthor":"test","billDate":null},{"billId":4,"billTitle":"haha55","billAuthor":"test","billDate":null}]}

http://localhost:8080/t-bill/queryListByPagging?page=1&limit=2

返回

{"msg":"操作成功","code":200,"data":{"count":4,"list":[{"billId":1,"billTitle":"haha","billAuthor":"test","billDate":null},{"billId":2,"billTitle":"haha","billAuthor":"test","billDate":null}]}}

http://localhost:8080/t-bill/queryListByPagging?page=2&limit=2

返回

{"msg":"操作成功","code":200,"data":{"count":4,"list":[{"billId":3,"billTitle":"haha","billAuthor":"test","billDate":null},{"billId":4,"billTitle":"haha55","billAuthor":"test","billDate":null}]}}

也可使用postman等工具测试insert,update,delete服务

http://localhost:8080/t-bill/insertTBill

{

"billId":888,

"billTitle":"888",

"billAuthor":"888",

"LocalDate":null

}

返回

{

"msg": "操作成功",

"code": 200

}

mysql> select * from t_bill;

+---------+------------+-------------+-----------+

| bill_id | bill_title | bill_author | bill_date |

+---------+------------+-------------+-----------+

| 1 | haha | test | NULL |

| 2 | haha | test | NULL |

| 3 | haha | test | NULL |

| 4 | haha55 | test | NULL |

| 888 | 888 | 888 | NULL |

+---------+------------+-------------+-----------+

5 rows in set (0.03 sec)

http://localhost:8080/t-bill/updateTBill

{

"billId":888,

"billTitle":"update888",

"billAuthor":"update888",

"LocalDate":null

}

返回

{

"msg": "操作成功",

"code": 200

}

mysql> select * from t_bill;

+---------+------------+-------------+-----------+

| bill_id | bill_title | bill_author | bill_date |

+---------+------------+-------------+-----------+

| 1 | haha | test | NULL |

| 2 | haha | test | NULL |

| 3 | haha | test | NULL |

| 4 | haha55 | test | NULL |

| 888 | update888 | update888 | NULL |

+---------+------------+-------------+-----------+

5 rows in set (0.00 sec)

http://localhost:8080/t-bill/deleteTBill?id=1

返回

{

"msg": "操作成功",

"code": 200

}

mysql> select * from t_bill;

+---------+------------+-------------+-----------+

| bill_id | bill_title | bill_author | bill_date |

+---------+------------+-------------+-----------+

| 2 | haha | test | NULL |

| 3 | haha | test | NULL |

| 4 | haha55 | test | NULL |

| 888 | update888 | update888 | NULL |

+---------+------------+-------------+-----------+

4 rows in set (0.00 sec)

总结

在构建工程过程中,踩了不少坑下面描述一下避免后面入坑。

1.没有安装mysql,工程中没有配置mysql相关配置,没有引入mysql相关驱动包等导致启动失败。

2.工程使用springboot2.0.7启动,引入mybatis-plus,pageHelper会修改springboot版本,应剔除

3.mybatis-plus,pageHelper的版本没有互相适配导致报jsqlparser错误

4.Application启动类中添加MapperScan扫描,且扫描路径应详细到具体包根路径,扫描引入的jar会出现bean冲突

5.mapper.xml中如果需要动态sql带有特殊字符如#的,在ftl模板中需要对#使用r"{"和r"}"进行转义,

<if test="${field.propertyName} != null and ${field.propertyName} != ''">

AND ${field.name} = #${r"{"}${field.propertyName}${r"}"}

</if>

最后生成的mapper.xml才会是我们想要的如下格式:

<if test="billId != null and billId != ''">

AND bill_id = #{billId}

</if>

优化思考

该例子是通过提前设定后台文件模板,填充数据进行生成,具有很大的局限性。

实际企业的代码开发平台大多会基于平台的元数据(包括方法名,入参,出参,sql,空方法,属性等等)。使用者在浏览器通过拖拽输入等方式自定义一系列服务方法,会以元数据的形式存储(如json格式),通过解析元数据动态生成对应的后台文件。这种方式可以动态适应开发者的开发自行添加需要的文件内容。

分享至此,欢迎讨论分享优化案例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186014.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

实现简单的栈与队列

前言&#xff1a;前面已经详细地介绍了基本的顺序表和链表&#xff0c;这次要介绍的是数据结构中的栈与队列。从本质上来说&#xff0c;二者是特殊的线性表&#xff0c;是依赖于顺序表或链表来实现的&#xff0c;所以只要能够很好地掌握顺序表和链表&#xff0c;再了解清楚栈与…

STM32F103学习笔记(11)——压力传感器GZP6859D使用

一、简介 数据手册&#xff1a;https://item.szlcsc.com/3590436.html GZP6859D 型压力传感器采用 SOP6 封装形式&#xff0c;内部集成了高精度 ADC 芯片&#xff0c;对传感器芯片输出的偏移、灵敏度、温漂和非线性进行数字补偿&#xff0c;以供电电压为参考&#xff0c;产生一…

基于Java实现对Excel表格数据的读写(附B站详细讲解视频)

文章目录 Maven依赖设置导入相应jar包 读取.xlsx表格文件数据 写入数据到.xlsx表格文件 读写后缀名为.xls类型的表格文件&#xff08;旧版表格文件&#xff09; 详细视频教程 Maven依赖设置导入相应jar包 <project xmlns"http://maven.apache.org/POM/4.0.0" …

论文理解【Offline RL】——【One-step】Offline RL Without Off-Policy Evaluation

标题&#xff1a;Offline RL Without Off-Policy Evaluation文章链接&#xff1a;Offline RL Without Off-Policy Evaluation代码&#xff1a;davidbrandfonbrener/onestep-rl发表&#xff1a;NIPS 2021领域&#xff1a;离线强化学习&#xff08;offline/batch RL&#xff09;—…

【深度学习】知识蒸馏原理以及实践从0到1

文章目录前言1、知识蒸馏1.1 是什么&#xff1f;1.2 训练流程1.3 问题与挑战2、落地使用2.1 后续问题&#xff1a;总结前言 有没有什么方法可以在不扩展硬件的情况下利用这些强大但庞大的模型来训练最先进的模型&#xff1f;目前&#xff0c;有三种方法可以压缩神经网络&#…

一文搞懂JDK8 HashMap源码

目录前言常量和变量构造器put方法resize扩容get方法前言 HashMap的源码非常经典&#xff0c;里面用到了哈希表、链表、红黑树等数据结构&#xff0c;而且又是用纯Java实现的&#xff0c;所以成为了Java程序员必读的源码之一。 事先了解下哈希表&#xff08;散列表&#xff09…

portraiture2023手动磨皮的p图插件

可以手动磨皮的p图软件&#xff0c;大部分美颜软件只能一键磨皮或简单调整磨皮强度&#xff0c;本文会介绍一款可自动、可手动磨皮的p图软件。人像p图软件哪个好用&#xff1f;本文还会盘点一下好用的人像p图软件。 portraiture2023功能特点 2x性能和精细的输出质量将您的皮肤…

AES加密算法

AES算法原理 对称加密算法&#xff08;用于取代DES算法&#xff0c;发展历史DES-3DES-AES&#xff09; 明文长度固定为128位&#xff08;DES&#xff1a;64位&#xff09;&#xff0c;密钥长度可128位、192位、256位&#xff08;DES&#xff1a;64位&#xff09; 加密原理 …

你是如何对待植物神经紊乱的?

大家好&#xff0c;你们是如何对待植物神经紊乱这种疾病的&#xff1f; 你们知道吗&#xff1f;植物神经紊乱是一种情绪情志障碍伴躯体化症状的特殊且复杂的疾病&#xff0c;这种疾病可能会导致浑身的不适。 并且&#xff0c;很多植物神经紊乱的患者发现&#xff0c;这种疾病是…

【GD32F427开发板试用】硬件SPI通信驱动CH376芯片,用单片机实现U盘数据下载

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;周文杰 SPI通信作为单片机多种基础数据传输模式中的一种&#xff0c;驱动外部芯片CH376实现数据导出到U盘功能在实际工程项目中是很方便的。本…

字符设备驱动之mmap、select

一、mmap mmap&#xff0c;简而言之就是将内核空间的一段内存区域映射到用户空间。映射成功后&#xff0c;用户对这段内存区域的修改可以直接反映到内核空间&#xff0c;相反&#xff0c;内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间与用户空间两者之…

Prometheus + Grafana + Alertmanager 本地安装调试

一、简介 Prometheus 是一款强大的监控软件&#xff0c;一般会与Grafana和Alertmanager一起配合使用&#xff0c;而且多用于k8s集群。简介的话网上很多&#xff0c;官网 更是详细&#xff0c;这里就不班门弄斧了。k8s集群环境下的安装网上很多&#xff0c;但是k8s集群搭建时间…

【实际开发10】- 远程调用 ( Feign )

目录 1. Feign 调用注意事项 - ★★★ 1. 【原则】: 禁止遍历 - 多次跨服务调用接口 ( 提需求 : idList ) 1. 单一数据查询 , 可直接用 Feign单一查询接口 2. List数据查询 , 需进行 Feign 数据转换 , 禁止遍历 Feign 3. stream() : 从List<对象> , 取出 id 和 name…

关于PS VR2和独占,开发者和分析师都怎么看

近期&#xff0c;索尼正式宣布了PS VR2首发游戏列表&#xff0c;共计37款游戏&#xff0c;其中包括备受关注的IP大作《地平线&#xff1a;山之召唤》等。从这37款首发阵容中可以看到一个现象&#xff0c;大部分游戏是非新作&#xff0c;而是已经在PS VR1或其它VR平台上线&#…

C++基础——C++数组

C基础——C数组C 数组声明数组初始化数组访问数组元素C 中数组详解C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的…

【数据结构基础】线性表 - 链表

n个节点离散分配&#xff0c;彼此通过指针相连&#xff0c;每个节点只有一个前驱节点&#xff0c;每个节点只有一个后续节点&#xff0c;首节点没有前驱节点&#xff0c;尾节点没有后续节点。确定一个链表我们只需要头指针&#xff0c;通过头指针就可以把整个链表都能推出来。知…

设计模式-UML图

目录 2&#xff0c;UML图 2.1 类图概述 2.2 类图的作用 2.3 类图表示法 2.3.1 类的表示方式 2.3.2 类与类之间关系的表示方式 2&#xff0c;UML图 统一建模语言&#xff08;Unified Modeling Language&#xff0c;UML&#xff09;是用来设计软件的可视化建模语言。它的特…

Matlab pdetool

云溪岩绵迎彩霞,博主精神压力大呀,没人说说知心话啊,SCU物理要命啦........基本物理方程静电磁场交流电磁场热传导Options->ApplicationGeneric Scalar泛型标量Generic System通用系统Structural Mechanics,Plane Stress结构力学 - 平面应力Structural Mechanics,Plane Stra…

Flashback Oracle文档阅读

和Flashback相关的文档大多位于备份和恢复用户指南 和Oracle 数据库开发指南中。 基本概念 请参看备份和恢复用户指南的1.4 About Oracle Flashback Technology。 Oracle Flashback Technology的定义&#xff1a; A set of Oracle Database features that provide an additi…

Verilog HDL门级建模

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…