留一点专注,给这篇手记。
Java 工程

Spring Boot 核心笔记:三层架构、IOC 与 MyBatis

学习 Spring Boot 时整理的实战笔记:三层架构、IOC 容器注解、Restful 风格、参数接收,以及 MyBatis 的预编译、增删改查与动态 SQL。

Java 工程手记第 2 / 3 篇
这篇文章的目录⌄
  1. 三层架构
  2. IOC 容器
  3. @Autowired
  4. 开发规范——Restful 风格
  5. 各类参数
  6. MyBatis
  7. 什么是 ORM?
  8. 为什么 MyBatis 是半自动的 ORM 框架?
  9. 预编译 SQL
  10. SQL 注入
  11. 在 Java 中使用预编译 SQL
  12. 占位符 #{} 和 ${}
  13. MyBatis 增删改查操作
  14. 新增操作
  15. 新增操作(主键返回)
  16. 查询操作
  17. XML 映射文件的增删改查操作(更灵活)
  18. 映射文件规范:三个一致
  19. 创建 XML 文件
  20. XML 查询操作
  21. MyBatis 动态 SQL
  22. <if> 标签
  23. <where> 标签
  24. <set> 标签
  25. <foreach> 标签
  26. <sql> 标签与 <include> 标签
  27. 数据库连接池
  28. lombok
  29. 小结

学习 Spring Boot 时整理的笔记,覆盖项目里的核心概念:三层架构、IOC 容器、Restful 风格、参数接收,以及 MyBatis 的完整用法。

三层架构

  • Controller 层(表现层):接收请求、返回响应。
  • Service 层(业务层):业务逻辑的处理和组装。
  • Dao / Mapper 层(持久层):与数据库交互。

数据流向:Controller 具体方法 -> Service 具体接口 -> Service 接口实现类 -> Mapper 具体方法(操作数据库)。

IOC 容器

IOC(Inversion of Control,控制反转)把对象的创建和依赖管理交给 Spring 容器,通过注解声明组件。

  • @Component:最基本的注解,用于将一个普通的类标记为 Spring 的组件。它是其他三个注解的父注解,通常用于标记一般性的业务逻辑类。
  • @Repository:用于标记数据访问对象(DAO),通常用于与数据库交互的类。
  • @Service:用于标记服务类,通常用于业务逻辑的处理和组装。
  • @Controller:用于标记控制器类,通常用于 Web 应用程序中处理用户请求和返回响应。
  • @Mapper:在接口类上添加 @Mapper,将 dao 交给 spring 容器管理,不需要写 mapper 映射文件,编译之后会生成相应的接口实现类。添加位置:接口类上面。

@Repository、@Service、@Controller 三者是对 @Component 的特化,用于提供更具体的语义。

@Autowired

通过 @Autowired 注解,可以自动将 Spring 容器中匹配类型的 Bean 注入到目标对象中,避免了手动处理依赖对象的创建和管理,提高了代码的可维护性和可测试性。

即不用创建对象便可调用方法,如在测试中或者 controller 中,接口的使用可以直接通过 @Autowired 进行依赖注入,以便于直接使用该接口的方法。

开发规范——Restful 风格

通用写法:@RequestMapping("/users"),或 @RequestMapping(value = "/users", method = RequestMethod.GET) 指定请求方式为 GET。

  • 查:@GetMapping("/users")
    http://localhost:/users/1
    查询 id 为 1 的用户
  • 增:@PostMapping("/users")
  • 改:@PutMapping("/users")
  • 删:@DeleteMapping("/users") —— 根据 id 删除用户信息

各类参数

  • 简单参数:定义方法形参,请求参数名与形参变量名一致。如果不一致,通过 @RequestParam 手动映射。
  • 实体参数:请求参数名与实体对象的属性名一致,会自动接收封装。
  • 数组参数:数组——请求参数名与数组名一致,直接封装;集合——请求参数名与集合名一致,@RequestParam 绑定关系。
  • 日期参数:@DateTimeFormat。
  • JSON 参数:@RequestBody。
  • 路径参数:@PathVariable。

MyBatis

MyBatis 是一款半自动的 ORM 持久层框架,具有较高的 SQL 灵活性,支持高级映射(一对一、一对多)、动态 SQL、延迟加载和缓存等特性,但它的数据库无关性较低。

什么是 ORM?

Object Relation Mapping,对象关系映射。对象指的是 Java 对象,关系指的是数据库中的关系模型,
对象关系映射指的就是在 Java 对象和数据库的关系模型之间建立一种对应关系。
比如用一个 Java 的 Student 类,去对应数据库中的一张 student 表,
类中的属性和表中的列一一对应:Student 类对应 student 表,一个 Student 对象对应 student 表中的一行数据。

为什么 MyBatis 是半自动的 ORM 框架?

用 MyBatis 开发需要手动编写 SQL 语句,而全自动的 ORM 框架(如 Hibernate)不需要。

用 Hibernate 开发,只需要定义好 ORM 映射关系,就可以直接进行 CRUD 操作。由于 MyBatis 需要手写 SQL 语句,所以它有较高的灵活性,可以根据需要自由地对 SQL 进行定制;但也因为要手写 SQL,当切换数据库时 SQL 语句可能就要重写,因为不同的数据库有不同的方言(Dialect),所以 MyBatis 的数据库无关性低。

虽然 MyBatis 需要手写 SQL,但相比 JDBC,它提供了输入映射和输出映射,可以很方便地进行 SQL 参数设置以及结果集封装,还提供了关联查询和动态 SQL 等功能,极大地提升了开发效率,并且它的学习成本也比 Hibernate 低很多。

预编译 SQL

用问号做占位符代替参数的位置:

select count(*) from emp where username = ? and password = ?

优点:

  • 性能更高
  • 更安全(防止 SQL 注入)——将整个字符串当成一个参数传递给预编译的问号

SQL 注入

SQL 注入是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。

select Count(*) from emp where username = 'wuieuwiueiwuiew' and password = '' or '1' = '1';

这里的 password 是不生效的。

在 Java 中使用预编译 SQL

只需要在传递参数时使用井号#加大括号{}的方式传入参数,即可将 SQL 语句变为预编译的 SQL 语句。

占位符 #{} 和 ${}

  • #{}:属于预编译 SQL,较为常用。
  • ${}:有注入风险,不常用、不建议使用。

MyBatis 增删改查操作

新增操作

@Insert("insert into emp(username,name,gender,image, job,entrydate, dept_id,create_time, update_time)"
        + " values (#{username},#{name},#{gender},#{image},#{job}, #{entrydate},#{deptId},#{createrime},#{updaterime})")
public void insert(Emp emp);
  • emp 括号中的参数指的是添加到数据库中的字段。
  • Values 括号中的参数指的是实体类 Emp 的各个属性值,这些属性的值来自前端给后端传递的数据。
  • 因为要传递实体类的属性太多,所以直接用实体类来接收前端的数据,并映射到 Value 的参数上。

新增操作(主键返回)

默认情况下的新增操作不会将字段的主键值返回,所以需要注解 @Options:

  • keyProperty = id:代表将自动生成的主键值赋值给实体类的 id 属性。
  • useGeneratedKeys = true:代表需要拿到自动生成的主键值。

查询操作

属性名与字段名不一致无法封装:默认情况下,若实体属性名与数据库字段名不一致,不能自动封装,只有二者一致时 MyBatis 才会自动封装。

解决方法(有三种):

  1. 起别名
  2. 手动结果映射
  3. 开启驼峰命名(在 application.properties 文件中配置),但编写代码时一定要严格按照驼峰命名规则:数据库的字段名使用下划线命名,实体类属性名使用驼峰命名

推荐第三种方式。

一般查询:

@Select("select * from emp where id = #{id}")
public Emp getById(Integer id);

条件查询:

@Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} "
        + "and entrydate between #{begin} and #{end} order by update_time desc")

表示根据用户姓名(模糊匹配)、性别、创建时间进行查询:

  1. 创建时间(entrydate)在开始(begin)和结束(end)的时间之内
  2. 根据更新时间进行倒序排列
  3. concat:用于拼接字符串,使得 name 在模糊查询时可以使用 %name%。若不使用 concat,则需使用 %${name}%,这样会导致 name 字段不是用预编译 SQL 处理的,缺点是性能低、不安全(有 SQL 注入的风险)

XML 映射文件的增删改查操作(更灵活)

映射文件规范:三个一致

  • XML 映射文件名称与Mapper 接口的文件名保持一致(同包同名)
  • XML 映射文件的 namespace 属性与Mapper 接口名保持一致
  • XML 映射文件的 SQL 语句的 id与Mapper 接口中的方法名保持一致

创建 XML 文件

  • 右键点击 resources 文件 → New → Directory(目录)

  • 创建目录时用 / 分割,如 com/example/gymsystem

  • 创建文件,XML 文件名与要映射的 Mapper 保持一致

  • 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">
  • XML 书写 mapper 标签大致如下:

    • namespace 为对应 mapper 的全类名,如 com.example.managementsystem.mapper.entityMapper
    • parameterType 用于指定 SQL 语句中参数的类型,MyBatis 会根据这个类型来解析传入的参数,并将其映射到 SQL 语句中
      • 如果参数是一个简单的类型(如 int、String、Long 等),可以直接指定对应的 Java 类型
      • 如果参数是一个复杂的对象(如 POJO 或 Map),则需要指定该对象的全限定类名
    <mapper namespace="com.example.managementsystem.mapper.entityMapper">
        <update id="updateUser" parameterType="com.example.managementsystem.pojo.entityData">
            update ebms
            <set>
                <if test="EBMSNameSpz != null">EBMS_name_spz = #{EBMSNameSpz},</if>
                <if test="EBMSTypeSpz != null">EBMS_type_spz = #{EBMSTypeSpz},</if>
                <if test="EBMSDescSpz != null">EBMS_desc_spz = #{EBMSDescSpz},</if>
                <if test="EBMSStatusSpz != null">EBMS_status_spz = #{EBMSStatusSpz},</if>
            </set>
            WHERE EBMS_id_spz = #{EBMSIdSpz}
        </update>
    
        <delete id="deleteByIDs">
            delete from ebms where EBMS_id_spz in
            <foreach collection="ids" item="id" separator="," open="(" close=")">
                #{id}
            </foreach>
        </delete>
    </mapper>

XML 查询操作

  • resultType:指的是查询操作过后,需要返回的数据中单条记录所需要封装类型的全类名(全限定类名),即 resultType 返回的数据类型与接口返回的数据类型是一致的。
  • id 属性:指的是 mapper 中方法的名称。
<select id="selectEntity" resultType="com.example.managementsystem.pojo.entityData">
        select * from ebmsdb.ebms
        <where>
            <if test="name != null">EBMS_name_spz like concat('%',#{name},'%')</if>
            <if test="type != null">and EBMS_type_spz = #{type}</if>
            <if test="desc != null">and EBMS_desc_spz like concat('%',#{desc},'%')</if>
            <if test="status != null">and EBMS_status_spz = #{status}</if>
        </where>
        order by EBMS_id_spz desc
</select>

MyBatis 动态 SQL

<if> 标签

用于判断条件是否成立。使用 test 属性进行条件判断,若条件为 true,则拼接 SQL。

<if test="name != null">
    name like concat('%',#{name},'%')
</if>

<where> 标签

  1. 生成 where 关键字:用来代替 SQL 语句中的 where,它会根据其内部的子标签动态地判断里面的条件——若里面所有的条件都不成立,它就不会生成 where 这个 SQL 关键字;若里面至少有一个条件成立,它就会生成关键字。
  2. 自动去除条件前多余的 and 或者 or 关键字。
<select id="selectEntity" resultType="com.example.managementsystem.pojo.entityData">
    select * from ebmsdb.ebms
    <where>
        <if test="name != null">EBMS_name_spz like concat('%',#{name},'%')</if>
        <if test="type != null">and EBMS_type_spz = #{type}</if>
        <if test="desc != null">and EBMS_desc_spz like concat('%',#{desc},'%')</if>
        <if test="status != null">and EBMS_status_spz = #{status}</if>
    </where>
    order by EBMS_id_spz desc
</select>

<set> 标签

与 where 标签相同,用来替换 SQL 语句中的 set 关键字,还能自动删除 update 语句中额外的逗号。

<update id="updateUser" parameterType="com.example.managementsystem.pojo.entityData">
    update ebms
    <set>
        <if test="EBMSNameSpz != null">EBMS_name_spz = #{EBMSNameSpz},</if>
        <if test="EBMSTypeSpz != null">EBMS_type_spz = #{EBMSTypeSpz},</if>
        <if test="EBMSDescSpz != null">EBMS_desc_spz = #{EBMSDescSpz},</if>
        <if test="EBMSStatusSpz != null">EBMS_status_spz = #{EBMSStatusSpz},</if>
    </set>
    WHERE EBMS_id_spz = #{EBMSIdSpz}
</update>

<foreach> 标签

属性:

  • collection:集合名称
  • item:集合遍历出来的元素/项
  • separator:每一次遍历使用的分隔符
  • open:遍历开始前拼接的片段
  • close:遍历结束后拼接的片段

SQL 语句:

delete from ebms where EBMS_id_spz in (1,2,3)

接口方法:

public void deleteByIds(List<Integer> ids); // 批量删除

XML 映射文件:

<delete id="deleteByIDs">
    delete from ebms where EBMS_id_spz in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

<sql> 标签与 <include> 标签

两个标签结合起来使用:

  • <sql>:用于公共 SQL 片段的抽取,定义可重用的 SQL 片段。
  • <include>:负责 SQL 片段的引用,通过属性 refid 指定包含的 SQL 片段。

数据库连接池

  • Hikari(追光者):属于 SpringBoot 自带的连接池技术。
  • Druid(德鲁伊):阿里巴巴开源的数据库连接池项目。
  • Druid(德鲁伊)、Hikari(追光者)是目前市面项目中使用最多的连接池技术。

lombok

可用于简化实体类的代码量(如自动生成 getter/setter、构造方法等)。

小结

这篇笔记整理了 Spring Boot 工程的核心骨架:分层架构、IOC 注解、Restful 接口、参数接收,以及 MyBatis 的预编译、增删改查、XML 映射和动态 SQL。下一部分继续整理 SpringMVC、AOP 与声明式事务。