Spring Boot 核心笔记:三层架构、IOC 与 MyBatis
学习 Spring Boot 时整理的实战笔记:三层架构、IOC 容器注解、Restful 风格、参数接收,以及 MyBatis 的预编译、增删改查与动态 SQL。
这篇文章的目录⌄
学习 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 才会自动封装。
解决方法(有三种):
- 起别名
- 手动结果映射
- 开启驼峰命名(在 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")
表示根据用户姓名(模糊匹配)、性别、创建时间进行查询:
- 创建时间(entrydate)在开始(begin)和结束(end)的时间之内
- 根据更新时间进行倒序排列
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.entityMapperparameterType用于指定 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> 标签
- 生成 where 关键字:用来代替 SQL 语句中的 where,它会根据其内部的子标签动态地判断里面的条件——若里面所有的条件都不成立,它就不会生成 where 这个 SQL 关键字;若里面至少有一个条件成立,它就会生成关键字。
- 自动去除条件前多余的
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 与声明式事务。