初始MyBatis
MyBatis 核心概念
- ORM 工具: Object-Relational Mapping (对象关系映射) 工具。
- 半自动化: 开发者需要手动编写 SQL 和结果映射,MyBatis 负责执行 SQL 并将结果集转换为对象。
- 轻量级: 框架本身小巧,依赖少,学习曲线相对平缓,易于集成。
Spring Boot 整合 MyBatis 步骤
-
添加依赖:
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>{latest-version}</version> <!-- 替换为实际版本号 --> </dependency>
-
配置 (application.yml / application.properties):
mybatis:# 配置 XML Mapper 文件的位置mapper-locations: classpath:mapper/*.xml# 配置实体类别名所在的包 (可选,简化 XML 中类型写全限定类名)type-aliases-package: com.example.demo.model# 开启驼峰命名自动映射 (数据库下划线字段 -> Java 驼峰属性)configuration:map-underscore-to-camel-case: true # 配置数据源 (通常使用 spring.datasource.*) spring:datasource:url: jdbc:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTCusername: rootpassword: your_passworddriver-class-name: com.mysql.cj.jdbc.Driver
-
编写 Mapper 接口:
- 定义数据库操作方法。
- 在接口上添加
@Mapper
注解,标记该接口为 MyBatis Mapper。 - (替代方案:) 在启动类上添加
@MapperScan("com.example.demo.mapper")
扫描指定包下的所有 Mapper 接口,避免在每个接口上加@Mapper
。
-
编写 SQL 映射文件 (Mapper XML):
- 通常放在
resources/mapper/
目录下。 - 文件名建议与 Mapper 接口名一致 (如
UserMapper.xml
)。 - 在 XML 文件中编写动态 SQL (
<select>
,<insert>
,<update>
,<delete>
等标签)。 - 关键: 每个 SQL 语句标签的
id
属性必须与其对应的 Mapper 接口方法名严格一致。
- 通常放在
-
工具推荐 (可选但实用):
- 安装 MyBatisX 插件 (IDEA/Eclipse),提供强大的 Mapper 接口与 XML 文件间跳转、代码提示、生成等功能。
动态 SQL 参数使用
- 核心机制: 使用 OGNL (Object-Graph Navigation Language) 表达式,通过反射机制获取参数对象的属性值。
- 参数传递方式:
- 单个参数 (JavaBean 或 Map 类型):
- 在 XML 中直接通过属性名 (JavaBean) 或键名 (Map) 访问。
- 示例 (JavaBean):
#{propertyName}
- 示例 (Map):
#{key}
- 多个参数:
- 默认方式 (不推荐): MyBatis 会将多个参数放入一个 Map 中,键为
param1
,param2
, … 或arg0
,arg1
, …。在 XML 中使用#{param1}
,#{param2}
等访问。 - 推荐方式 -
@Param
注解: 在 Mapper 接口方法参数前使用@Param("name")
注解显式指定参数名称。User selectUser(@Param("username") String username, @Param("status") Integer status);
- 在 XML 中通过
@Param
指定的名称访问:#{username}
,#{status}
。
- 默认方式 (不推荐): MyBatis 会将多个参数放入一个 Map 中,键为
- 单个参数 (JavaBean 或 Map 类型):
- 重要规则: 如果使用了
@Param
注解指定了参数名,必须在 XML 中使用该指定的名称引用参数。
模型查询示例 (模糊查询)
-
安全方式 (推荐 - 使用
#{}
和<bind>
或 CONCAT):<!-- 方式1: 使用 <bind> 预拼接 --> <select id="findByName" resultType="User"><bind name="nameLike" value="'%' + name + '%'" />SELECT * FROM userWHERE name LIKE #{nameLike} </select><!-- 方式2: 使用数据库 CONCAT 函数 (数据库相关) --> <select id="findByName" resultType="User">SELECT * FROM userWHERE name LIKE CONCAT('%', #{name}, '%') </select>
- 优点: 使用
#{}
是预编译的,能有效防止 SQL 注入。<bind>
或CONCAT
在 SQL 执行前完成字符串拼接。
- 优点: 使用
-
不安全方式 (不推荐 - 使用
${}
直接拼接):<select id="findByName" resultType="User">SELECT * FROM userWHERE name LIKE '%${name}%' </select>
- 缺点: 使用
${}
是字符串替换,直接将参数值嵌入 SQL 语句。如果name
来自用户输入且未经过滤,存在严重的 SQL 注入风险。应避免使用。
- 缺点: 使用
手动映射 (Result Maps)
-
类型处理器 (Type Handler):
- 用于处理 Java 类型和 JDBC 类型之间的转换。
- MyBatis 内置了常用类型的处理器 (如 String, Integer, Date 等)。
- 可自定义处理器来处理特殊类型 (如枚举、自定义对象、数据库特定类型如 PostgreSQL 的
jsonb
)。
-
关联查询 (Result Maps with Associations/Collections):
- 一对一 (
<association>
): 表示一个对象嵌套另一个对象。<resultMap id="orderWithUserMap" type="Order"><id property="id" column="order_id"/><!-- ... other order columns ... --><association property="user" javaType="User"><id property="id" column="user_id"/><result property="username" column="username"/><!-- ... other user columns ... --></association> </resultMap>
- 一对多 (
<collection>
): 表示一个对象包含一个集合 (通常是另一个对象的列表)。<resultMap id="userWithOrdersMap" type="User"><id property="id" column="user_id"/><!-- ... other user columns ... --><collection property="orders" ofType="Order"><id property="id" column="order_id"/><!-- ... other order columns ... --></collection> </resultMap>
- 一对一 (
解决 N+1 查询性能问题
关联查询(尤其是一对多)容易引发 N+1 问题(查询主对象 1 次,再查询每个主对象的关联对象 N 次)。MyBatis 提供以下机制优化:
-
一级缓存 (SqlSession 级别):
- 在同一个 SqlSession 会话内有效。
- 执行相同的 SQL 查询(相同的 SQL 语句和参数)时,会直接返回缓存的结果,避免再次访问数据库。
- 作用范围小:SqlSession 关闭或执行了更新操作(INSERT/UPDATE/DELETE)、调用
clearCache()
或设置了不同的环境,缓存会失效。
-
延迟加载 (懒加载 - Lazy Loading):
创建实体类的子类,每次在子类上进行操作,所以要加上@JsonIgnoreProperties("handler")
来防止子类中的handler无法序列化,导致无法进行网络传输给前端。- 核心思想:用到时才加载。查询主对象时,不立即查询其关联对象。只有当代码真正访问关联对象(或集合)的属性时,MyBatis 才发出 SQL 查询加载关联数据。
- 配置: 在
mybatis.configuration
或全局<settings>
中开启:mybatis:configuration:lazy-loading-enabled: true # 开启全局延迟加载aggressive-lazy-loading: false # 禁用"侵略性"延迟加载 (重要)
- 注意: 每次触发关联对象的延迟加载,MyBatis 会使用一个全新的 SqlSession (除非配置了其他拦截机制)。这意味着:
- 一级缓存无法跨延迟加载的查询生效(因为每次是新会话)。
- 延迟加载操作需要确保新的 SqlSession 能正确获取(例如在事务管理下或使用
OpenSessionInView
模式 - 后者需谨慎评估)。
-
二级缓存 (Application 级别 / Mapper 级别):
- 作用范围比一级缓存大,在整个应用或同一个 Mapper 内有效(跨 SqlSession)。
- 开启步骤:
-
全局开关 (通常默认开启): 确保
mybatis.configuration.cache-enabled=true
(默认值通常就是 true)。 -
序列化支持 (可选但推荐): 如果缓存的对象需要序列化(例如使用分布式缓存),确保它们实现了
Serializable
接口。 -
Mapper XML 中声明:
-
缓存行为控制 (在 SQL 标签上):
<select>
标签:设置useCache="true"
(默认) 表示该查询结果可存入二级缓存;useCache="false"
表示不缓存。<insert>
,<update>
,<delete>
标签:执行后会**自动刷新(清空)**其所属 Mapper 的二级缓存 (flushCache="true"
是默认值)。设置为flushCache="false"
可阻止刷新(慎用,可能导致脏读)。
-
- 特点:
- 作用域大: 数据在整个应用运行期间(或 Mapper 作用域内)有效。
- 需要管理: 需注意缓存一致性(更新操作会自动刷新对应 Mapper 缓存),以及缓存策略(大小、过期时间等 - 需要集成第三方缓存如 Ehcache, Redis 来实现更复杂策略)。
关于 @RestController
@RestController
是@Controller
和@ResponseBody
的组合注解。- 作用: 标记一个类为 Web 控制器 (Controller)。
- 关键特性: 该注解下的所有方法默认都添加了
@ResponseBody
注解。这意味着方法的返回值会直接写入 HTTP 响应体中(通常被序列化为 JSON/XML),而不是被解析为视图名称去寻找模板页面。这是构建 RESTful Web 服务的标准方式。