数据库

  • 一个系列功能的表名尽量统一规范,如:goods、goods_dictionary、goods_xxx,就要改 goods 为 goods_info 统一
  • 表名和字段名都是各自统一大小写(数据库不区分大小写),所以一般就表名全小写,字段名全大写,字母之间下划线衔接
  • 数据表 ID 不是自增长、不是 int,而是 varchar 类型的后端自行生成的,如:goods.setGoodsId(IDUtils.getUUID32()); UUID 自动生成 32 位的 ID,且不会显现到公开页面上
  • 字典表 dictionary 主要字段 CODE、PCODE、NAME,PCODE 可为空,空就表示是没有父编码的最顶层了
  • 涉及到级联部分会在后面再说明

后端

entity、mapper、dao、service

先前所学

  • entity
  • 对应数据库的每一个表,内写对应的所有字段的属性名,alt + insert 快捷加 Getter and Setter、toString()
  • mapper.xml 文件位置方式
  1. 都放在 resources 下严格与 dao 同目录下的地方
  2. resources 下不严格要求,但要配置文件配置:<property name="mapperLocations" value="classpath:mapper/*.xml"/>
  • dao 层编写接口,mapper.xml 编写 sql 语句实现接口方法
  • mapper.xml 里 <mapper namespace="com.qst.项目名.dao.dao接口"> 绑定
  • dao 层方法名的参数列表中加 @Param:不同参数名需要明确参数名时转换 @Param("ANO") no,或是在多个参数时使用(单个的话可以自行解析)
  • mapper 里 数据库列名 = #{传来的参数}
  • service 层里同样接口,impl 继承
  • @Service 绑定在 impl 中,@Autowired 自动注入 dao 层的接口,impl 若无需要的额外操作就直接 return dao 的方法名即可
  • 小提一嘴:不用 mapper.xml 方式时,就是给 dao 的 impl 加上 @Repository 注解

实用方式

  • 最重要的一点:千万不要按照自己的原来编写的风格和目录样式来
  • entity
  • 推荐使用 @Data —— lombok 依赖自动生成 toStringequalshashCodegettersetter 等方法
  • 实体类里可以划分出多个文件夹存放不同情况下用的实体类,如 PO、DTO、VO、BO 等,根据统一需求来,一般情况下只用 entity 也无妨
  • mapper.xml
  • 同是在 resources 下,位置按框架配置要求来,大体样式并无不同
  • <mapper namespace="自行定义为文件也可,由于可读性也会要求写java下的mapper全路径">(爆红也无碍),对应的,在使用时 private static final String NAMESPACE = "与mapper里的namespace同值"; 匹配(下面 service 的 impl 会说)
  • <select> 同一个表的话就全写在一起,不要有一个表但存在多个查询,不同的要求全部用动态 sql 语句编辑,如:
<select id="selectGoods" parameterType="cn.com.xxx.xxx.entity.Goods" resultType="cn.com.xxx.xxx.entity.Goods">
        select g.GOODS_ID, g.GOODS_NAME, g.GOODS_CODE, g.GOODS_DATE, g.GOODS_STATE, g.GOODS_NUM_STOCK, g.GOODS_PRICE, g.GOODS_BRAND, g.GOODS_TEL,
        gd.GOODS_DICTIONARY_NAME, gd.GOODS_DICTIONARY_CODE, gd.GOODS_DICTIONARY_PCODE, gd.GOODS_DICTIONARY_TYPE,
        gt.TREE_CODE, gt.TREE_PARENT_CODE, gt.TREE_NAME
        from goods_info g
        left join goods_dictionary gd on g.GOODS_CODE = gd.GOODS_DICTIONARY_CODE
        left join goods_tree gt on g.GOODS_TREE_CODE = gt.TREE_CODE
        <where>
            <if test="goodsName!=null and goodsName != ''">
                AND g.GOODS_NAME like CONCAT('%',#{goodsName},'%')
            </if>
            <if test="goodsState!=null and goodsState != ''">
                AND g.GOODS_STATE = #{goodsState}
            </if>
            <if test="goodsCode!=null and goodsCode != ''">
                AND g.GOODS_CODE = #{goodsCode}
            </if>
            <if test="treeCodeList!= null and treeCodeList.size() > 0">
                AND g.GOODS_TREE_CODE in
                <foreach collection="treeCodeList" open="(" close=")" separator="," item="treeCode">
                    #{treeCode}
                </foreach>
            </if>
            <if test="goodsPcodeList!= null and goodsPcodeList.size() > 0">
                AND g.GOODS_CODE in
                <foreach collection="goodsPcodeList" open="(" close=")" separator="," item="childCode">
                    #{childCode}
                </foreach>
            </if>
        </where>
        ORDER BY GOODS_NAME
    </select>
  • 一般都是配置好了驼峰转换,若是没有转换需要用 resultMap:
<select id="selectGoods" parameterType="cn.com.xxx.xxx.entity.Goods" resultMap="goodsResultMap">
        <!-- ...... -->
    
    
    <resultMap id="goodsResultMap" type="cn.com.xxx.xxx.entity.Goods">
        <id property="goodsId" column="GOODS_ID"/>
        <result property="goodsName" column="GOODS_NAME"/>
        <result property="goodsCode" column="GOODS_CODE" />
        <result property="goodsDate" column="GOODS_DATE" javaType="java.time.LocalDateTime" jdbcType="TIMESTAMP"/>
        <result property="goodsState" column="GOODS_STATE"/>
        <result property="goodsNumStock" column="GOODS_NUM_STOCK"/>
        <result property="goodsPrice" column="GOODS_PRICE"/>
        <result property="goodsBrand" column="GOODS_BRAND"/>
        <result property="goodsTel" column="GOODS_TEL"/>
        <association property="goodsDic" javaType="cn.com.victorysoft.vs.entity.GoodsDic">
            <result property="goodsDicId" column="GOODS_DICTIONARY_ID"/>
            <result property="goodsDicName" column="GOODS_DICTIONARY_NAME"/>
            <result property="goodsDicCode" column="GOODS_DICTIONARY_CODE"/>
            <result property="goodsDicPcode" column="GOODS_DICTIONARY_PCODE"/>
            <result property="goodsDicType" column="GOODS_DICTIONARY_TYPE"/>
        </association>
        <association property="goodsTree" javaType="cn.com.victorysoft.vs.entity.GoodsTree">
            <result property="treeId" column="TREE_ID"/>
            <result property="treeCode" column="TREE_CODE"/>
            <result property="treeParentCode" column="TREE_PARENT_CODE"/>
            <result property="treeName" column="TREE_NAME"/>
        </association>
    </resultMap>
  • 更新也不要全写进去,而是做判断:
<update id="updateGoodsById" parameterType="cn.com.xxx.xxx.entity.Goods">
        update goods_info
        <set>
            <if test="goodsName!=null">
                GOODS_NAME = #{goodsName}
            </if>
            <if test="goodsCode!=null">
                , GOODS_CODE = #{goodsCode}
            </if>
        </set>
        where GOODS_ID = #{goodsId}
    </update>
  • 批量删除同理:
<delete id="deleteSomeGoodsByIds" parameterType="list" >
        delete from goods_info
        where GOODS_ID in
        <foreach collection="list" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
  • mapper 里不要写 ' * ',若是获取全部就全部都写上
  • 查询的标签上要写上 parameterType 指定输入参数的类型,resultType 是期望 MyBatis 将查询结果映射到的 Java 类型,简单来说就是指定查询返回的样式
  • 若是使用了 resultMap 来进行更复杂的结果映射,就可以省略 resultType
  • dao、service
  • 在提供了封装了 MyBatis 常用语句的类下,可以不加 dao 层接口或 impl,也不需要 service 的接口,直接在 impl 里加上 private static final String NAMESPACE = "与mapper里的namespace同值";,这样就可以匹配到 mapper 的方法,使用时:封装的方法参数就是 (statement, parameter)(NAMESPACE + ".mapper的方法id", 传参值);

conroller

大体回顾

  • @RestController 标识一个类是 RESTful 风格的控制器,大体样式:
@Api(value = "xxxxxx", tags = "xxxxxx")
@RestController
@RequestMapping("/xxx")
public class XxxController {
    @Autowired
    private XxxService xxxService;

    /**
     *尽量标注每个方法的作用
     * @Param 必要时备注参数含义
     * @return
     */
    @ApiOperation(value = "xxxx", notes = "xxxx")
    @PostMapping("yyyXxx")
    public 封装好的类型 yyyXxx(@ApiParam(name = "page", value = "页面", required = true) @RequestParam (value = "page", required = true, defaultValue = "1") int page,
                                      @ApiParam(name = "rows", value = "行数", required = true) @RequestParam (value = "rows", required = true, defaultValue = "#{T(java.lang.Integer).MAX_VALUE}") int rows,
                                      @ApiParam(name = "xxx", value = "xxxx", required = true) @RequestBody Xxx xxx) {
        JsonMessage success = new JsonMessage().success(xxxService.yyyyXxxx(xxx, page, rows));
        return success;
    }
  • @RestController:是 @Controller@ResponseBody 的组合,表示该类的所有方法都会返回数据而不是视图
  • @GetMapping:映射 GET 请求
  • @PostMapping:映射 POST 请求
  • @PutMapping:映射 PUT 请求
  • @DeleteMapping:映射 DELETE 请求
  • @Api@ApiOperation@ApiParam 等: Swagger 注解用于生成 API 文档,提供给开发者或前端人员查看和理解 API 的用途和参数,此处参考规范要求
  • “ 封装好的类型 ”:就是在前后端分离情况下需要规定返回样式供前端使用,所需内容也都包含在其中,每次发送到前端都是此种样式,也就例如方法最后返回的 JsonMessage
  • " #{T(java.lang.Integer).MAX_VALUE} ":是使用 SpEL(Spring Expression Language,Spring 表达式语言)表达式来指定默认值的一种方式,含义是获取 Java 中 Integer 类的 MAX_VALUE 静态字段的值,即整数的最大值(在 Spring 表达式中,T() 是一个类型运算符,用于获取类或接口的静态字段或方法)
  • 根据需求 defaultValue 也可以直接写一个数值,例如 " 10 "

各种注解作用

  • 上述后端代码对应的前端样式就是:
const url = xxx + "/yyyXxx";
axiosUtil.post(`${url}?page=${currentPage}&rows=${pageSize}`, {
    xxxDic: {
        xxxDicType: xxxDicType,
        xxxDicPcode: xxxPcode,
    },
    xxxName: goodsName
}
    // ......
)
  • @RequestParam 是从请求中提取查询参数,可以用于方法的参数上,将请求中的参数值映射到方法的参数上
  • 也就是上述的 page=${currentPage}&rows=${pageSize} 部分
  • @RequestBody 用于从请求体中提取数据,通常用于接收客户端发送的 JSON 或 XML 格式的数据
  • 前端使用 Axios 等库发送 HTTP 请求时,编写的看到的发送的一般都是 JavaScript 对象字面量样式,然后发送时会自动将 JavaScript 对象转换为 JSON 格式由后端的 @RequestBody 解析获取
  • 具体发送代码会在后面前端 vue 处呈现
  • 这里就不得不提一些概念了:
  • Java 对象:就是常见的后端编码的对象,常见的就是些实体类的对象,后端写的几乎都是 Java 对象的样式,如
public class Goods {
    String goodsName;
    String goodsState;
    GoodsDic goodsDic;
    String treeCode;
// ...... Setter、Getter......
}
  • JavaScript 对象:前端常编写的样式,如
{
	goodsName: goodsName,
    goodsState: goodsState,
    goodsDic: {
        goodsDicType: goodsDicType,
        goodsDicPcode: goodsPcode,
    },
    treeCode: treeCode,
}
  • 这里的冒号后面的都是数据值
  • JSON 对象:就是 JavaScript 对象键值都加上双引号,如
{
    "goodsName": "goodsNameValue",
    "goodsState": "goodsStateValue",
    "goodsDic": {
        "goodsDicType": "goodsDicTypeValue",
        "goodsDicPcode": "goodsPcodeValue"
    },
    "treeCode": "treeCodeValue"
}
  • 这里的冒号后面的都是数据值
  • @PathVariable 用于处理路径变量的注解,它主要用于从请求路径中提取变量值,例如参数列表为 ("yyyXxx/{xxxId}") 样式时,就能用 @PathVariable("xxxId") String xxxId 从 url 中获取
  • 注意,@PathVariable@RequestParam 不一样
  • @PathVariable 用于提取 URI 模板中的变量,通常用于提取路径中的变量,例如,/example/{id} 中的 {id} 就是一个路径变量,前端就是
const url = 'xxx/xxx/xxx'
axiosUtil.post(`${url}/${goodsId}`,  ......
  • @RequestParam 用于提取查询参数,即在 URL 中通过 ?key=value 形式传递的参数,前端就是
const url = 'xxx/xxx/xxx'
axiosUtil.post(`${url}?page=${currentPage}&rows=${pageSize}`,  ......
  • 只有 @RequestBody 是用于 POST,其他两种是都适用,前端请求是 GET,并且参数作为 URL 的一部分,那么直接使用 @RequestParam,一般不写的话 Spring MVC 默认会将这些参数作为 @RequestParam 处理
  • 举个例子,批量删除时需要后端获取 id 数组, 如:["1", "20"] :
  • get:会将其写到 url 上,并且以 xxx?0=1&1=20 的形式发送给后端,后端就不能简单用 @RequestBody List<String> ids 来操作了
  • post:就会放进请求体中:
// 源:
["1", "20"]
// 下拉展开显示是:
0:"1"
1:”20“

// 已分析视图就是:
["1", "20"]
  • 的样式,则请求体里的 ["1", "20"] 就可以在后端用 @RequestBody List<String> ids 来操作了
  • 简单来讲就是:
@PathVariable —— 路径取 " /xxx "

@RequestParam —— get 取 " ?xxx "

@RequestBody —— post 取方法体中 "封装的"