MyBatis学习知识点大全(二)

简介: 教程来源 https://app-abdss1rim1oh.appmiaoda.com 系统讲解MyBatis核心映射机制与高级特性:涵盖基础/关联/集合/鉴别器四种结果映射方式;注解开发(@Select、@Results、动态SQL等);以及缓存(一二级)、延迟加载、插件机制和批量操作等实战要点,助力高效数据持久层开发。

四、结果映射

4.1 基础映射

<!-- 简单映射 -->
<resultMap id="simpleMap" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="age" column="age"/>
</resultMap>

<!-- 类型处理器指定 -->
<resultMap id="typedMap" type="User">
    <id property="id" column="id" jdbcType="BIGINT"/>
    <result property="username" column="username" jdbcType="VARCHAR"/>
    <result property="createTime" column="create_time" 
            jdbcType="TIMESTAMP" javaType="java.util.Date"/>
</resultMap>

4.2 关联映射(一对一)

java

// 实体类
public class User {
    private Long id;
    private String username;
    private UserDetail detail;  // 一对一关联
    // getter/setter
}

public class UserDetail {
    private Long id;
    private Long userId;
    private String address;
    private String phone;
    // getter/setter
}
xml

<!-- 嵌套查询(延迟加载) -->
<resultMap id="userWithDetail" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="detail" column="id" 
                 select="com.example.mapper.UserDetailMapper.selectByUserId"/>
</resultMap>

<!-- 嵌套结果 -->
<resultMap id="userWithDetailResult" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <association property="detail" javaType="UserDetail">
        <id property="id" column="detail_id"/>
        <result property="address" column="address"/>
        <result property="phone" column="phone"/>
    </association>
</resultMap>

<select id="selectUserWithDetail" resultMap="userWithDetailResult">
    SELECT u.id, u.username, d.id as detail_id, d.address, d.phone
    FROM user u
    LEFT JOIN user_detail d ON u.id = d.user_id
    WHERE u.id = #{id}
</select>

4.3 集合映射(一对多)

public class Department {
    private Long id;
    private String name;
    private List<User> users;  // 一对多关联
    // getter/setter
}
xml

<!-- 嵌套查询 -->
<resultMap id="deptWithUsers" type="Department">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="users" column="id" 
                select="com.example.mapper.UserMapper.selectByDeptId"
                ofType="User"/>
</resultMap>

<!-- 嵌套结果 -->
<resultMap id="deptWithUsersResult" type="Department">
    <id property="id" column="dept_id"/>
    <result property="name" column="dept_name"/>
    <collection property="users" ofType="User">
        <id property="id" column="user_id"/>
        <result property="username" column="username"/>
        <result property="age" column="age"/>
    </collection>
</resultMap>

<select id="selectDeptWithUsers" resultMap="deptWithUsersResult">
    SELECT d.id as dept_id, d.name as dept_name,
           u.id as user_id, u.username, u.age
    FROM department d
    LEFT JOIN user u ON d.id = u.dept_id
    WHERE d.id = #{id}
</select>

4.4 鉴别器映射

xml
<!-- 根据字段值映射不同的结果 -->
<resultMap id="vehicleMap" type="Vehicle">
    <id property="id" column="id"/>
    <result property="type" column="type"/>
    <discriminator javaType="int" column="type">
        <case value="1" resultMap="carMap"/>
        <case value="2" resultMap="truckMap"/>
    </discriminator>
</resultMap>

<resultMap id="carMap" type="Car" extends="vehicleMap">
    <result property="doorCount" column="door_count"/>
</resultMap>

<resultMap id="truckMap" type="Truck" extends="vehicleMap">
    <result property="loadCapacity" column="load_capacity"/>
</resultMap>

五、注解开发

5.1 基础注解

import org.apache.ibatis.annotations.*;
import java.util.List;

public interface UserAnnotationMapper {

    // @Select 注解
    @Select("SELECT * FROM user WHERE id = #{id}")
    User selectById(Long id);

    // 多个参数
    @Select("SELECT * FROM user WHERE username = #{username} AND age = #{age}")
    User selectByUsernameAndAge(@Param("username") String username, 
                                 @Param("age") Integer age);

    // @Insert 注解
    @Insert("INSERT INTO user (username, age, create_time) VALUES (#{username}, #{age}, NOW())")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    // @Update 注解
    @Update("UPDATE user SET username = #{username}, age = #{age} WHERE id = #{id}")
    int update(User user);

    // @Delete 注解
    @Delete("DELETE FROM user WHERE id = #{id}")
    int deleteById(Long id);

    // 返回 Map
    @Select("SELECT * FROM user WHERE id = #{id}")
    @MapKey("id")
    Map<Long, User> selectAsMap();

    // 动态 SQL 使用 Script 注解
    @Select({
        "<script>",
        "SELECT * FROM user",
        "<where>",
        "   <if test='username != null'>",
        "       AND username LIKE CONCAT('%', #{username}, '%')",
        "   </if>",
        "   <if test='age != null'>",
        "       AND age = #{age}",
        "   </if>",
        "</where>",
        "</script>"
    })
    List<User> findByCondition(@Param("username") String username, 
                                @Param("age") Integer age);
}

5.2 结果映射注解

// @Results 注解
@Select("SELECT id, username, age FROM user WHERE id = #{id}")
@Results(id = "userResultMap", value = {
    @Result(property = "id", column = "id", id = true),
    @Result(property = "username", column = "username"),
    @Result(property = "age", column = "age")
})
User selectById(Long id);

// 引用结果映射
@Select("SELECT * FROM user WHERE username = #{username}")
@ResultMap("userResultMap")
User selectByUsername(String username);

// 一对一关联
@Select("SELECT u.id, u.username, d.address FROM user u LEFT JOIN user_detail d ON u.id = d.user_id WHERE u.id = #{id}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "username", column = "username"),
    @Result(property = "detail", column = "id", 
            one = @One(select = "com.example.mapper.UserDetailMapper.selectByUserId"))
})
User selectUserWithDetail(Long id);

// 一对多关联
@Select("SELECT * FROM department WHERE id = #{id}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "name", column = "name"),
    @Result(property = "users", column = "id",
            many = @Many(select = "com.example.mapper.UserMapper.selectByDeptId"))
})
Department selectDeptWithUsers(Long id);

5.3 动态 SQL 注解

import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.annotations.DeleteProvider;

// SQL 构建器
public class UserSqlProvider {

    public String selectByCondition(Map<String, Object> params) {
        return new SQL() {
  {
            SELECT("*");
            FROM("user");
            if (params.get("username") != null) {
                WHERE("username LIKE #{username}");
            }
            if (params.get("age") != null) {
                WHERE("age = #{age}");
            }
            ORDER_BY("id DESC");
        }}.toString();
    }

    public String insert(User user) {
        return new SQL() {
  {
            INSERT_INTO("user");
            VALUES("username", "#{username}");
            VALUES("age", "#{age}");
            VALUES("create_time", "NOW()");
        }}.toString();
    }

    public String update(User user) {
        return new SQL() {
  {
            UPDATE("user");
            if (user.getUsername() != null) {
                SET("username = #{username}");
            }
            if (user.getAge() != null) {
                SET("age = #{age}");
            }
            SET("update_time = NOW()");
            WHERE("id = #{id}");
        }}.toString();
    }
}

// 使用 Provider
public interface UserProviderMapper {

    @SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
    List<User> selectByCondition(Map<String, Object> params);

    @InsertProvider(type = UserSqlProvider.class, method = "insert")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    @UpdateProvider(type = UserSqlProvider.class, method = "update")
    int update(User user);
}

六、高级特性

6.1 缓存机制
一级缓存(SqlSession 级别):

public class FirstLevelCacheDemo {

    public void testFirstLevelCache() {
        try (SqlSession session = MyBatisUtil.getSqlSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);

            // 第一次查询,从数据库查询
            User user1 = mapper.selectById(1L);

            // 第二次查询,从一级缓存获取
            User user2 = mapper.selectById(1L);

            // 返回同一个对象
            System.out.println(user1 == user2); // true

            // 执行更新操作会清空缓存
            mapper.update(new User(1L, "新名字", 30));

            // 再次查询,从数据库查询
            User user3 = mapper.selectById(1L);

            // 手动清空缓存
            session.clearCache();
        }
    }
}

二级缓存(Mapper 级别):

xml

<!-- 开启二级缓存 -->
<configuration>
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

<!-- Mapper XML 中配置二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 基础缓存配置 -->
    <cache/>

    <!-- 高级缓存配置 -->
    <cache 
        eviction="LRU"           <!-- 缓存淘汰策略 -->
        flushInterval="60000"    <!-- 刷新间隔 60秒 -->
        size="512"               <!-- 缓存对象数量 -->
        readOnly="false"/>       <!-- 是否只读 -->
</mapper>
java

// 实体类需要实现 Serializable
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

// 在 Mapper 接口上配置缓存
@CacheNamespace(eviction = FifoCache.class, flushInterval = 60000, size = 512)
public interface UserMapper {
    // ...
}

// 使用参照缓存
@CacheNamespaceRef(UserMapper.class)
public interface RoleMapper {
    // 使用 UserMapper 的缓存配置
}

6.2 延迟加载

xml
<!-- 配置延迟加载 -->
<configuration>
    <settings>
        <!-- 开启延迟加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 关闭积极加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 延迟加载触发方法 -->
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
</configuration>
xml
<!-- 映射文件中配置延迟加载 -->
<resultMap id="userWithOrders" type="User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <!-- fetchType 覆盖全局配置 -->
    <collection property="orders" column="id" 
                select="com.example.mapper.OrderMapper.selectByUserId"
                fetchType="lazy"/>
</resultMap>
java
// 使用代理对象触发加载
public class LazyLoadingDemo {
    public void testLazyLoading() {
        try (SqlSession session = MyBatisUtil.getSqlSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.selectById(1L);

            // 此时只加载了用户信息
            System.out.println(user.getUsername());

            // 访问订单时触发加载
            List<Order> orders = user.getOrders();  // 执行额外查询

            // 判断是否已加载
            boolean loaded = PersistenceUtils.isLoaded(user.getOrders());
        }
    }
}

6.3 插件机制

java
// 实现 Interceptor 接口
@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})
public class PageInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];

        // 分页逻辑
        if (parameter instanceof Page) {
            Page page = (Page) parameter;
            // 获取原始 SQL
            BoundSql boundSql = ms.getBoundSql(parameter);
            String sql = boundSql.getSql();

            // 添加分页 SQL
            String pageSql = sql + " LIMIT " + page.getOffset() + "," + page.getLimit();

            // 创建新的 BoundSql
            BoundSql newBoundSql = new BoundSql(
                ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);

            // 创建新的 MappedStatement
            MappedStatement newMs = copyMappedStatement(ms, newBoundSql);
            args[0] = newMs;
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 读取配置
        String dialect = properties.getProperty("dialect");
    }

    private MappedStatement copyMappedStatement(MappedStatement ms, BoundSql boundSql) {
        // 复制 MappedStatement 的实现
        // ...
        return ms;
    }
}

配置插件:

xml
<configuration>
    <plugins>
        <plugin interceptor="com.example.plugin.PageInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
</configuration>

6.4 批量操作

java
public class BatchDemo {

    // 使用 ExecutorType.BATCH
    public void batchInsert(List<User> users) {
        try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserMapper mapper = session.getMapper(UserMapper.class);

            for (User user : users) {
                mapper.insert(user);
            }

            session.commit();
        }
    }

    // 使用 foreach 批量插入
    public void batchInsertWithForeach(List<User> users) {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            mapper.batchInsert(users);
            session.commit();
        }
    }

    // 批量更新
    public void batchUpdate(List<User> users) {
        try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
            UserMapper mapper = session.getMapper(UserMapper.class);

            for (User user : users) {
                mapper.update(user);
            }

            session.commit();
        }
    }

    // 批量删除
    public void batchDelete(List<Long> ids) {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            UserMapper mapper = session.getMapper(UserMapper.class);
            mapper.deleteByIds(ids);  // 使用 foreach 实现
            session.commit();
        }
    }
}

来源:
https://app-abdss1rim1oh.appmiaoda.com

相关文章
|
17天前
|
运维 监控 Java
从单体地狱到微服务天堂:架构演进与拆分的核心原则+全链路实战落地
本文系统阐述微服务本质与渐进式演进路径:破除“盲目拆分”误区,强调业务驱动;详解单体→模块化→垂直拆库→非核心服务→核心服务的五步安全演进;提炼高内聚低耦合、数据自治、业务域对齐等七大落地原则;辅以电商实战代码与避坑指南。
260 6
|
18天前
|
存储 算法 关系型数据库
吃透分布式 ID:雪花算法、号段模式的底层逻辑与全场景架构避坑
本文深度解析分布式ID两大主流方案——雪花算法与号段模式,涵盖核心设计准则(唯一性、趋势递增、高性能等)、底层原理、代码实现、6大生产避坑指南及场景化选型建议,助你构建稳定可靠的分布式ID服务。
336 3
|
20天前
|
算法 Java 关系型数据库
JVM GC 深度破局:G1 与 ZGC 底层原理、生产调优全链路实战
本文深度解析JDK17主流GC:G1(默认,兼顾吞吐与延迟)与ZGC(革命性低延迟,STW&lt;1ms)。涵盖核心理论(可达性分析、三色标记)、内存布局、全流程机制(SATB写屏障 vs 染色指针+读屏障)、关键参数调优及生产选型指南,助你精准定位性能瓶颈,高效优化JVM。
430 4
|
24天前
|
存储 缓存 监控
JVM 运行时数据区全解:从底层原理到 OOM 根因定位全链路实战
JVM运行时数据区是Java内存管理的核心,分为线程私有区域(程序计数器、虚拟机栈、本地方法栈)和线程共享区域(堆、方法区)。不同区域有明确的OOM触发规则:堆内存不足引发Java heap space异常,元空间不足导致Metaspace异常,直接内存溢出表现为Direct buffer memory错误。排查OOM需结合异常类型、堆dump、GC日志等现场数据,使用MAT等工具分析内存泄漏点。
411 1
|
25天前
|
安全 Java API
Java 8+ 核心高阶特性全解:Lambda、Stream、CompletableFuture 从底层原理到生产最佳实践
本文深入解析Java8至17版本中Lambda表达式、Stream流和CompletableFuture三大核心特性的底层原理与生产实践。Lambda表达式基于invokedynamic指令实现,性能优于匿名内部类;Stream流通过惰性求值机制实现高效集合操作,支持并行处理;CompletableFuture提供完善的异步编程能力,支持任务组合与超时控制。
278 1
|
1月前
|
缓存 Java 开发者
吃透 Spring Bean 生命周期:从源码底层到实战落地
本文深度解析Spring 6.2.3 Bean生命周期,涵盖BeanDefinition注册、实例化、属性填充、Aware回调、BeanPostProcessor前后置处理、初始化(@PostConstruct/InitializingBean/init-method)、AOP代理、单例缓存及销毁全流程,结合源码、实战示例与生产问题排查,助你彻底掌握IoC核心机制。
455 3
|
12天前
|
存储 安全 Java
别让你的 Java 应用裸奔!OWASP Top10 全漏洞原理、复现与一站式防护方案
本文详解Java应用十大安全风险(OWASP Top10),涵盖失效访问控制、加密失效、注入攻击等核心漏洞的原理、复现代码及防护方案,结合Spring生态最佳实践,助力开发者构建高安全性企业级系统。
259 1
|
16天前
|
JavaScript 前端开发 API
VUE前端初级新手知识大全(一)
教程来源 https://app-a6nw7st4g741.appmiaoda.com/ Vue.js是轻量、易上手的渐进式前端框架,专注视图层,支持声明式编程与MVVM模式。本文系统讲解入门知识:从CDN/CLI环境搭建、核心语法(插值、指令、ref/reactive)、响应式原理,到计算属性与侦听器,助你快速构建首个Vue应用。
|
4月前
|
安全 Java API
Java日期处理完全指南(新手也能轻松掌握的Java时间格式化与日期API教程)
教程来源https://www.vpshk.cn/本文介绍Java 8引入的java.time包,详解LocalDateTime、LocalDate等类的使用,涵盖获取当前时间、格式化、解析字符串及日期运算,助你轻松掌握现代Java日期处理方法,适合初学者快速上手。
|
SQL 网络协议 关系型数据库
mysql 连接超时wait_timeout问题解决
com.mysql.jdbc.CommunicationsException: The last packet successfully received from the server was58129 seconds ago.The last packet sent successfully to the server was 58129 seconds ago, which is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or tes