当前位置: 首页 > news >正文

框架-MyBatis|Plus-1

1. 什么是MyBatis?

MyBatis 是一款优秀的 Java 持久层框架,最初由 Apache 的开源项目 iBatis 演变而来,2010 年迁移至 Google Code 并更名为 MyBatis,2013 年 11 月迁移到 GitHub.以下是其核心特点与功能:

①. 核心功能‌
SQL 映射‌:通过 XML 或注解配置 SQL 语句,将 Java 对象与数据库记录映射,避免手动编写 JDBC 代码
‌动态 SQL‌:支持条件分支(如 、)、循环()等标签,灵活构建复杂查询
执行器机制‌:提供 SimpleExecutor(默认)、ReuseExecutor(重用预处理语句)、BatchExecutor(批处理)三种执行器,优化性能

②. 优势‌
‌灵活性‌:开发者可直接编写原生 SQL,精准控制查询逻辑与性能
轻量级‌:仅封装 JDBC 核心流程(如连接管理、结果映射),不强制使用特定数据库模式
集成便捷‌:与 Spring、Spring Boot 等框架无缝整合

‌③. 与 JDBC 的关系‌
MyBatis 是对 JDBC 的高级封装,简化了驱动加载、连接池管理、参数绑定等繁琐操作,但仍需开发者编写 SQL 语句,属于“半自动化” ORM 框架

④. 适用场景‌
适合需要复杂 SQL 优化或对数据库操作有精细控制的项目,但对完全自动化 ORM(如 Hibernate)或简单 CRUD 场景可能略显冗余

其核心组件包括 SqlSessionFactory(管理数据库会话)、Mapper 接口(动态代理执行 SQL)、以及一级/二级缓存机制

2. Mybaits 的优点 & 缺点

  1. MyBatis 的优点
    ①. 简化 JDBC 操作‌
    减少重复代码,自动处理资源关闭和异常捕获,降低内存泄漏风险
    相比 JDBC,代码量减少 50% 以上
    ②. SQL 可控性高‌
    直接编写原生 SQL,支持复杂查询优化,适合高性能场景
    SQL 与代码分离(XML 或注解),降低耦合度,便于维护
    ③. 动态 SQL 支持‌
    通过 、、 等标签灵活生成条件查询,减少冗余代码
    ④. 轻量级与易集成‌
    启动快、内存占用低,与 Spring、Spring Boot 无缝整合
    不强制改变现有数据库设计,侵入性低
    ⑤. 缓存机制‌
    一级缓存(SqlSession 级别)默认开启,二级缓存(命名空间级别)可配置,提升查询性能
    ⑥. 兼容性‌
    支持所有 JDBC 兼容的数据库,但需注意 SQL 语法差异

  2. MyBatis 的缺点
    ①. SQL 编写与维护成本高‌
    需手动编写 SQL,对复杂查询或多表关联时工作量较大
    数据库移植性差,更换数据库需调整 SQL 语法
    ‌②. 学习曲线较陡‌
    需掌握 SQL 和 MyBatis 配置,比全自动 ORM(如 Hibernate)学习成本高
    ‌③. 配置繁琐‌
    XML 配置文件可能冗长,尤其在大型项目中难以维护
    ④. ORM 功能有限‌
    缺乏自动关联映射,复杂对象关系(如一对多)需手动处理
    类型安全和映射错误仅在运行时暴露
    ⑤. 性能优化依赖手动‌
    缓存策略配置复杂,分布式环境下一致性难保证

  3. 适用场景
    需精细控制 SQL 或高性能查询的互联网项目
    不适合快速 CRUD 开发或对 SQL 不熟悉的团队

3. MyBatis,MyBatis Plus 与 JPA 有哪些不同?

MyBatis、MyBatis-Plus 和 JPA 是 Java 生态中三种主流的持久层技术,它们在设计理念、功能特性和适用场景上有显著差异。以下是核心对比:

  1. 设计理念‌
    MyBatis‌:
    ‌半自动化 ORM‌:开发者需手动编写 SQL,通过 XML 或注解配置映射关系,强调 SQL 可控性
    ‌轻量级‌:仅封装 JDBC 核心流程,不强制对象关系映射
    ‌MyBatis-Plus‌:
    ‌MyBatis 增强工具‌:在 MyBatis 基础上提供通用 CRUD、条件构造器、分页插件等,简化单表操作
    ‌保留 SQL 灵活性‌:支持原生 SQL 与动态查询构造器(如 QueryWrapper)
    ‌JPA‌:
    ‌全自动化 ORM 规范‌:基于对象模型自动生成 SQL,支持 JPQL(面向对象查询语言)
    ‌标准化‌:作为 Java EE 规范,Hibernate 是其主流实现

  2. 核心功能对比‌
    在这里插入图片描述

  3. 优缺点分析‌
    ‌MyBatis‌
    ‌优点‌:SQL 灵活性强,适合复杂查询优化;性能接近原生 JDBC
    ‌缺点‌:需维护大量 XML 文件;多表关联需手动处理
    ‌MyBatis-Plus‌
    ‌优点‌:简化 CRUD 开发;保留 MyBatis 的 SQL 控制权
    ‌缺点‌:复杂查询仍需手写 SQL;数据库移植性差
    ‌JPA‌
    ‌优点‌:开发效率高(如自动生成表结构);跨数据库兼容性好
    ‌缺点‌:复杂查询性能较差(如 N+1 问题);学习曲线陡峭

  4. 适用场景‌
    ‌MyBatis‌:需要精细控制 SQL 或处理高性能查询(如报表系统)
    ‌MyBatis-Plus‌:快速开发单表 CRUD 为主的项目(如管理后台)
    ‌JPA‌:企业级应用,强调标准化和跨数据库兼容性(如微服务架构)

  5. 补充差异‌
    ‌事务管理‌:JPA 提供声明式事务(如 @Transactional),MyBatis 需结合 Spring 实现
    ‌扩展性‌:MyBatis-Plus 插件体系(如分页、乐观锁)更丰富,JPA 依赖 Hibernate 扩展。

可根据项目需求混合使用,例如 JPA 处理简单 CRUD,MyBatis 处理复杂查询

附录1:为什么说 Mybatis 是半ORM 映射工具?

MyBatis 被称为半 ORM(对象关系映射)工具,主要因其在数据库操作中结合了 ORM 的部分特性与手动 SQL 控制的灵活性。以下是核心原因及与全自动 ORM 的区别:

  1. SQL 控制权差异‌
    ‌半自动性‌:MyBatis 要求开发者手动编写 SQL 语句,通过 XML 或注解配置映射关系,而非自动生成 SQL。这种设计保留了 SQL 的精确控制权,适合需要优化查询性能的场景
    ‌全自动 ORM(如 Hibernate)‌:完全封装 SQL 生成,开发者仅需操作对象模型,关联查询通过配置(如 @OneToMany)自动完成,但可能牺牲 SQL 优化空间
  2. 映射功能范围‌
    ‌MyBatis‌:提供基础的对象-字段映射(如 resultMap),但关联查询(如一对多)需手动编写 SQL 实现,属于“部分映射”
    ‌全自动 ORM‌:自动处理对象间的级联关系(如懒加载),无需显式编写关联查询 SQL
  3. 灵活性与复杂度权衡‌
    ‌MyBatis‌:轻量级,学习门槛低,适合需要动态 SQL 或复杂查询优化的项目(如互联网应用)
    ‌全自动 ORM‌:简化 CRUD 操作,但框架较重,生成的 SQL 可能不够高效,且对数据库移植性要求高时更适用
  4. ‌典型场景对比‌
    在这里插入图片描述
    综上,MyBatis 的“半 ORM”特性体现在其 ‌手动 SQL 与部分映射的结合‌,既简化了 JDBC 的冗余操作,又避免了全自动 ORM 的过度封装

4. 模糊查询 like 语句该怎么写?

在MyBatis中,要执行模糊查询(使用LIKE语句),你可以使用SQL语句的字符串拼接或使用动态SQL来构建查询语句。下面我将为你展示两种常用的方式。
假设你要在一个查询中执行模糊查询,搜索用户的用户名包含特定关键字的情况。

  1. 字符串拼接方式:
<select id="searchUsers" resultMap="userResultMap">SELECT * FROM usersWHERE username LIKE CONCAT('%', #{keyword}, '%')
</select>

在这个例子中,#{keyword}是参数占位符,表示要搜索的关键字。CONCAT(‘%’, #{keyword}, ‘%’)用于构建模糊匹配的字符串
2. 动态SQL方式:

<select id="searchUsers" resultMap="userResultMap">SELECT * FROM users<where><if test="keyword != null">AND username LIKE CONCAT('%', #{keyword}, '%')</if></where>
</select>

在这个例子中,使用了标签来创建动态条件。只有在keyword参数不为null时,才会添加AND username LIKE CONCAT(‘%’, #{keyword}, ‘%’)这个条件到查询语句中。
无论哪种方式,你都可以在SQL语句中使用LIKE关键字来实现模糊查询,然后通过#{}语法或动态SQL的方式将参数值插入到查询语句中。选择哪种方式取决于你的项目需求和团队的偏好。

附录1: mybatis使用like的时候,如何防止sql注入

在 MyBatis 中使用 LIKE 查询时,
错误示例:

@Select("SELECT * FROM users WHERE name LIKE '%${name}%'")
List<User> findUsersByName(@Param("name") String name);

问题:
使用 ${} 会直接将参数插入到 SQL 中,而不会进行转义。
-如果用户输入了恶意内容(如 '; DROP TABLE users; --),可能导致 SQL 注入。

防止 SQL 注入是一个重要的安全问题。SQL 注入通常发生在用户输入的内容直接拼接到 SQL 语句中,而没有进行适当的处理。以下是防止 SQL 注入的几种方法:

  1. 使用 MyBatis 的参数占位符+CONCAT动态拼接
    MyBatis 提供了参数占位符(#{}),可以自动对参数进行转义,防止 SQL 注入。
<select id="findUsersByName" resultType="User">SELECT * FROM usersWHERE name LIKE CONCAT('%', #{name}, '%')
</select>

Java 调用:

List<User> users = userMapper.findUsersByName("John");
  1. 手动拼接 % 并使用参数占位符
    在 Java 代码中手动拼接 %,然后将拼接后的字符串作为参数传递给 MyBatis。
String name = "%John%";
List<User> users = userMapper.findUsersByName(name);

XML 配置:

<select id="findUsersByName" resultType="User">SELECT * FROM usersWHERE name LIKE #{name}
</select>
  1. 使用 MyBatis 的 bind 标签
    MyBatis 的 bind 标签可以在 SQL 中动态绑定变量,同时防止 SQL 注入。
<select id="findUsersByName" resultType="User"><bind name="pattern" value="'%' + name + '%'" />SELECT * FROM usersWHERE name LIKE #{pattern}
</select>

解释:

  • <bind> 标签将用户输入的 name 参数动态绑定为 pattern,并拼接 %
  • #{pattern} 会自动转义,防止 SQL 注入。
  1. 使用MyBatis Plus 的 QueryWrapper
    如果需要更复杂的查询,可以结合 MyBatis 和其他工具(如 MyBatis Plus 或 QueryWrapper)来构建安全的查询。
    示例:
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", name);
List<User> users = userMapper.selectList(queryWrapper);

解释:
MyBatis Plus 的 QueryWrapper 会自动处理参数,防止 SQL 注入。

  1. 输入校验
    除了使用 MyBatis 的安全特性,还可以对用户输入进行校验,确保输入内容符合预期。
    示例:
  • 检查输入是否包含非法字符(如 %_ 等)。
  • 对输入进行长度限制,避免过长的字符串。
    示例代码:
public String sanitizeInput(String input) {if (input == null) {return null;}// 去除特殊字符return input.replaceAll("[^a-zA-Z0-9]", "");
}

通过这些方法,可以有效防止 SQL 注入,同时保证查询的安全性和可靠性。

5. #{}和${}的区别是什么?

在MyBatis中,#{}和${}都是用于参数替换的标记,用于将参数值插入到SQL语句中。然而,它们在处理参数值的方式上有一些重要的区别。
1 #{}(预编译):
#{}是用于预编译的参数标记。当使用#{}时,MyBatis会将参数值放入一个预编译的PreparedStatement中,并确保参数值被正确地转义和引用,从而防止SQL注入攻击。
#{}适用于大多数情况,尤其是当参数值是从用户输入中获得时,因为它提供了更好的安全性和可靠性。
示例:

SELECT * FROM users WHERE id = #{userId}

2 ${}(字符串替换):
是用于字符串替换的参数标记。当使用{}是用于字符串替换的参数标记。当使用是用于字符串替换的参数标记。当使用{}时,MyBatis会直接将参数值嵌入到SQL语句中,不会进行预编译或转义。这可能导致潜在的安全问题,如果不正确地处理参数值,可能会导致SQL注入攻击
${}适用于一些特殊情况,例如在动态表名、列名或函数调用等情况下,但要谨慎使用,确保参数值的安全性。
示例:

SELECT * FROM ${tableName} WHERE id = ${userId}

总结区别:
#{}用于预编译,提供参数安全性,适合大多数情况。
而$ {}用于字符串替换,潜在安全风险较高,仅在特定情况下使用,确保参数值安全。
在实际使用中,推荐优先使用#{}来处理参数,以确保数据库操作的安全性和可靠性。只有在确保参数值不会引发安全问题的情况下,才应该考虑使用${}。
补充:
1、#{} 是预编译处理,是占位符。 ${} 是字符串替换,是拼接符。
2、mybatis 在处理 #{} 时,会将SQL中的 #{} 替换为 ? 号,调用 PrepareStatement 来赋值。
3、mybatis 再处理 ${} 时,会将SQL中的 ${} 替换成变量的值,调用 Statement 来赋值。
4、使用 #{} 可以有效防止SQL注入,提高系统安全性。
5、动态调整查询列,排序列使用 ${}

6、MyBatis中的动态SQL是什么?

MyBatis是一个流行的Java持久化框架,它允许你将数据库查询语句与Java代码分离,使得代码更加清晰易读。动态SQL是MyBatis中一个强大的特性,它允许你根据不同的条件在运行时构建不同的SQL查询语句。

举个例子来说明动态SQL的概念。假设你有一个搜索页面,用户可以根据不同的条件来搜索商品,比如商品名、价格范围和分类。使用动态SQL,你可以构建一个灵活的查询语句,只在用户提供相关条件时包含这些条件。
在MyBatis中,你可以使用、、、等标签来构建动态SQL。以下是一个简单的例子,假设你要根据用户的选择来动态构建查询语句:

<select id="searchProducts" resultType="Product">SELECT * FROM products<where><if test="productName != null">AND name = #{productName}</if><if test="minPrice != null">AND price >= #{minPrice}</if><if test="maxPrice != null">AND price <= #{maxPrice}</if><if test="category != null">AND category = #{category}</if></where>
</select>

7. MyBatis中XML映射有哪些标签?

MyBatis 的 XML 映射文件包含多种标签,主要用于 SQL 定义、动态 SQL、结果映射和缓存配置等。以下是主要标签分类及功能说明:

  1. SQL 定义类标签‌

  2. 结果映射类标签‌
    在这里插入图片描述

  3. 动态 SQL 标签‌
    在这里插入图片描述
    ‌4. 基础 CRUD 标签‌
    在这里插入图片描述
    ‌5. 其他功能标签‌
    在这里插入图片描述
    ‌完整标签体系总结‌
    在这里插入图片描述
    以上标签可通过组合使用实现灵活的数据操作,例如通过 处理复杂对象关系,或通过动态 SQL 标签实现条件查询

<?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.example.mapper.UserMapper"><!-- 1. 可复用SQL片段 --><sql id="baseColumns">id, username, email, create_time</sql><!-- 2. 基础结果映射 --><resultMap id="userResultMap" type="com.example.model.User"><id property="id" column="id"/><result property="username" column="username"/><result property="email" column="email"/><result property="createTime" column="create_time"/></resultMap><!-- 3. 关联查询结果映射 --><resultMap id="userWithOrdersMap" type="com.example.model.User" extends="userResultMap"><collection property="orders" ofType="com.example.model.Order"><id property="orderId" column="order_id"/><result property="amount" column="amount"/></collection></resultMap><!-- 4. 查询操作 --><select id="selectById" resultMap="userResultMap">SELECT <include refid="baseColumns"/>FROM usersWHERE id = #{id}</select><!-- 5. 动态SQL查询 --><select id="selectByCondition" resultMap="userResultMap">SELECT <include refid="baseColumns"/>FROM users<where><if test="username != null">AND username LIKE CONCAT('%', #{username}, '%')</if><if test="email != null">AND email = #{email}</if><choose><when test="createTimeStart != null and createTimeEnd != null">AND create_time BETWEEN #{createTimeStart} AND #{createTimeEnd}</when><when test="createTimeStart != null">AND create_time >= #{createTimeStart}</when></choose></where>ORDER BY id DESC</select><!-- 6. 插入操作(返回自增ID) --><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">INSERT INTO users(username, email, create_time)VALUES(#{username}, #{email}, #{createTime})</insert><!-- 7. 批量插入 --><insert id="batchInsert">INSERT INTO users(username, email, create_time)VALUES<foreach collection="list" item="user" separator=",">(#{user.username}, #{user.email}, #{user.createTime})</foreach></insert><!-- 8. 更新操作 --><update id="updateUser">UPDATE users<set><if test="username != null">username = #{username},</if><if test="email != null">email = #{email},</if></set>WHERE id = #{id}</update><!-- 9. 删除操作 --><delete id="deleteById">DELETE FROM users WHERE id = #{id}</delete><!-- 10. 缓存配置 --><cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
</mapper>

8. MyBatis|MyBatis plus中的分页如何实现?

MyBatis-Plus 分页功能实现步骤如下:
1‌. 配置分页插件‌
在 Spring Boot 配置类中添加分页拦截器:
需指定数据库类型(如 DbType.MYSQL)

@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}

2‌. 基础分页查询‌
使用 Page 对象和 selectPage 方法:

// 创建分页对象(当前页=1,每页条数=10)
Page<User> page = new Page<>(1, 10);
// 执行分页查询(第二个参数为查询条件,可为null)
IPage<User> userPage = userMapper.selectPage(page, null);
// 获取分页结果
List<User> records = userPage.getRecords();  // 当前页数据
long total = userPage.getTotal();            // 总记录数

‌3. 带条件的分页查询‌
结合 QueryWrapper 或 LambdaQueryWrapper:

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(User::getName, "张");  // 条件:name包含"张"
Page<User> page = new Page<>(1, 10);
IPage<User> result = userMapper.selectPage(page, wrapper);

‌4. 自定义 SQL 分页‌
若需复杂 SQL,可在 Mapper 接口定义方法并配合 XML/注解:

// Mapper 接口
IPage<User> selectUserByCustomPage(IPage<User> page, @Param("status") Integer status);

XML 映射文件:

<select id="selectUserByCustomPage" resultType="User">SELECT * FROM user WHERE status = #{status}
</select>

附录1:

基础的分页查询:
< select id=“getPagedData” resultType=“YourResultType”>
SELECT * FROM your_table
LIMIT #{offset}, #{limit}
< /select>
~
MyBatis在数据库查询中执行分页操作时,通常会使用分页插件来处理。分页插件能够根据数据库的不同,生成适当的分页查询语句,并将查询结果进行分页处理。下面我将解释MyBatis如何进行分页以及分页插件的一般原理。
~
MyBatis的分页原理:
1 数据库方言(Dialect)
不同的数据库(如MySQL、Oracle、SQL Server等)在分页查询语法上有所不同。MyBatis并不直接支持所有数据库的分页语法,而是通过数据库方言来处理。数据库方言是一个抽象层,它根据数据库类型生成相应的分页查询语句。
2 参数传递
在查询方法中,你可以传递分页相关的参数,如页码(pageNumber)和每页条数(pageSize)。
3 分页处理
MyBatis会根据数据库方言生成合适的分页查询语句,然后将查询结果返回给调用者。通常,MyBatis会添加类似LIMIT(对于MySQL)或ROWNUM(对于Oracle)等语句来限制返回的结果行数。
~
分页插件的原理:
分页插件是一种扩展机制,它允许MyBatis在查询过程中,自动应用分页逻辑而不需要手动编写分页查询语句。分页插件的一般原理如下:
1 拦截器(Interceptor)
分页插件实际上是MyBatis的一个拦截器,它可以在查询被执行之前或之后进行干预。
2 处理分页逻辑:
在查询执行之前,分页插件会检测是否有分页参数传入。如果有分页参数,插件会根据数据库方言生成适当的分页查询语句。
3 修改查询参数:
插件会修改查询的SQL语句,添加分页的限制条件。同时,它还会修改参数对象,将分页参数替换为实际的分页偏移量(offset)和每页条数(limit)。
4 执行查询:
修改后的查询语句被执行,得到查询结果。
5 封装分页结果:
插件会根据查询结果和分页参数,将查询结果进行切割,得到分页后的结果。
~
分页插件的使用可以大大简化代码,使得在查询中不必关注分页逻辑,只需传递分页参数即可。一些常用的分页插件包括MyBatis-Paginator、PageHelper等,它们都基于上述原理来实现分页功能。不同的插件可能有不同的配置方式和特性,具体的使用方法和配置请参考各个插件的文档。
~
常用的分页插件和技巧:
1 PageHelper 插件PageHelper 是一个流行的 MyBatis 分页插件,它简化了分页查询的操作。你只需要在查询方法前调用 PageHelper.startPage(pageNum, pageSize),然后执行查询语句,PageHelper 就会自动处理分页逻辑。

@Configuration
public class MyBatisConfig {@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);// 配置PageHelper插件Properties properties = new Properties();properties.setProperty("helperDialect", "mysql");properties.setProperty("reasonable", "true");PageInterceptor pageInterceptor = new PageInterceptor();pageInterceptor.setProperties(properties);sessionFactory.setPlugins(new Interceptor[]{pageInterceptor});return sessionFactory.getObject();}
}@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic PageInfo<User> getUsers(@RequestParam(defaultValue = "1") int pageNum,@RequestParam(defaultValue = "10") int pageSize) {return userService.getUsersByPage(pageNum, pageSize);}
}@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);List<User> users = userMapper.selectAllUsers();return new PageInfo<>(users);}
}public interface UserMapper {@Select("SELECT * FROM users")List<User> selectAllUsers();
}

2 使用 RowBounds: 在 MyBatis 中,你还可以使用 RowBounds 对象来实现分页查询。通过在查询方法中传递一个 RowBounds 对象,你可以指定从哪一行开始取数据,以及每页显示多少条数据。

import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/users")
public class UserController {@Autowiredprivate UserService userService;@GetMappingpublic List<User> getUsers(@RequestParam(defaultValue = "1") int page,@RequestParam(defaultValue = "10") int size) {return userService.getUsersByPage(page, size);}
}import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {@Autowiredprivate SqlSession sqlSession;public List<User> getUsersByPage(int currentPage, int pageSize) {int offset = (currentPage - 1) * pageSize;RowBounds rowBounds = new RowBounds(offset, pageSize);UserMapper userMapper = sqlSession.getMapper(UserMapper.class);return userMapper.selectAllUsers(rowBounds);}
}import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {@Select("SELECT * FROM users")List<User> selectAllUsers();
}public class User {private Long id;private String name;private Integer age;// Getters and Setters
}

3 自定义分页插件: 如果你有特殊的分页需求,你可以编写自己的分页插件。这可能涉及到在 MyBatis 的拦截器链中插入你自己的逻辑,以实现定制化的分页处理。

9.Mybatis如何返回插入的主键ID

在 MyBatis 中,当向 MySQL 数据库插入数据时,如果需要返回数据库自动生成的自增主键(如 AUTO_INCREMENT 的 ID),可以通过以下两种方式实现:

‌方法 1:使用 useGeneratedKeys 和 keyProperty‌(推荐)
这是 MyBatis 官方推荐的方式,适用于支持自增主键的数据库(如 MySQL)。
‌步骤说明‌
在 标签中添加 useGeneratedKeys=“true”,表示启用自动生成的主键。
指定 keyProperty 属性,将生成的主键值回填到实体类的某个字段中。
‌示例代码‌

<!-- Mapper XML 配置 -->
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">INSERT INTO user (name, age)VALUES (#{name}, #{age})
</insert>
// Java 调用代码
User user = new User();
user.setName("Alice");
user.setAge(25);
userMapper.insertUser(user);

// 插入后,自增 ID 会自动回填到 user 对象的 id 字段中
System.out.println(“插入的 ID 是:” + user.getId());

‌方法 2:使用 标签‌(更灵活)
如果数据库不支持自动生成主键(如 Oracle 序列),可以使用 手动获取主键。
‌步骤说明‌
在 标签内添加 子标签。
通过 SQL 查询最近插入的自增 ID(MySQL 使用 LAST_INSERT_ID() 函数)。
将查询结果回填到实体类的字段中。
‌示例代码‌

<!-- Mapper XML 配置 -->
<insert id="insertUser" parameterType="User">INSERT INTO user (name, age)VALUES (#{name}, #{age})<!-- 获取自增 ID --><selectKey keyProperty="id" resultType="int" order="AFTER">SELECT LAST_INSERT_ID()</selectKey>
</insert>
// Java 调用代码(与方式 1 相同)
User user = new User();
user.setName("Bob");
user.setAge(30);
userMapper.insertUser(user);
System.out.println("插入的 ID 是:" + user.getId());

‌关键点总结‌
‌配置项‌ ‌说明‌
useGeneratedKeys 设置为 true,表示启用数据库自动生成的主键。
keyProperty 指定实体类中接收主键值的字段名(如 id)。
适用于复杂场景(如 Oracle 序列),需指定 SQL、结果类型和执行顺序。
LAST_INSERT_ID() MySQL 专用函数,返回最近插入的自增 ID(需在同一连接中调用)。

常见问题‌
为什么获取的 ID 是 0 或 null?‌
检查 keyProperty 是否与实体类的字段名一致。
确保 useGeneratedKeys 已启用或 配置正确。
‌批量插入如何返回所有 ID?‌
MySQL 的 LAST_INSERT_ID() 只能返回第一个插入的 ID。
批量插入时,需通过 useGeneratedKeys + @Param 注解返回所有 ID(需 MyBatis 3.3.1+)。
‌扩展:批量插入返回所有 ID(MySQL)‌

<insert id="batchInsertUsers" useGeneratedKeys="true" keyProperty="id">INSERT INTO user (name, age)VALUES<foreach item="user" collection="list" separator=",">(#{user.name}, #{user.age})</foreach>
</insert>
List<User> users = new ArrayList<>();
// 添加多个 User 对象...
userMapper.batchInsertUsers(users);// 遍历获取所有生成的 ID
users.forEach(user -> System.out.println(user.getId()));

10, MyBatis关联查询association、collection

① 多对一

    <resultMap id="getEmpAllByEmpId" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><result column="dept_id" property="dept.deptId"></result><result column="dept_name" property="dept.deptName"></result></resultMap><!-- Emp getEmpByEmpId(@Param("empId") Integer empId);--><select id="getEmpByEmpId" resultMap="getEmpAllByEmpId">selectt_emp.*,t_dept.*from t_emp inner join t_depton t_emp.dept_id = t_dept.dept_idwhere t_emp.emp_id = #{empId}</select>
    <resultMap id="getEmpAllByEmpIdTwo" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><association property="dept" javaType="Dept" ><id  column="dept_id" property="deptId"></id><result  column="dept_name" property="deptName"></result></association></resultMap><!--Emp getEmpByEmpIdTwo(@Param("empId") Integer empId);--><select id="getEmpByEmpIdTwo" resultMap="getEmpAllByEmpIdTwo">selectt_emp.*,t_dept.*from t_emp inner join t_depton t_emp.dept_id = t_dept.dept_idwhere t_emp.emp_id = #{empId}</select>
    <resultMap id="getEmpAndDeptByStepMap" type="Emp"><id column="emp_id" property="empId"></id><result column="emp_name" property="empName"></result><result column="age" property="age"></result><result column="gender" property="gender"></result><association  column="dept_id" property="dept" select="com.gothic.sunset.mapper.EmpMapper.getEmpAndDeptByStepTwo" ></association></resultMap><!-- Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);--><select id="getEmpAndDeptByStepOne" resultMap="getEmpAndDeptByStepMap">select * from t_emp where emp_id = #{empId}</select><resultMap id="getEmpAndDeptByStepTwoMap" type="Dept"><id column="dept_id" property="deptId"></id><result column="dept_name" property="deptName"></result></resultMap><!--Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);--><select id="getEmpAndDeptByStepTwo" resultMap="getEmpAndDeptByStepTwoMap">select * from t_dept where dept_id = #{deptId}</select>

②. 一对多

    <resultMap id="getDeptAndEmpMap" type="Dept"><id property="deptId" column="dept_id"></id><result property="deptName" column="dept_name"></result><collection property="emps" ofType="Emp"><id property="empId" column="emp_id"></id><result property="empName" column="emp_name"></result><result property="age" column="age"></result><result property="sex" column="sex"></result><result property="email" column="email"></result></collection></resultMap><!--List<Dept> getDeptAndEmpById(Integer deptId);--><select id="getDeptAndEmpById" resultMap="getDeptAndEmpMap">select * from t_dept left join t_emp on t_dept.dept_id = t_emp.emp_id where t_dept.dept_id = #{dept_id}</select>
<resultMap id="getDeptAndEmpByStepOneMap" type="Dept"><id property="deptId" column="dept_id"></id><result property="deptName" column="dept_name"></result><collection property="emps"select="com.gothic.sunset.mapper.DeptMapper.getDeptAndEmpByStepTwo"column="dept_id"></collection></resultMap><!--Dept getDeptAndEmpByStepOne(@Param("deptId") Integer deptId);--><select id="getDeptAndEmpByStepOne" resultMap="getDeptAndEmpByStepOneMap">select * from t_dept where dept_id = #{deptId}</select><!--List<Emp> getDeptAndEmpByStepTwo(@Param("empId") Integer empId);--><select id="getDeptAndEmpByStepTwo" resultType="Emp">select * from t_emp where emp_id = #{empId}</select>

11.mysql主流连接池比较

‌MySQL 主流连接池对比与选型‌
‌一、核心连接池特性对比‌
在这里插入图片描述
‌二、关键参数与调优建议‌
以下为连接池通用核心参数(具体值需根据业务负载调整)‌:
‌参数‌ ‌作用‌
maxActive(最大连接数) 控制连接池最大并发连接数,避免数据库过载(建议根据 CPU 和内存资源设置)
minIdle(最小空闲连接) 维持池中空闲连接下限,减少突发请求时的延迟
maxWait(最大等待时间) 获取连接的超时时间(超时抛出异常,防止线程阻塞)
testOnBorrow(连接校验) 获取连接时验证有效性(可能增加开销,建议开启异步校验替代)
validationQuery(校验SQL) 用于验证连接的简单 SQL(如 SELECT 1)

‌三、选型建议‌
‌高性能场景(如互联网应用)‌
‌优先选择 HikariCP‌:通过无锁设计与高效内存管理,适合每秒数千次查询的高并发场景‌。
‌配置示例‌:

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20);  // 最大连接数
HikariDataSource dataSource = new HikariDataSource(config);

‌监控与安全需求(如企业级系统)‌
‌选择 Druid‌:内置 SQL 防火墙、慢查询日志、Web 监控页面,便于排查性能瓶颈‌。
‌防泄漏配置‌:

DruidDataSource dataSource = new DruidDataSource();
dataSource.setMaxActive(50);
dataSource.setRemoveAbandoned(true);  // 自动回收泄漏连接
dataSource.setFilters("stat,wall");   // 启用统计和防火墙

‌传统或低负载场景‌
‌DBCP/C3P0‌:适合历史项目维护或简单 CRUD 操作,但需注意性能瓶颈‌。

‌四、连接池工作原理‌
‌初始化阶段‌:根据 initialSize 创建初始连接,存入池中‌。
‌连接获取‌:应用从池中申请连接,若无可用连接且未达 maxActive,则新建连接‌。
‌连接归还‌:使用完毕后归还连接至池中,而非直接关闭‌。

‌总结‌
‌HikariCP‌ 是大多数现代应用的‌首选‌,尤其在性能敏感场景‌。
‌Druid‌ 在监控与安全需求场景下‌不可替代‌,适合复杂业务分析‌。
避免滥用连接池参数,优先使用默认配置,逐步调优(如 maxActive 根据压测结果调整)‌

12.批量操作示例

package com.*.courier.repository;import com.*.CourierLeaderEvaluateDO;
import com.*.SuperMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;public interface CourierLeaderEvaluateMapper extends SuperMapper<CourierLeaderEvaluateDO> {int batchInsertReviewBaseInfo(@Param("list") List list);int batchUpdateReviewLevel(@Param("list") List<CourierLeaderEvaluateDO> courierList);int batchUpdateReportLevel(@Param("list") List<CourierLeaderEvaluateDO> list);int batchUpdateReportByIds(@Param("list") List<CourierLeaderEvaluateDO> list);int batchUpdateState(@Param("list") List<CourierLeaderEvaluateDO> list);int batchUpdateFinalise(@Param("list") List<CourierLeaderEvaluateDO> list);int batchDelByIds(@Param("list") List<CourierLeaderEvaluateDO> list);int batchUpdateReviewByIds(@Param("list") List<CourierLeaderEvaluateDO> courierList);int batchUpdateDeptLevelBySchemeCode(@Param("list") List list);@Update("${key}")int updateByKey(String key);
}
<?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.*.repository.CourierLeaderEvaluateMapper"><sql id="Base_Column_List">ID as id, YEAR as year, MONTH as month, SCHEME_CODE as schemeCode, PAR_CODE as parCode, PAR_NAME as parName,PAR_JOB as parJob, PAR_DEPT as parDept, PAR_DEPT2 as parDept2, PAR_DEPT3 as parDept3, PAR_DEPT4 as parDept4,PAR_DEPT5 as parDept5, PAR_DEPT6 as parDept6, EVAL_CODE as evalCode, EVAL_NAME as evalName, EVAL_START as evalStart,EVAL_END as evalEnd, GRADE as grade, STATE as state,FINALISE as finalise, EVAL_TIME as evalTime, DR as dr,CREATE_TIME as createTime, CREATE_USER as createUser, MODIFY_TIME as modifyTime, MODIFY_USER as modifyUser</sql><insert id="batchInsertReviewBaseInfo">insert into  p.t_evaluate(year, month, scheme_code, par_code, par_name,par_dept, par_dept2,par_dept3,par_dept4,par_dept5,par_dept6,par_job,eval_code,eval_name,eval_start,eval_end,state,finalise,dr,create_user,create_time,modify_user,modify_time)values<foreach item="item" index="index" collection="list" separator=",">(#{item.year},#{item.month},#{item.schemeCode},#{item.parCode},#{item.parName},#{item.parDept},#{item.parDept2},#{item.parDept3},#{item.parDept4},#{item.parDept5},#{item.parDept6},#{item.parJob},#{item.evalCode},#{item.evalName},#{item.evalStart},#{item.evalEnd},#{item.state},#{item.finalise},#{item.dr},#{item.createUser},#{item.createTime},#{item.modifyUser},#{item.modifyTime})</foreach></insert><update id="batchUpdateReviewLevel" parameterType="java.util.List">update p.t_evaluateset par_code =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parCode}</foreach>,par_name =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parName}</foreach>,grade =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.grade}</foreach>,eval_code =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.evalCode}</foreach>,eval_name =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.evalName}</foreach>,state =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.state}</foreach>,eval_time =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.evalTime}</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyTime}</foreach>where scheme_code in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.schemeCode}</foreach></update><update id="batchUpdateReportLevel" parameterType="java.util.List">update p.t_evaluateset grade =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.grade}</foreach>,eval_time =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.evalTime}</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyTime}</foreach>where scheme_code in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.schemeCode}</foreach></update><update id="batchUpdateState" parameterType="java.util.List">update p.t_evaluateset state =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then "1"</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.modifyTime}</foreach>where scheme_code in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.schemeCode}</foreach></update><update id="batchUpdateFinalise" parameterType="java.util.List">update p.t_evaluateset finalise =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then "1"</foreach>where id in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.id}</foreach></update><update id="batchDelete" parameterType="java.util.List">update p.t_evaluateset dr =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then "1"</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyTime}</foreach>where id in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.id}</foreach></update><update id="batchUpdateReviewByIds" parameterType="java.util.List">update p.t_evaluateset grade =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.grade}</foreach>,state =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.state}</foreach>,eval_code =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.evalCode}</foreach>,eval_name =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.evalName}</foreach>,eval_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.evalTime}</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyTime}</foreach>where id in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.id}</foreach></update><update id="batchUpdateReportByIds" parameterType="java.util.List">update p.t_evaluateset grade =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.grade}</foreach>,state =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.state}</foreach>,eval_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.evalTime}</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyTime}</foreach>where id in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.id}</foreach></update><update id="batchDelByIds" parameterType="java.util.List">update p.t_evaluateset dr =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then "1"</foreach>,modify_user =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyUser}</foreach>,modify_time =<foreach collection="list" item="item" index="index"separator=" " open="case id" close="end">when #{item.id} then #{item.modifyTime}</foreach>where id in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.id}</foreach></update><update id="batchUpdateDeptLevelBySchemeCode" parameterType="java.util.List">update p.t_evaluateset par_dept2 =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parDept2}</foreach>,par_dept3 =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parDept3}</foreach>,par_dept4 =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parDept4}</foreach>,par_dept5 =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parDept5}</foreach>,par_dept6 =<foreach collection="list" item="item" index="index"separator=" " open="case scheme_code" close="end">when #{item.schemeCode} then #{item.parDept6}</foreach>where scheme_code in<foreach collection="list" item="item" index="index"separator="," open="(" close=")">#{item.schemeCode}</foreach></update></mapper>

其他TODO-----------------------------

1.Mybatis如何提高批量查询的效率?
2.Mybatis如何提高批量增删改的效率?
3.Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
4.MyBatis 的 xml 映射文件中,不同 xml 映射文件的id 是否可以重复?
5.MyBatis 是否可以映射 Enum 枚举类?
6.简述 MyBatis 的 xml 映射文件和 MyBatis 内部数据结构之间的映射关系?


文章转载自:

http://e22at9VJ.ybgcn.cn
http://2wh0GSD9.ybgcn.cn
http://mGrt2GHq.ybgcn.cn
http://L6xPsToM.ybgcn.cn
http://RoikyjTv.ybgcn.cn
http://c7mjwN4S.ybgcn.cn
http://Hr23cT0l.ybgcn.cn
http://ae3Z4DNE.ybgcn.cn
http://g9N4BjfI.ybgcn.cn
http://3AjK24tE.ybgcn.cn
http://4sUpFr30.ybgcn.cn
http://lBaou6Am.ybgcn.cn
http://Epz3s7BM.ybgcn.cn
http://WckJHSh7.ybgcn.cn
http://wsyani8o.ybgcn.cn
http://LZ17rzcv.ybgcn.cn
http://RfV3ChXx.ybgcn.cn
http://rBu0yd86.ybgcn.cn
http://SFuQgGWn.ybgcn.cn
http://pWSgVEmy.ybgcn.cn
http://0Qkqv6Sl.ybgcn.cn
http://CpFAqkiO.ybgcn.cn
http://m94Z9T1t.ybgcn.cn
http://H76jhbyP.ybgcn.cn
http://sZI9T2qt.ybgcn.cn
http://FbAfay2K.ybgcn.cn
http://ijg0sz9P.ybgcn.cn
http://XntvJm13.ybgcn.cn
http://zCbJwI1P.ybgcn.cn
http://ISQcSvu5.ybgcn.cn
http://www.dtcms.com/a/375296.html

相关文章:

  • Spring Boot 2.7 启动流程详解
  • springboot框架使用websocket实现一个聊天室的细节
  • Kubernetes集群部署Jenkins指南
  • 027、全球数据库市场深度分析:技术革命下的产业格局重塑
  • 贪心算法与动态规划:数学原理、实现与优化
  • Oracle APEX 利用卡片实现翻转(方法二)
  • 记一次 electron 添加 检测 终端编码,解决终端打印中文乱码问题
  • 从生活照料到精神关怀,七彩喜打造全场景养老服务体系
  • 2025-09-08升级问题记录: 升级SDK从Android11到Android12
  • BizDevOps 是什么?如何建设企业 BizDevOps 体系
  • 一、ARM异常等级及切换
  • 【项目复现】MOOSE-Chem 用于重新发现未见化学科学假说的大型语言模型
  • mybatis plus 使用wrapper输出SQL
  • PgSQL中优化术语HOT详解
  • Python 绘制 2025年 9~11月 P/1999 RO28 (LONEOS) 彗星路径
  • Spring Cloud Stream深度实战:发布订阅模式解决微服务通信难题
  • 【菜狗每日记录】深度轨迹聚类算法、GRU门控神经网络—20250909
  • OpenCV 实战:多角度模板匹配实现图像目标精准定位
  • C#/.NET/.NET Core技术前沿周刊 | 第 53 期(2025年9.1-9.7)
  • 基于Java+Vue开发的家政服务系统源码适配H5小程序APP
  • 使用Flask实现接口回调地址
  • Java线程中的sleep、wait和block:区别与联系详解
  • 生信软件管理, 容器-Singularity学习笔记
  • go webrtc - 2 webrtc重要概念
  • 智能驱动,全程可控——D-QS工程造价数字化平台核心功能深度解析
  • [硬件电路-170]:50Hz工频干扰:本质、产生机制与影响
  • tab切换动画,背景图向内收缩效果,主图片缓慢展开效果(含自适应)
  • 【内存管理】设置内存页表项 set_pte_at
  • Python中内置装饰器
  • 鸿蒙NEXT UI高性能开发实战:从原理到优化