MyBatis学习笔记

本文详细介绍了MyBatis框架,包括持久化、持久层的概念,解释了为何需要MyBatis及其优缺点。逐步指导如何搭建第一个MyBatis程序,涉及增删改查、配置解析、生命周期和作用域、属性名与字段名不一致的解决方案、日志系统以及分页和动态SQL的使用。还探讨了MyBatis的复杂查询、缓存机制及其执行流程,适合初学者和进阶者学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MyBatis

MyBatis 是一款优秀的持久层框架

它支持自定义 SQL、存储过程以及高级映射。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录。

官网: https://mybatis.org/mybatis-3/zh/index.html

优点:

  • 易于上手和掌握,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
  • sql写在xml里,便于统一管理和优化, 解除sql与程序代码的耦合。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

maven 仓库

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

1. 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 数据在内存中,断电即失
  • 有些数据不能丢失,不能存储在内存中
  • 内存贵
  • 需要将数据持久化到硬盘中

2. 持久层

Dao层,Service层,Controller层

  • 完成持久化工作的代码块
  • 层界限十分明显

3. 为什么需要MyBatis

  • 帮助程序员将数据存入到数据库中
  • 方便
  • 传统的JDBC代码太复杂了
  • 简化、框架、自动化

优点

  • 易于上手和掌握,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象模型要求的项目来说,相当完美。
  • sql写在xml里,便于统一管理和优化, 解除sql与程序代码的耦合。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql。

缺点

  • 关联表多时,字段多的时候,sql工作量很大。
  • sql依赖于数据库,导致数据库移植性差。
  • 由于xml里标签id必须唯一,导致DAO中方法不支持方法重载。
  • 对象关系映射标签和字段映射标签仅仅是对映射关系的描述,具体实现仍然依赖于sql。
  • DAO层过于简单,对象组装的工作量较大。
  • 不支持级联更新、级联删除。
  • Mybatis的日志除了基本记录功能外,其它功能薄弱很多。
  • 编写动态sql时,不方便调试,尤其逻辑复杂时。
  • 提供的写动态sql的xml标签功能简单,编写动态sql仍然受限,且可读性低。

4. 第一个MyBatis程序

会出现的问题

没有注册MyBatis配置
  • 在mybatis-config.xml文件中绑定
<configuration>
	<mappers>
        <mapper resource="life/leong/dao/UserMapper.xml"/>
    </mappers>
</configuration>
资源被过滤
  • 在maven中,java包下的xml 文件会被过滤

  • 一:在java同级的resource文件下,创建和dao同级的包,在该包下编写xml配置

  • 二:在maven中添加资源过滤配置

    • maven由于他的约定大于配置,可能会出现写的配置文件无法生效
    <!--在build中配置resource,来防止资源导出失败的问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
    

环境搭建

  • 创建一个maven项目

  • 导入依赖

    <dependencies>
       
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
    </dependencies>
    

创建一个模块

  • 创建mybatis-config.xml文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <!--configuration核心配置文件-->
    <configuration>
        <!--此处的default为切换数据源,对应environment中的id-->
        <environments default="development">
            <!--可以创建多个-->
            <environment id="development">
                <!--事务管理-->
                <transactionManager type="JDBC"/>
                <!--数据源-->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <!--数据库类型,连接ip和接口,数据库,编码集,&amp;转义&,SSL安全连接,时区--> 
                    <property name="url" value="jdbc:mysql://8.129.123.6:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true&amp;serverTimezone=Asia/Shanghai"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        <!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
        <mappers>
            <mapper resource="org/mybatis/example/BlogMapper.xml"/>
        </mappers>
    </configuration>
    
  • 编写MyBatis工具类

    public class MyBatisUtil {
    
        private static SqlSessionFactory sqlSessionFactory;
    
        static {
            try {
                // 使用MyBatis第一步:获取SqlSessionFactory,格式为官网固定
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 有了SqlSessionFactory,使用其获得SqlSession实例
         * SqlSession完全包含了面向数据库执行sql命令所需要的所有方法
         * @return
         */
        public static SqlSession getSqlSession() {
            // 参数为true,则表示自动提交事务
            return sqlSessionFactory.openSession();
        }
    }
    
  • 实体类

    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
  • UserDao接口类

    public interface UserDao {
        List<User> getUserList();
    }
    
  • UserMapper.xml映射配置文件

    <?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">
    
    <!--namespace->绑定一个对应的Dao/Mapper接口-->
    <!--id对应接口中的方法名字-->
    <!--resultType:返回结果-->
    <!--resultMap:返回集合-->
    <!--mapper中只能写sql中的注释-->
    <mapper namespace="life.leong.dao.UserDao">
        <select id="getUserList" resultType="life.leong.entity.User">
            select * from mybatis.user;
        </select>
    </mapper>
    
  • 测试类

    public class MyTest {
        @Test
        public void test() {
            // 获得SqlSession对象
            SqlSession sqlSession = MyBatisUtil.getSqlSession();
    
            // 执行sql
            // 方式一:getMapper
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> userList = mapper.getUserList();
    
            // 方式二:原来的方式,要根据方法的返回值来使用
            // List<User> userList = sqlSession.selectList("life.leong.dao.UserDao.getUserList");
    
            for (User user : userList) {
                System.out.println(user);
            }
            
            // 关闭SqlSession
            sqlSession.close();
        }
    }
    

5. 增删改查

mapper映射文件

namespace

命名空间,用来绑定一个对应的Dao/Mapper接口

<mapper namespace="life.leong.dao.UserMapper">
    ...
</mapper>

select

查询

<select id="getUserById" parameterType="int" resultType="life.leong.entity.User">
    select * from mybatis.user where id=#{id}
</select>
  • id 对应的为接口中的方法名
  • resultType 返回类型
  • parameterType 参数类型
  • #{} 取值,值对应的名称为接口中的参数名

insert

插入

<insert id="addUser" parameterType="life.leong.entity.User">
    insert into mybatis.user (id, name, pwd) values (#{id},#{name},#{pwd})
</insert>
  • 获得参数时,实体类可以直接使用变量名

  • 测试类

    @Test
    public void test03() {
        // 增删改需要提交事务
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.addUser(new User(2, "2", "2"));
        System.out.println(i);
    
        // 提交事务,增删改不提交事务,数据更改不成功
        sqlSession.commit();
    
        sqlSession.close();
    }
    
  • 增删改,需要提交事务才能更改数据

update

修改

<update id="updateUser" parameterType="life.leong.entity.User">
    update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
</update>

delete

删除

<delete id="deleteUserById" parameterType="int">
    delete from mybatis.user where id=#{id}
</delete>

6. 配置解析

核心配置文件

mybatis-config.xml

configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<configuration>
    <environments default="">
        <environment id="">
            <!--事务管理-->
            <transactionManager type=""/>
            <!--数据源-->
            <dataSource type="">
                <property name="" value=""/>
                <property name="" value=""/>
                <property name="" value=""/>
                <property name="" value=""/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
    <mappers>
        <!--resource-->
        <mapper resource=""/>
    </mappers>
</configuration>
  • 默认的事务管理器:JDBC
  • 连接池:POOLED

使用配置文件存储数据源

  • 创建db.properties

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
    username=root
    password=root
    
  • 引入外部配置文件

    <!--configuration核心配置文件-->
    <configuration>
        <!--引入外部配置文件-->
        <properties resource="db.properties"/>
    </configuration>
    
    • 可以在引入外部配置文件下添加标签
  • 标签的位置有明确的顺序,顺序错误将出错

类型别名配置

typeAliases

起别名,减少包路径冗余

<!--给实体类起别名-->
<typeAliases>
    <!--指定一个类-->
    <typeAlias type="life.leong.entity.User" alias="User"/>
    <!--指定一个包,包下类的别名为首字母小写的类名,大小写均可,小写用于区分-->
    <!--如果也要自定义别名,则需要在类上添加@Alias("")注解-->
    <package name="life.leong.entity"/>
</typeAliases>
  • 指定一个类,自定义别名
  • 指定扫描一个包,别名为该包下首字母小写的类名(大写也可,为区分,建议首字母小写)
    • 需要自定义别名,在类上添加@Alias("")注解即可
java类型中相应的别名

常见的java类型,mybatis内建了相应的别名

  • 基本数据类型前面添加"_"
  • 引用数据类型首字母均为小写,Integer的别名为int,int的别名为_int

设置

settings

核心配置文件中的设置,会改变MyBatis运行时的行为

参考官网:https://mybatis.org/mybatis-3/zh/configuration.html

  • cacheEnabled

在这里插入图片描述

  • lazyLoadingEnabled

在这里插入图片描述

  • logImpl

在这里插入图片描述

映射器

mappers

绑定映射文件,告诉MyBatis在哪查找映射语句

  • 相对类路径的资源引用:resource,推荐使用

    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
  • 完全限定资源定位符:弃用

    <mappers>
      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
      <mapper url="file:///var/mappers/BlogMapper.xml"/>
      <mapper url="file:///var/mappers/PostMapper.xml"/>
    </mappers>
    
  • 映射器接口实现类的完全限定类名:class

    • 当接口与mapper映射文件不在同一个包下时,无法查找到
    • 当接口与mapper映射文件名称不同时,无法查找到
    • 要求:接口与映射文件必须同名,必须在同一个包下
    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
  • 包内的映射器接口实现全部注册为映射器:name

    • 当接口与mapper映射文件不在同一个包下时,无法查找到
    • 当接口与mapper映射文件名称不同时,无法查找到
    • 要求:接口与映射文件必须同名,必须在同一个包下。与class一样
    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    

7. 生命周期和作用域

生命周期和作用域,是至关重要的,错误的使用会导致严重的并发问题

  • 流程

在这里插入图片描述

SqlSessionFactoryBuild

  • 一旦创建SqlSessionFactory就不需要它了

SqlSessionFactory

  • 一旦被创建,运行期间一直存在,类比数据库连接池
  • 没有任何理由丢弃它或重新创建另一个实例
  • 多次重建占用资源,是不好的
  • SqlSessionFactory 的最佳作用域是应用作用域
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例
  • 类比连接到连接池的一个请求
  • SqlSession 的实例不是线程安全的,因此是不能被共享的
  • SqlSession的最佳的作用域是请求或方法作用域
  • 不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行,也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中。比如 Servlet 框架中的 HttpSession
  • 每次请求完之后都要确保SqlSession关闭,否则资源被占用

在这里插入图片描述

  • 每一个mapper就代表一个具体的业务

8. 属性名与字段名不一致

实体类中的属性名与数据库中的字段名不一致,在之前的方法中,查询返回的结果为空

  • 实体类中的pwd更改为password
  • 在执行后,查询出来的结果中,password为null

解决方法

  • 方式一:起别名

    <select id="getUserList" resultType="life.leong.entity.User">
        select id,name,pwd as password from mybatis.user;
    </select>
    
  • 方式二:resultMap,结果集映射,处理简单的结果集

    参考官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html

    <!--结果集映射-->
    <resultMap id="userMap" type="life.leong.entity.User">
      	<!--column数据库中的字段,property实体类中的属性-->  
        <!--什么属性不一致,就只要映射这个属性即可-->
        <!--<result column="id" property="id"/>-->
        <!--<result column="name" property="name"/>-->
        <result column="pwd" property="password"/>
    </resultMap>
    
    <select id="getUserList" resultMap="userMap">
        select * from mybatis.user;
    </select>
    
  • 如果只是基本数据类型和String,则可以在接口中添加@param注解,绑定数据

  • 如果数据库是’_'连接,而在实体类中是驼峰式命名,则可以在核心配置文件中的设置里添加属性,实现自动转换

    <settings>
        <setting name="mapUnderscoreToCamelCase" value="STDOUT_LOGGING"/>
    </settings><sett
    

9. 日志

打印数据库操作

日志工厂

  • logImpl:指定MyBatis所用日志的具体实现,未指定时将自动查找

    • SLF4J
    • LOG4J (掌握)
    • LOG4J2
    • JDK_LOGGING
    • COMMONS_LOGGING
    • STDOUT_LOGGING (掌握)
    • NO_LOGGING
  • 具体使用哪个日志实现,在设置中

    • STDOUT_LOGGING 标准日志输出
具体实现
STDOUT_LOGGING

标准的日志输出,可以直接使用

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

在这里插入图片描述

LOG4J

Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件

配置文件参考博客:https://blog.youkuaiyun.com/eagleuniversityeye/article/details/80582140

  • 需要导入依赖
  • 可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,能够更加细致地控制日志的生成过程
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
  1. 依赖
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 新建配置文件: log4j.properties
### 将等级为DEBUG的日志信息输出到console和file这两个目的地 ###
# 等级可以自定义,使用INFO、DEBUG、ERROR都可以
# 在开发环境下日志级别要设置成 DEBUG ,生产环境设为 INFO 或 ERROR
log4j.rootLogger = DEBUG,console,file

### 输出信息到控制抬 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n

### 输出信息到文件 ###
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./logs/leong.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c]%m%n

### 日志输出级别 ###
log4j.logger.org.mybatis = DEBUG
log4j.logger.java.sql = DEBUG
log4j.logger.java.sql.Statement = DEBUG
log4j.logger.java.sql.ResultSet = DEBUG
log4j.logger.java.sql.PreparedStatement = DEBUG
  1. 设置日志实现
<settings>
    <!--标准的日志工厂实现-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
    <setting name="logImpl" value="LOG4J"/>
</settings>
  • 当核心配置中使用扫描包时,log4j会产生乱码,idea无法打开log文件
  1. 使用
import org.apache.log4j.Logger;
public class MyTest {
	
    // 使用的类
    static Logger logger = Logger.getLogger(MyTest.class);

    @Test
    public void test() {
        // 获得SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();

        // 执行sql
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = mapper.getUserList();

        for (User user : userList) {
            logger.info(user); // [INFO]标签输出到日志和控制台中
            // logger.debug("debug");
            // logger.error("error");
        }

        // 关闭SqlSession
        sqlSession.close();

    }
}
  • 日志级别:INFO、DEBUG、ERROR

10. 分页

mybatis使用分页

回顾mysql中的分页

分页用于缓解数据库夜里,给用户更好的体验

  • 语句

    select * from user limit 0,3 // 从第一个开始,展示三个
    select * from user limit 4 // 只有一个参数,从第一个展示个数
    
    limit 起始位置,页面大小
    -- 以下为limit分页
    -- limit (n-1)*page_size,page_size
    -- page_size 为 页面大小
    -- n 为 当前页
    -- (n-1)*page_size 为 起始值
    -- 总页数 为 数据总行数/页面大小
    

实现

  • 接口类

    // 分页实现查询
    List<User> getUserByLimit(Map<String, Integer> map);
    
  • mapper映射文件

    <select id="getUserByLimit" parameterType="map" resultType="life.leong.entity.User">
        select * from mybatis.user limit #{startIndex},#{pageSize}
    </select>
    
  • 测试类

    @Test
    public void getUserByLimitTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map map = new HashMap<String, Integer>();
        map.put("startIndex",0);
        map.put("pageSize", 2);
        List<User> userList = mapper.getUserByLimit(map);
        for (User user : userList) {
            logger.info(userList);
        }
        sqlSession.close();
    }
    
  • 升级分页就使用:limit (n-1)*page_size,page_size,n为当前页

11. 使用注解开发

在接口上添加注解,写入sql语句即可

  1. 接口

    @Select("select * from user")
    List<User> getUsers();
    
    • 当接口中有多个参数时,建议添加@Param注解,用来绑定select语句中的变量名,以Param注解为准,参数名更改不用对应也行.
    @Select("select * from user where id=#{id} and name=#{name}")
    List<User> getUserByIdName(@Param("id") int id,@Param("name") String Name);
    
    • 参数是实体类时,在注解语句中直接使用实体类的属性名即可
    @Update("update user set name=#{name},pwd=#{password} where id=#{id}")
    List<User> getUserByIdName(User user);
    
  2. 接口绑定

    <mappers>
        <mapper class="life.leong.dao.UserMapper"/>
    </mappers>
    
  • 实现的本质
    • 反射机制实现
    • 底层:动态代理

12. MyBatis执行流程

  1. Resources 获取加载全局配置文件
  2. 实例化SqlSessionBuilder构造器
  3. 解析配置文件流 XMLConfigBuilder
  4. Configuration所有的配置信息
  5. SqlSessionFactory实例化
  6. transaction事务管理
  7. 创建executor执行器
  8. 创建sqlSession
  9. 实现CRUD:实现失败,回滚 ----> 6 ,关闭
  10. 是否执行成功:执行失败,回滚 ----> 6 ,关闭
  11. 成功:提交事务
  12. 关闭

13. 复杂查询

多对一:关联

数据库中的多对一,例如多个学生对应一个老师

使用对象:association

  • 当多对一时,只需要一个对象,所以在resultMap中使用association标签
按照查询嵌套处理

查询学生所对应的老师:子查询

  • 实体类

    @Data
    public class Student {
        private int uid;
        private String name;
        private Teacher teacher;
    }
    @Data
    public class Teacher {
        private int tid;
        private String name;
    }
    
  • 接口类

    public interface StudentMapper {
        List<Student> getStudent();
        List<Teacher> getTeacher();
    }
    
  • 映射文件

    <mapper namespace="life.leong.dao.StudentMapper">
    
        <resultMap id="studentMap" type="Student">
            <result property="uid" column="uid"/>
            <result property="name" column="name"/>
            <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
        </resultMap>
        
        <select id="getStudent" resultMap="studentMap">
            select * from mybatis.student
        </select>
        
        <select id="getTeacher" resultType="Teacher">
            select * from mybatis.teacher where tid=#{tid}
        </select>
        
    </mapper>
    
    • resultMap中的 association 映射对象 类似于子查询,按照查询嵌套处理
    • property:对象名称(实体类中)
    • column:数据库中对应字段
    • javaType:对象类型
    • select:子查询语句
  • 测试类

    @Test
    public void getStudentTeacher() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudent();
        for (Student student : studentList) {
            logger.info(student);
        }
        sqlSession.close();
    }
    
按照结果嵌套处理

查询学生所对应的老师:联表查询

  • 不同的是映射文件

    <mapper namespace="life.leong.dao.StudentMapper">
        <select id="getStudent" resultMap="studentMap">
            select s.uid sid,s.name sname,t.name tname,t.tid tid
            from mybatis.student s,mybatis.teacher t
            where s.tid = t.tid
        </select>
    
        <!--按照结果嵌套处理-->
        <resultMap id="studentMap" type="Student">
            <result property="uid" column="sid"/>
            <result property="name" column="sname"/>
            <association property="teacher" javaType="Teacher">
                <result property="tid" column="tid"/>
                <result property="name" column="tname"/>
            </association>
        </resultMap>
    </mapper>
    
    • select标签中写完整的查询语句,并起别名(方便绑定)
    • resultMap中对结果进行嵌套
    • Teacher对象嵌套结果

一对多:集合

数据库中的一对多,例如一个老师教多个学生

使用集合:collection

  • 当一对多时,需要多个对象,组成集合,resultMap中使用collection标签
按照结果嵌套处理
  • 实体类

    @Data
    public class Student {
        private int uid;
        private String name;
        private int tid;
    }
    @Data
    public class Teacher {
        private int tid;
        private String name;
    
        private List<Student> students;
    }
    
  • 接口类

    public interface TeacherMapper {
        /**
         * 获得指定老师下的所有学生
         */
        Teacher getTeacherStudent(@Param("tid") int id);
    }
    
  • 映射文件

    <mapper namespace="life.leong.mapper.TeacherMapper">  
        <resultMap id="teacherMap" type="Teacher">
            <result property="tid" column="tid"/>
            <result property="name" column="tname"/>
            <!--复杂的属性需要单独处理:对象:association,集合:collection
            javaType="" 指定属性的类型
            ofType="" 集合中泛型的类型
            -->
            <collection property="students" ofType="Student">
                <result property="uid" column="sid"/>
                <result property="name" column="sname"/>
                <result property="tid" column="tid"/>
            </collection>
        </resultMap>
    </mapper>
    
  • 测试类

    @Test
    public void getTeacherStudent() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        logger.info(mapper.getTeacherStudent(1));
    
        sqlSession.close();
    }
    

14. 动态SQL

根据不同的条件,生成不同的sql语句

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

环境搭建

  • 数据库表

    CREATE TABLE `blog`(
       `id` int(4) NOT NULL COMMENT '博客id',
       `title` VARCHAR(100) NOT NULL COMMENT '博客标题',
       `author` VARCHAR(30) NOT NULL COMMENT '博客作者',
       `create_time` DATETIME NOT NULL COMMENT '创建时间',
       `views` INT(30) NOT NULL COMMENT '浏览量'
    )ENGINE=INNODB DEFAULT CHARSET=utf8;
    
  • 实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Blog {
        private int id;
        private String title;
        private String author;
        private Date createTime;
        private int views;
    }
    
  • 接口类

    public interface BlogMapper {
        int addBlog(Blog blog);
    }
    
  • 映射文件

    <?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="life.leong.mapper.BlogMapper">
        <insert id="addBlog" parameterType="Blog">
            insert into mybatis.blog (id, title, author, create_time, views)
            value (#{id},#{title},#{author},#{createTime},#{views})
        </insert>
    </mapper>
    
  • 测试类

    @Test
    public void addBlog() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        mapper.addBlog(new Blog(1, "1", "1", new Date(), 1));
        mapper.addBlog(new Blog(2, "2", "2", new Date(), 2));
        mapper.addBlog(new Blog(3, "3", "3", new Date(), 3));
        mapper.addBlog(new Blog(4, "4", "4", new Date(), 4));
        mapper.addBlog(new Blog(5, "5", "5", new Date(), 5));
    
        logger.info("成功插入数据");
        // sqlSession.commit(); // 工具类设置了自动提交事务
        sqlSession.close();
    }
    
    • 在工具类中,设置了自动提交事务
    public static SqlSession getSqlSession() {
        // 参数默认为false:需要手动提交事务,添加参数true:自动提交事务
        return sqlSessionFactory.openSession(true);
    }
    
  • 核心配置文件改动

    <settings>
        <!--自动转换驼峰命名和下划线分割,将属性名与字段名映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    

if

查询博客

使用 if 语句,实现sql的复用,传入不同的参数名或个数,实现不同的查询

  • 映射文件

    <select id="queryIfBlog" parameterType="map" resultType="Blog">
        select * from mybatis.blog where 1 
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </select>
    
    • 了让程序能在不同情况下正常跑,添加了where 1
    • 正常应该添加where标签,where下的if标签,会将第一个if的AND或者OR去掉,保证sql能正常执行
  • 测试类

    @Test
    public void queryIfBlogTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        Map map = new HashMap();
        // map.put("title", "1");
        // map.put("author", "2");
        List<Blog> blogs = mapper.queryIfBlog(map);
        logger.info("博客:" + blogs);
        sqlSession.close();
    }
    

choose (when, otherwise)

相当于java中的switch-catch语句,或者if-else语句

  • 映射文件

    <select id="queryChooseBlog" resultType="life.leong.entity.Blog">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = 1
                </otherwise>
            </choose>
        </where>
    </select>
    
    • 类似switch-catch或者if-else语句,选择其中一个

trim (where, set)

where
<select id="queryIfBlog" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <if test="title != null">
            and title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>
  • where下的if标签,会将第一个if的AND或者OR去掉,保证sql能正常执行
set

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

  • 映射文件

    <update id="updateBlog" parameterType="map">
        update mybatis.blog -- set title = #{title}
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where id = #{id}
    </update>
    
trim

自定义语句,前缀去除和后缀去除

  • 如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ... # 使用where,开头为and 或者 or将去除
</trim>
  • set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<trim prefix="SET" suffixOverrides=",">
  ... # 使用set,结尾为 , 将去除
</trim>
  • trim
<trim prefix="" prefixOverrides="" suffixOverrides="">
  ...
</trim>

SQL 片段

将sql语句中的部分语句抽取出来,方便复用

  • 使用sql标签抽取公共部分

    <sql if="if-test">
    	<if test="title != null">
        	title = #{title}
        </if>
    </sql>
    
  • 在需要的地方导入

    <where>
    	<include refid="if-test"/>
    </where>
    
  • 最好基于单表来定义SQL片段:即不要在片段中执行负责的语句

  • 不要存在where标签

  • 尽量只要if判断

foreach

对集合进行遍历,在需要多个相同的字段进行查询时

例如根据id查询

  • sql语句

    select * from blog where (id=1 or id=2 or id=3)
    -- 或者
    select * from blog where id in(1,2,3)
    
  • 接口方法

    List<Blog> queryForeachBlog(Map map);
    
  • 映射文件

    <select id="queryForeachBlog" parameterType="map" resultType="Blog">
        select * from mybatis.blog
        <where>
            <foreach collection="ids" item="id" open="and (" separator="or" close=")">
                id=#{id}
            </foreach>
        </where>
    </select>
    
    
    • collection:集合,名字和map中集合相对应的key相同
    • item:遍历的对象名
    • open:每条语句的前缀
    • separator:每段语句的间隔符
    • close:每条语句的后缀
  • 测试类

    @Test
    public void queryForeachBlogTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        List ids = new ArrayList(); // 集合
        Map map = new HashMap(); // Map
        ids.add(1);
        ids.add(2);
        map.put("ids", ids);
        List<Blog> blogs = mapper.queryForeachBlog(map);
        logger.info(blogs);
        sqlSession.close();
    }
    

15. MyBatis缓存

放在内存中的临时数据即为缓存

减小和数据库交互的次数,减少系统开销

经常查询,并且不经常改变的数据,使用缓存,。反之不使用

  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只有一级缓存开启:SqlSession级别的缓存,也称为本地缓存。
    • 二级缓存需要手动开启和配置,是基于namespace级别的缓存
    • 为了提高扩展性,MyBatis定义缓存接口Cache,通过实现Cache接口来定义二级缓存

一级缓存

SqlSession级别的缓存

  • 默认开启的一级缓存,只在一次SqlSession中有效
  • 当创建SqlSession时,即缓存生效,SqlSession.close()之后缓存失效。
  • 在缓存有效期间,即SqlSession创建后未关闭,查询两次同一个数据,第二次查询时没有进入数据库中查询,而是在缓存中读取
  • 增删改操作,可能会改变原来的数据,所以会必定会刷新缓存,再次查询还是要重新进入数据库查询
  • 手动刷新缓存sqlSession.clearCache();

二级缓存

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

参考官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache

  • 基于namespace级别的缓存,即一个名称空间(mapper),对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭,这个会话对应的一级缓存就失效了。此时一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的mapper查出的数据会放在自己对应的缓存中。
步骤
  1. 开启全局缓存

    <settings>
        <!--默认是true,但是为了阅读性,显示的开启全局缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 在namespace中使用

    <mapper namespace="">
    	<!--<cache/>-->
        <cache
          eviction="FIFO"
          flushInterval="60000"
          size="512"
          readOnly="true"/>
    </mapper>
    
    • 可以修改相应参数,也可用默认的

在这里插入图片描述

  1. 测试
    • 测试的时候,在测试类中创建两个SqlSession
    • 用一个SqlSession查询之后关闭这个SqlSession
    • 再用另一个SqlSession查询同样的数据,此数据是从二级缓存中得到的
    • 在没有使用策略时,即使用<cache/>,没有readOnly=“true”,需要给实体类序列化:实体列实现Serializable接口
    • 创建实体类时,都序列化实体类

缓存原理

  1. 用户先在mapper中的二级缓存进行查询
  2. 没有查询到,再到SqlSession中的一级缓存查询
  3. 没有查询到,最后再到数据库中查询
  4. 查询到的数据保存在一级缓存中
  5. SqlSession失效,一级缓存的数据保存到二级缓存中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值