
mybatis复习
Mybatis
半自动ORM 对象关系映射框架
核心组件
- SqlSessionFactory
- SqlSession
- Mapper接口
- Mapper.xml
- mybatis-config(配置文件,springboot中集成)
Mapper.xml核心标签
insert,update,select,delete,resultMap
${},字符串拼接,有注入风险,用于拼接表名,排序字段
#{},预编译,防注入
动态标签
where,if,choose,foreach,set
resultMap:
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
</resultMap>
resultType对比resultMap:
- resultType:字段属性名完全一致,性能略高,灵活性低
- resultMap:自定义字段属性映射,性能低,灵活性高
- 同时设置只有resultMap生效
注意驼峰命名
一对一,一对多映射
association
<resultMap id="OrderResultMap" type="Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<!-- 一对一:association -->
<association property="user" javaType="User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
</association>
</resultMap>
<select id="selectOrderWithUser" resultMap="OrderResultMap">
SELECT o.*, u.id user_id, u.username
FROM t_order o
LEFT JOIN t_user u ON o.user_id = u.id
</select>
collection
<resultMap id="UserResultMap" type="User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<!-- 一对多:collection -->
<collection property="orderList" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="UserResultMap">
SELECT u.id user_id, u.username,
o.id order_id, o.order_no
FROM t_user u
LEFT JOIN t_order o ON u.id = o.user_id
</select>
一对多必须给子表起别名
一对多必须用collection,用association只会封装最后一条数据
一对多查询重复数据正常,mybati会根据主表id合并
一级缓存:sqlSession级别(同一个SqlSession,同一个Mapper方法,同一个参数),分布式环境会导致脏读,Spring整合Mybatis,每次请求都创建新的SqlSession,一级缓存失效
二级缓存:mapper级别,xml中手动<cache/> 默认关闭
<!-- mybatis-config.xml -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- Mapper.xml -->
<cache/>
mybatis的缓存比较鸡肋,分布式环境下会产生脏读
mybatis-plus
mybatis增强
实体类注解
@TableName("user") // 表名
@TableId(type = IdType.AUTO) // 主键策略
@TableField("user_name") // 字段名
@TableField(exist = false) // 非数据库字段
BaseMapper
userMapper.selectById(1);
userMapper.selectList(null);
userMapper.insert(user);
userMapper.updateById(user);
userMapper.deleteById(1);
ServiceImpl
-
条件构造器:QueryWrapper 、LambdaQueryWrapper
- in()空数组会报错,非报错也会查询全部,
-
分页插件,Page对象必须放Mapper第一个参数
java@Configuration public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件(必须指定数据库类型)必须放在所有插件最后 PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); // 单页最大条数限制,防止恶意攻击 paginationInterceptor.setMaxLimit(1000L); // 溢出总页数后是否返回第一页 paginationInterceptor.setOverflow(true); interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } } -
批量新增
- for循环insert:性能极差
- saveBatch方法,10-1000条,默认1000条,saveBatch时要传入list.size(),不会返回自增主键(3.3.0+需要配置)某批次失败不会回滚完整事务
- 自定义mapper foreach:性能极高,但受sql语句长度限制
-
逻辑删除 @TableLogic
- 联表查询,自定义sql不会加逻辑删除条件,需要自己加
- 唯一索引冲突,需要将唯一索引改为(x,del_flag)
-
自动填充 @TableField(fill = FieldFill.INSERT)
- 手动设置值覆盖自动填充
- 只对mp自提供方法生效,saveBatch,updateBatch,以及自定义sql不生效
-
update,非必填项更新为null(合并更新)
1.实体类字段加@TableField (IGNORED) // X,容易在业务层忘记赋值丢数据 2.update(entity,new lambdaUpdateWrapper().eq().set(非必填字段)); // √ -
防全表更新插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); -
乐观锁@Version
java@Version private Integer version; -
ID生成策略
类型 说明 使用场景 坑 AUTO 数据库自增 MySQL,PostgreSQL 分布式环境下ID不唯一 ASSIGN_ID 雪花算法(默认) 分布式系统 时钟回拨问题,生成位数19位,前端精度丢失 ASSIGN_UUID UUID 不需要有序ID场景 无序,占空间,索引性能差 INPUT 用户输入ID 自定义ID生成 必须手动设置ID NONE 不生成ID 联合主键 必须手动设置ID
多数据源
dynamic-datasource-spring-boot-starter
@DS()切换数据源
-
动态切换数据源需要在方法调用前,不能在方法内部
-
需要被Spring管理,同一方法不能持有不同@DS
-
@Transactional只能单数据源,跨数据源事务需要分布式事务Seata
动态表名,分表场景
DynamicTableNameInnerInterceptor 插件
- RequestContextHolder是基于ThreadLocal,异步会失效
更多用法
-
自定义sql+qw
java@Select(""" select u.* from user u ${ew.customSqlSegment} """) List<User> getUserList(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper); -
service层lambda调用
java// 查询 List<User> list = userService.lambdaQuery() .eq(User::getAge, 18) .like(User::getName, "张") .list(); // 更新 userService.lambdaUpdate() .eq(User::getId,1) .set(User::getName, "新名字") .update(); // 删除 userService.lambdaUpdate() .eq(User::getId,1) .remove(); -
静态工具类Wrappers
javaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery() .eq(User::getAge,20); -
qw函数
java// 嵌套条件 (a=1 AND (b=2 OR c=3)) wrapper.eq(User::getA, 1) .and(w -> w.eq(User::getB,2).or().eq(User::getC,3)); // 过滤 null 条件(自动忽略) wrapper.eq(StrUtil.isNotBlank(name), User::getName, name); // EXISTS / NOT EXISTS wrapper.exists("select 1 from order o where o.user_id = user.id"); // 子查询 wrapper.inSql(User::getId, "select user_id from order where status=1"); -
自定义ID生成器
java@Component public class CustomIdGenerator implements IdentifierGenerator { @Override public Number nextId(Object entity) { return 你的分布式ID; } } -
多租户和数据权限插件(基本都是自己实现,不用)
-
TypeHandler自定义类型处理器,JSON,Map,对象场景等
-
枚举自动映射
java@EnumValue private Integer code; @JsonValue private String desc; // 容易出坑,sql排查困难,强依赖历史数据,不建议使用Mybatis
半自动ORM 对象关系映射框架
核心组件
-
SqlSessionFactory
-
SqlSession
-
Mapper接口
-
Mapper.xml
-
mybatis-config(配置文件,springboot中集成)
Mapper.xml核心标签
insert,update,select,delete,resultMap
${},字符串拼接,有注入风险,用于拼接表名,排序字段
#{},预编译,防注入
动态标签
where,if,choose,foreach,set
resultMap:
<resultMap id="userMap" type="User"> <id column="id" property="id"/> <result column="user\_name" property="userName"/> </resultMap>resultType对比resultMap:
-
resultType:字段属性名完全一致,性能略高,灵活性低
-
resultMap:自定义字段属性映射,性能低,灵活性高
-
同时设置只有resultMap生效
注意驼峰命名
一对一,一对多映射
association
<resultMap id="OrderResultMap" type="Order">
<id column="id" property="id"/>
<result column="order_no" property="orderNo"/>
<!-- 一对一:association -->
<association property="user" javaType="User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
</association>
</resultMap>
<select id="selectOrderWithUser" resultMap="OrderResultMap">
SELECT o.*, u.id user_id, u.username
FROM t_order o
LEFT JOIN t_user u ON o.user_id = u.id
</select>
collection
<resultMap id="UserResultMap" type="User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<!-- 一对多:collection -->
<collection property="orderList" ofType="Order">
<id column="order_id" property="id"/>
<result column="order_no" property="orderNo"/>
</collection>
</resultMap>
<select id="selectUserWithOrders" resultMap="UserResultMap">
SELECT u.id user_id, u.username,
o.id order_id, o.order_no
FROM t_user u
LEFT JOIN t_order o ON u.id = o.user_id
</select>
一对多必须给子表起别名
一对多必须用collection,用association只会封装最后一条数据
一对多查询重复数据正常,mybati会根据主表id合并
一级缓存:sqlSession级别(同一个SqlSession,同一个Mapper方法,同一个参数),分布式环境会导致脏读,Spring整合Mybatis,每次请求都创建新的SqlSession,一级缓存失效
二级缓存:mapper级别,xml中手动<cache/> 默认关闭
<!-- mybatis-config.xml --><settings>
<setting name="cacheEnabled" value="true"/>
</settings>
mybatis的缓存比较鸡肋,分布式环境下会产生脏读
mybatis-plus
mybatis增强
实体类注解
@TableName("user") // 表名
@TableId(type = IdType.AUTO) // 主键策略
@TableField("user_name") // 字段名
@TableField(exist = false) // 非数据库字段
BaseMapper
userMapper.selectById(1);
userMapper.selectList(null);
userMapper.insert(user);
userMapper.updateById(user);
userMapper.deleteById(1);
ServiceImpl
-
条件构造器:QueryWrapper 、LambdaQueryWrapper
- in()空数组会报错,非报错也会查询全部,
-
分页插件,Page对象必须放Mapper第一个参数
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件(必须指定数据库类型)必须放在所有插件最后
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 单页最大条数限制,防止恶意攻击
paginationInterceptor.setMaxLimit(1000L);
// 溢出总页数后是否返回第一页
paginationInterceptor.setOverflow(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
} -
批量新增
-
for循环insert:性能极差
-
saveBatch方法,10-1000条,默认1000条,saveBatch时要传入list.size(),不会返回自增主键(3.3.0+需要配置)某批次失败不会回滚完整事务
-
自定义mapper foreach:性能极高,但受sql语句长度限制
-
-
逻辑删除 @TableLogic
-
联表查询,自定义sql不会加逻辑删除条件,需要自己加
-
唯一索引冲突,需要将唯一索引改为(x,del_flag)
-
-
自动填充 @TableField(fill = FieldFill.INSERT)
-
手动设置值覆盖自动填充
-
只对mp自提供方法生效,saveBatch,updateBatch,以及自定义sql不生效
-
-
update,非必填项更新为null(合并更新)
1.实体类字段加@TableField (IGNORED) // X,容易在业务层忘记赋值丢数据
2.update(entity,new lambdaUpdateWrapper().eq().set(非必填字段)); // √ -
防全表更新插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
-
乐观锁@Version
@Version private Integer version;
-
ID生成策略
类型 说明 使用场景 坑 AUTO 数据库自增 MySQL,PostgreSQL 分布式环境下ID不唯一 ASSIGN_ID 雪花算法(默认) 分布式系统 时钟回拨问题,生成位数19位,前端精度丢失 ASSIGN_UUID UUID 不需要有序ID场景 无序,占空间,索引性能差 INPUT 用户输入ID 自定义ID生成 必须手动设置ID NONE 不生成ID 联合主键 必须手动设置ID
多数据源
dynamic-datasource-spring-boot-starter
@DS()切换数据源
-
动态切换数据源需要在方法调用前,不能在方法内部
-
需要被Spring管理,同一方法不能持有不同@DS
-
@Transactional只能单数据源,跨数据源事务需要分布式事务Seata
动态表名,分表场景
DynamicTableNameInnerInterceptor 插件
- RequestContextHolder是基于ThreadLocal,异步会失效
更多用法
-
自定义sql+qw
@Select(""" select u.* from user u ${ew.customSqlSegment} """) List<User> getUserList(@Param(Constants.WRAPPER) LambdaQueryWrapper<User> wrapper);
-
service层lambda调用
// 查询 List<User> list = userService.lambdaQuery() .eq(User::getAge, 18) .like(User::getName, "张") .list();
// 更新 userService.lambdaUpdate() .eq(User::getId,1) .set(User::getName, "新名字") .update();
// 删除 userService.lambdaUpdate() .eq(User::getId,1) .remove();
-
静态工具类Wrappers
QueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery() .eq(User::getAge,20);
-
qw函数
// 嵌套条件 (a=1 AND (b=2 OR c=3)) wrapper.eq(User::getA, 1) .and(w -> w.eq(User::getB,2).or().eq(User::getC,3));
// 过滤 null 条件(自动忽略) wrapper.eq(StrUtil.isNotBlank(name), User::getName, name);
// EXISTS / NOT EXISTS wrapper.exists("select 1 from order o where o.user_id = user.id");
// 子查询 wrapper.inSql(User::getId, "select user_id from order where status=1");
-
自定义ID生成器
@Component public class CustomIdGenerator implements IdentifierGenerator { @Override public Number nextId(Object entity) { return 你的分布式ID; } }
-
多租户和数据权限插件(基本都是自己实现,不用)
-
TypeHandler自定义类型处理器,JSON,Map,对象场景等
-
枚举自动映射
@EnumValue private Integer code;
@JsonValue private String desc; // 容易出坑,sql排查困难,强依赖历史数据,不建议使用