SpringBoot之MyBatis
更新: 2/10/2025 字数: 0 字 时长: 0 分钟
MyBatis XML写法
批量操作优化
xml
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO user (username, email, create_time) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.username}, #{item.email}, #{item.createTime})
</foreach>
</insert>
xml
<update id="batchUpdate" parameterType="java.util.List">
<foreach collection="list" item="item" separator=";">
UPDATE user
SET username = #{item.username}, email = #{item.email}
WHERE id = #{item.id}
</foreach>
</update>
xml
<delete id="batchDelete" parameterType="java.util.List">
DELETE FROM user WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
动态SQL
xml
<!-- trim标签可以帮助我们优化生成的SQL语句,避免出现多余的AND或OR关键字 -->
<select id="findUsers" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<!-- concat模糊查询 -->
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
<!-- 开始时间 >= 传入时间的 00:00:00 -->
<if test="timeStart != null">
AND <![CDATA[ time_start >= #{condition.timeStart} + INTERVAL '0' HOUR ]]>
</if>
<!-- 结束时间 < 传入时间+1的 00:00:00 -->
<if test="timeEnd != null">
AND <![CDATA[ time_end < #{condition.timeEnd} + INTERVAL '1' DAY ]]>
</if>
<!-- 班级id in查询 -->
<if test="ids != null and ids.size() > 0">
AND class_id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</if>
</trim>
</select>
xml
<select id="findUsersByCondition" resultType="User">
SELECT * FROM user
<where>
<choose>
<when test="searchType == 'username'">
AND username LIKE CONCAT('%', #{keyword}, '%')
</when>
<when test="searchType == 'email'">
AND email LIKE CONCAT('%', #{keyword}, '%')
</when>
<otherwise>
AND (username LIKE CONCAT('%', #{keyword}, '%') OR email LIKE CONCAT('%', #{keyword}, '%'))
</otherwise>
</choose>
</where>
</select>
xml
<!--在插入操作中,我们经常需要获取数据库自动生成的主键。MyBatis提供了<selectKey>标签来实现这一功能。-->
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
SELECT 2531020
</selectKey>
INSERT INTO user (username, email, create_time)
VALUES (#{username}, #{email}, #{createTime})
</insert>
注解写法
java
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user")
List<User> findAll();
/**
* 通过id查询用户的全部信息
* @param id 用户id
* @return 用户信息模型
*/
@Select({"select * from user where id = #{id}"})
@Results(id = "studentMap", value = {
@Result(column = "id", property = "id", id = true),
@Result(column = "username", property = "username"),
@Result(column = "name", property = "name"),
@Result(property = "roleList", javaType = List.class, column = "id", many = @Many(select = "top.haijunit.sample.mapper.RoleMapper.getRoleListById"))
})
UserVO getUserById(@Param("id") Integer id);
}
java
@Mapper
public interface RoleMapper {
@Select("SELECT * FROM role WHERE roleName = #{roleName}")
Role findByUsername(String roleName);
@Select("SELECT * FROM role WHERE id IN (SELECT role_id FROM user_role_con WHERE user_id = #{id})")
List<Role> getRoleListById(@Param("id") Integer id);
}
java
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.util.Map;
@Mapper
public interface UserMapper extends BaseMapper<User> {
@SelectProvider(type = SqlProvider.class, method = "findByCondition")
List<User> findByCondition(Map<String, Object> conditions);
class SqlProvider {
public String findByCondition(Map<String, Object> params, ProviderContext context) {
StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
if (params.get("username") != null) {
sql.append(" AND username = #{username}");
}
if (params.get("email") != null) {
sql.append(" AND email = #{email}");
}
return sql.toString();
}
}
}
Wrapper查询
java
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper;
public List<User> findUsers() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "张");
queryWrapper.lamba().eq(User::getStatus, 1);
return userMapper.findUsers(wrapper);
}
}
java
@Mapper
public interface UserMapper {
@Select("SELECT * FROM user")
List<User> findUsers(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
}
xml
<select id="findUsers" resultType="User">
select * from user
<where>
<if test="ew.sqlSegment != null ">
${ew.sqlSegment}
</if>
</where>
</select>
Wrapper 高级写法
java
// 减少数据库连接的创建和销毁开销
// 批量操作可以在一个事务中完成,提高数据一致性
// 数据库可以优化批量操作的执行计划
// 显著减少网络往返次数,提升吞吐量
class UserService extends ServiceImpl<UserMapper, User> {
public void saveBatch() {
// ❌ 不推荐
for (User user : userList) {
userMapper.insert(user);
}
// ✅ 推荐
userService.saveBatch(userList, 100); // 每批次处理100条数据
// ✅ 更优写法:自定义批次大小
userService.saveBatch(userList, BatchConstants.BATCH_SIZE);
}
}
java
// EXISTS是基于索引的快速查询,可以使用到索引
// EXISTS在找到第一个匹配项就会停止扫描
// IN子查询需要加载所有数据到内存后再比较
// 当外表数据量大时,EXISTS的性能优势更明显
class UserService extends ServiceImpl<UserMapper, User> {
public void getUserList() {
// ❌ 不推荐
wrapper.inSql("user_id", "select user_id from order where amount > 1000");
// ✅ 推荐
wrapper.exists("select 1 from order where order.user_id = user.id and amount > 1000");
// ✅ 更优写法:使用LambdaQueryWrapper
wrapper.exists(orderService.lambdaQuery().gt(Order::getAmount, 1000).apply("order.user_id = user.id"));
}
}
java
// 直接拼接SQL容易导致SQL注入攻击
// 动态SQL可能破坏SQL语义完整性
// 影响SQL语句的可维护性和可读性
// last会绕过MyBatis-Plus的安全检查机制
class UserService extends ServiceImpl<UserMapper, User> {
public void getUserList() {
// ❌ 不推荐:SQL注入风险
wrapper.last("ORDER BY " + sortField + " " + sortOrder);
// ❌ 不推荐:直接字符串拼接
wrapper.last("ORDER BY FIELD(status, 'active', 'pending', 'inactive')");
// ✅ 推荐:使用 Lambda 安全排序
wrapper.orderBy(true, true, User::getStatus);
// ✅ 推荐:多字段排序示例
wrapper.orderByAsc(User::getStatus).orderByDesc(User::getCreateTime);
}
}
java
// 优雅处理空值,避免无效条件
// 减少代码中的if-else判断
// 提高代码可读性
// 防止生成冗余的SQL条件
class UserService extends ServiceImpl<UserMapper, User> {
public void getUserList() {
// ❌ 不推荐
if (StringUtils.isNotBlank(name)) {
wrapper.eq("name", name);
}
if (age != null) {
wrapper.eq("age", age);
}
// ✅ 推荐
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name);
wrapper.eq(Objects.nonNull(age), User::getAge, age);
// ✅ 更优写法:结合业务场景
wrapper.eq(StringUtils.isNotBlank(name), User::getName, name);
wrapper.eq(Objects.nonNull(age), User::getAge, age);
wrapper.eq(User::getDeleted, false); // 默认查询未删除记录
wrapper.orderByDesc(User::getCreateTime); // 默认按创建时间倒序
}
}
java
// 业务代码和性能监控代码完全分离
// try-with-resources 即使发生异常,close() 方法也会被调用,确保一定会记录耗时
// 不需要手动管理计时的开始和结束
// 更优雅
class UserService extends ServiceImpl<UserMapper, User> {
// ❌ 不推荐:简单计时,代码冗余
public List<User> listUsers(QueryWrapper<User> wrapper) {
long startTime = System.currentTimeMillis();
List<User> users = userMapper.selectList(wrapper);
long endTime = System.currentTimeMillis();
log.info("查询耗时:{}ms", (endTime - startTime));
return users;
}
// ✅ 推荐:使用 Try-with-resources 自动计时
public List<User> listUsersWithPerfTrack(QueryWrapper<User> wrapper) {
try (PerfTracker.TimerContext ignored = PerfTracker.start()) {
return userMapper.selectList(wrapper);
}
}
}
// 性能追踪工具类
@Slf4j
public class PerfTracker {
private final long startTime;
private final String methodName;
private PerfTracker(String methodName) {
this.startTime = System.currentTimeMillis();
this.methodName = methodName;
}
public static TimerContext start() {
return new TimerContext(Thread.currentThread().getStackTrace()[2].getMethodName());
}
public static class TimerContext implements AutoCloseable {
private final PerfTracker tracker;
private TimerContext(String methodName) {
this.tracker = new PerfTracker(methodName);
}
@Override
public void close() {
long executeTime = System.currentTimeMillis() - tracker.startTime;
if (executeTime > 500) {
log.warn("慢查询告警:方法 {} 耗时 {}ms", tracker.methodName, executeTime);
}
}
}
}
java
// 类型安全
// 避免手动拼接sql,防止sql注入
// 代码可维护性更强,更清晰
class UserService extends ServiceImpl<UserMapper, User> {
public void updateUser() {
// ❌ 不推荐:使用 setSql
userService.lambdaUpdate().setSql("integral = integral + 10").update();
// ✅ 推荐:使用 setIncrBy
userService.lambdaUpdate().eq(User::getId, 1L).setIncrBy(User::getIntegral, 10).update();
// ✅ 推荐:使用 setDecrBy
userService.lambdaUpdate().eq(User::getId, 1L).setDecrBy(User::getStock, 5).update();
}
}