Kaze
Kaze
Published on 2022-04-19 / 94 Visits
0
0

SSM框架

MyBatis 入门

配置

  • 添加依赖:

    1. 添加 Spring Web 依赖
    2. 添加 MyBatis Framework 依赖
    3. 添加 MySQL Driver 依赖
  • 配置数据源:

    在工程的 application.properties 文件里,数据源配置 格式 为:

    spring.datasource.url=jdbc:mysql://mysql数据库地址:数据库端口/数据库名称?serverTimezone=GMT%2B8
    spring.datasource.username=用户名
    spring.datasource.password=密码
    

    serverTimezone=GMT%2B8 这个参数的作用在于设置数据库的时区为中国区域

MyBatis 映射对象

  • 所有的 ORM 框架都需要有一个 Java 对象来映射数据库的表,并且是一一对应的,一般我们把这类对象称为 DO 对象,对象名称的规范是表名+DO

  • 一般情况下企业中都会把这个DO对象存放在xxx.xxx.dataobject包下,所以DO就是 dataobject 的缩写。

  • DO 对象和普通的 POJO 类并无不同,唯一要注意的点是属性类型要和数据库类型进行匹配

    JDBC 类型(数据库字段类型)Java 数据类型
    VARCHARString
    TEXTString
    INTEGERint
    DOUBLEdouble
    BIGINTlong
    DATETIMEDate

MyBatis DAO

  • 数据层的服务称为DAO层,DAO层包含对数据库操作的接口和实现类。
  • MyBatis 只需要定义接口就可以完成数据的增删改查

MyBatis 查询

  • 在DAO接口类中添加接口方法:

    @Mapper
    public interface UserDAO {
    	//默认参数就是 SQL 语句
        @Select("SELECT id,user_name as userName,pwd,nick_name as nickName,avatar,gmt_created as gmtCreated,gmt_modified as gmtModified FROM user")
        List<UserDO> findAll();
    
    }
    
  • MyBatis 转化对象的时候是按照名称一一对应的,表字段和DO字段名称不一致的时候,数据就会映射失败。可以使用 SQL 别名来解决这个问题。

MyBatis 插入

  • @Insert("INSERT INTO user (user_name, pwd, nick_name,avatar,gmt_created,gmt_modified) VALUES(#{userName}, #{pwd}, #{nickName}, #{avatar},now(),now())")
      @Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
      int insert(UserDO userDO);
    
  • Options 注解有三个参数

    • useGeneratedKeys

      设置为 true,代表允许数据库使用自增主键

    • keyColumn

      设置表的主键字段名称,一般都是id

    • keyProperty

      设置DO模型的主键字段,一般都是id

  • 一般数据插入我们都使用 POST 请求,同时使用 JSON 方式传递数据,为了接收 JSON 参数,需要在参数中添加@RequestBody 注解

MyBatis 修改

  • @Update("update user set nick_name=#{nickName},gmt_modified=now() where id=#{id}")
       int update(UserDO userDO);
    

MyBatis 删除

  • @Delete("delete from user where id=#{id}")
       int delete(@Param("id") long id);
    

    # 中的 id 要和 @Param("id") 中的 id 一样

MyBatis 简单查询

  • 根据用户名查询

    @Select("select id,user_name as userName,pwd,nick_name as nickName,avatar,gmt_created as gmtCreated,gmt_modified as gmtModified  from user  where user_name=#{userName} limit 1")
      UserDO findByUserName(@Param("userName") String name);
    

MyBatis XML语句

MyBatis XML 配置

  • application.properties 文件中,添加配置mybatis.mapper-locations,这个配置用于指定 MyBatis Mapper XML 文件路径,一般来说这个路径和 DAO 的包路径一致

    mybatis.mapper-locations=classpath:com/youkeda/comment/dao/*.xml
    //对应的文件路径
    src/main/resources/com/youkeda/comment/dao/
    

MyBatis XML Mapper

  • 一个 DAO 类对应一个 DAO.xml 文件,如 UserDAO.java 会对应 UserDAO.xml

    <?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="com.youkeda.comment.dao.UserDAO">
    
      <resultMap id="userResultMap" type="com.youkeda.comment.dataobject.UserDO">
        <id column="id" property="id"/>
        <result column="user_name" property="userName"/>
      </resultMap>
    
    </mapper>
    
  • 头信息:固定格式,复制即可

    <?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命名空间,一般就是这个 mapper 所对应的 DAO 接口的全称
    <mapper namespace="com.youkeda.comment.dao.UserDAO">
    
    </mapper>
    
  • resultMap:

    • resultMap 用于处理表和 DO 对象的属性映射,确保表中的每个字段都有属性可以匹配

      <resultMap id="userResultMap" type="com.youkeda.comment.dataobject.UserDO">
          <id column="id" property="id"/>
          <result column="user_name" property="userName"/>
        </resultMap>
      
    • 映射子对象:resultMap 有一个子节点 association 可以聚合其他模型

       <resultMap id="commentModelResultMap" type="com.youkeda.comment.model.Comment">
          <id column="id" property="id"/>
          <result column="ref_id" property="refId"/>
          <result column="content" property="content"/>
          <result column="parent_id" property="parentId"/>
          <result column="gmt_created" property="gmtCreated"/>
          <result column="gmt_modified" property="gmtModified"/>
          <association property="author" javaType="com.youkeda.comment.model.User">
              <id property="id" column="user_id"/>
              <result column="user_name" property="userName"/>
              <result column="nick_name" property="nickName"/>
              <result column="avatar" property="avatar"/>
          </association>
      </resultMap>
      
    • resultMap属性

      • id

        唯一标示,一般我们的命名规则是 xxxResultMap,比如这里的 userResultMap

      • type

        对应的 DO 类完整路径,比如这里的 com.youkeda.comment.dataobject.UserDO

    • resultMap 子节点

      • id

        设置数据库主键字段信息,column 属性对应的是表的字段名称,property 对应的是 DO 属性名称

      • result

        设置数据库其他字段信息,column 属性对应的是表的字段名称,property 对应的是 DO 属性名称

MyBatis XML Insert 语句

  • 在 mapper 节点下添加 insert 节点

    <insert id="add" parameterType="com.youkeda.comment.dataobject.UserDO" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO user (user_name, pwd, nick_name,avatar,gmt_created,gmt_modified)
        VALUES(#{userName}, #{pwd}, #{nickName}, #{avatar},now(),now())
      </insert>
    
  • insert节点属性:

    • id

      同 DAO 类的方法名,同一个 xml 内是要唯一的,比如这里的 id="add" 是和 UserDAO.add 一致的

    • parameterType

      这个用于传递参数类型,一般是和 UserDAO.add 方法的参数类型一致的, add 方法 int add(UserDO userDO)

MyBatis XML Update/Delete 语句

  • update:

    <update id="update" parameterType="com.youkeda.comment.dataobject.UserDO">
            update user set nick_name=#{nickName},gmt_modified=now() where id=#{id}
    </update>
    
  • delete:

    <delete id="delete">
            delete from user where id=#{id}
    </delete>
    
  • delete 方法的参数是由 @Param 注解组成,默认情况下,MyBaits 会把这类数据当成 Map 数据来传递,而 MyBatis 默认的 parameterType 类型就是 Map,所以可以省略不写

MyBatis XML Select 语句

  • 基于 XML 模式的开发顺序

    1. 创建 DO 对象
    2. 创建 DAO 接口,配置 @Mapper 注解
    3. 创建 XML 文件,并完成 resultMap 配置
    4. 创建 DAO 接口方法
    5. 创建对应的 XML 语句
  • select 语句:

    • 属性resultMap的值我们一般配置为该 XML 文件下的resultMap节点的 id 值
    <select id="findByUserName" resultMap="userResultMap">
      select * from user where user_name=#{userName} limit 1
    </select>
    
  • 当用LIKE子句进行模糊查询时,要用concat('%',#{keyWord},'%')

MyBatis 动态SQL

MyBatis XML 条件语句

  • if+set:

    <update id="update" parameterType="com.youkeda.comment.dataobject.UserDO">
      update user
      <set>
        <if test="nickName != null">
          nick_name=#{nickName},
        </if>
        <if test="avatar != null">
          avatar=#{avatar},
        </if>
        gmt_modified=now()
      </set>
       where id=#{id}
    </update>
    
  • if+select+where:

    <select id="search" resultMap="userResultMap">
      select * from user
       <where>
          <if test="keyWord != null">
              user_name like CONCAT('%',#{keyWord},'%')
                or nick_name like CONCAT('%',#{keyWord},'%')
          </if>
          <if test="time != null">
            and  gmt_created <![CDATA[ >= ]]> #{time}
          </if>
       </where>
    </select>
    

    >=、<、<=、>、& 这类的表达式会导致 MyBatis 解析失败,所以我们需要使用<![CDATA[ key ]]> 来包围住。

  • URL 传递过来的参数都是字符串类型的,所以API的LocalDateTime类型参数要加上@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解把字符串参数转化为日期类型

MyBatis XML 循环语句

  • foreach 语法:

    <select id="findByIds" resultMap="userResultMap">
        select * from user
        <where>
            id in
            <foreach item="item" index="index" collection="ids"
                        open="(" separator="," close=")">
                #{item}
            </foreach>
        </where>
    </select>
    
    • collection 指定集合的上下文参数名称,比如这里的 ids,对应的是 @Param("ids")
    • item 指定遍历中的每一个数据的变量,一般我们用 item 命名,使用 item.userName 获取具体的值
    • index集合的索引值,从0开始
    • separator遍历每条记录并添加分隔符
    • open表示的是节点开始时自定义的分隔符
    • close表示的是节点结束时自定义的分隔符

MyBatis 进阶

MyBatis 分页插件

  • PageHelper分页插件

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.13</version>
    </dependency>
    
  • 用法示例:

    public Paging<UserDO> getAll() {
            // 设置当前页数为1,以及每页3条记录
            Page<UserDO> page = PageHelper.startPage(1, 3).doSelectPage(() -> userDAO.findAll());
    
            return new Paging<>(page.getPageNum(), page.getPageSize(), page.getPages(), page.getTotal(), page.getResult());
        }
    
  • 返回类型Page对象是 MyBatis 封装的分页模型,常用方法有:

    • getResult() 获取当前页面数据
    • getPages() 获取总页数
    • getTotal() 获取总记录数
    • getPageNum() 获取当前页数
  • 在企业开发中,通常都会额外封装一个通用的分页模型Paging用于处理返回值

    public class Paging<R> implements Serializable {
    
        private static final long serialVersionUID = 522660448543880825L;
        /**
         * 页数
         */
        private int pageNum;
    
        /**
         * 每页数量
         */
        private int pageSize = 15;
        /**
         * 总页数
         */
        private int totalPage;
    
        /**
         * 总记录数
         */
        private long totalCount;
    
        /**
         * 集合数据
         */
        private List<R> data;
    
        public Paging() {
    
        }
    
        public Paging(int pageNum, int pageSize, int totalPage, long totalCount, List<R> data) {
            this.pageNum = pageNum;
            this.pageSize = pageSize;
            this.totalPage = totalPage;
            this.totalCount = totalCount;
            this.data = data;
        }
    
        // 省略 getter、setter
    
    }
    

Druid 连接池

  • 依赖:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.23</version>
    </dependency>
    
  • 开启监控:在 application.properties 文件里增加配置项

    spring.datasource.druid.stat-view-servlet.enabled=true
    spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
    spring.datasource.druid.stat-view-servlet.login-username=自定义
    spring.datasource.druid.stat-view-servlet.login-password=自定义
    spring.datasource.druid.stat-view-servlet.allow=
    spring.datasource.druid.stat-view-servlet.deny=
    
  • 本地访问地址是http://localhost:8080/druid/login.html

评论服务开发

开发技巧

  • 一般User类中会有密码属性 pwd,而密码是不能泄漏的,所以我们需要在服务端输出的时候过滤掉这个属性,我们可以使用 jackson 的注解来忽略

    @JsonSerialize(using = NullSerializer.class)

  • 对于时间我们一般都希望输出的格式是yyyy-MM-dd HH:mm:ss,我们依然可以借助 jackson 的注解来完成格式化输出

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")

  • 为了能够统一描述返回数据,一般需要新增一个 Result 公共模型,如下:

    public class Result<D> implements Serializable {
    
        @JsonProperty("isSuccess") //该注解用于自定义 JSON 输出的时候的字段名称
        private boolean success = false;
    
        private String code;
        private String message;
    
        private D data;
    
        // 省略 getter、setter
    
    }
    
  • 优化JSON:

    在application.properties 文件中,新增如下配置

    spring.jackson.deserialization.fail-on-unknown-properties=false
    spring.jackson.default-property-inclusion=non_null
    

    上面2个配置分别的作用是:

    • 允许序列化未知的字段,可以兼容 Java 模型和 JSON 数据不一致的情况
    • 忽略 null 字段
  • 在企业中,我们一般会结合 commons-lang3 库来处理字符串,依赖如下:

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.10</version>
    </dependency>
    

    可以使用 StringUtils.isEmpty(str) 来判断是否为空,注意包路径为org.apache.commons.lang3.StringUtils

  • 加密:

    一般来说,我们会使用 md5 算法进行加密,建议使用 commons-codec 库进行加密处理

    依赖如下:

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.14</version>
    </dependency>
    

    加密的例子:

    // 密码加自定义盐值,确保密码安全
    String saltPwd = pwd + "_ykd2050";
    // 生成md5值,并转为大写字母
    String md5Pwd = DigestUtils.md5Hex(saltPwd).toUpperCase();
    
  • 通用开发模式:

    设计领域模型->开发基础的DAO =>开发Service =>开发API

  • 优化DO和Model互转:

    在企业中,我们一般会把DO和Model的转换代码抽象成公共方法,放在 DO 对象里,一般命名为 toModel

    public User toModel() {
            User user = new User();
            user.setId(getId());
            user.setUserName(getUserName());
            user.setNickName(getNickName());
            user.setAvatar(getAvatar());
            user.setGmtCreated(getGmtCreated());
            user.setGmtModified(getGmtModified());
            return user;
        }
    
  • 安全处理:

    基于安全的考虑,用户自定义输入的文本需要禁止 HTML 标签,这样可以避免很多恶意的代码,可以使用 commons-text 这个库进行 HTML 转义,从而达到禁用 HTML 标签的作用,需要添加依赖

    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.8</version>
    </dependency>
    

    执行下面的代码可以完成转义

    String body = StringEscapeUtils.escapeHtml4(content);
    
  • 将comment集合变成树状结构,也就是说如果评论有回复,那么回复数据应该在这条评论的 children 字段里

    //查询所有的评论记录包含回复的
    List<Comment> comments = commentDAO.findByRefId(refId);
    //构建 map 结构
    Map<Long, Comment> commentMap = new HashMap<>();
    //初始化一个虚拟根节点,0 可以对应的是所有一级评论的父亲
    commentMap.put(0L, new Comment());
    //把所有的评论转换为 map 数据
    comments.forEach(comment -> commentMap.put(comment.getId(), comment));
    // 再次遍历评论数据
    comments.forEach(comment -> {
        //得到父评论
        Comment parent = commentMap.get(comment.getParentId());
        if (parent != null) {
            // 初始化 children 变量
            if (parent.getChildren() == null) {
                parent.setChildren(new ArrayList<>());
            }
            // 在父评论里添加回复数据
            parent.getChildren().add(comment);
        }
    });
    // 得到所有的一级评论
    List<Comment> data = commentMap.get(0L).getChildren();
    

Spring 安全

CORS

  • CORS全称"跨域资源共享",它允许浏览器向跨源服务器发出 XML HttpRequest 请求,从而克服了 AJAX 只能同源使用的限制(浏览器会禁止读取跨域名的资源)。

  • SpringBoot 支持 CORS 请求:

    @Configuration
    public class CorsConfig {
    
        @Bean
        public FilterRegistrationBean corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            // 设置你要允许的网站域名,如果全允许则设为 *
            config.addAllowedOrigin("*");
            // 如果要限制 HEADER 或 METHOD 请自行更改
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            config.setMaxAge(Duration.ofDays(5));
            source.registerCorsConfiguration("/**", config);
            FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
            // 这个顺序很重要哦,为避免麻烦请设置在最前
            bean.setOrder(0);
            return bean;
        }
    }
    

Comment