MyBatis

MyBatis

一、MyBatis概述

1. 服务端框架

  • 服务端的三层架构(体现程序高内聚、低耦合,将复杂的任务进行拆分)

    持久层框架:负责对数据库进行CRUD

    常见框架:Hibernate、MyBatis、Spring JDBC

    业务层框架:负责调用持久层处理数据和业务逻辑

    常见框架:Spring

    表现层框架:负责接收参数、调用业务层处理业务、视图跳转

    常见框架:Struts2、Spring MVC

  • 三层框架中处理参数的方式:将接收的参数封装成一个JavaBean

  • 常见的服务端框架组合

    • SSH:Struts2、Spring、Hibernate
    • SSM:Spring MVC、Spring、MyBatis

2. 持久层框架

  • 分类
    • 半自动:MyBatis
    • 全自动:Hibernate
  • 区别:是否需要手动编写SQL,半自动好处是可以对SQL进行优化

3. MyBatis特点

  • MyBatis是基于ORM的半自动化轻量级的持久层框架,它对JDBC的操作数据库的过程进行封装

    ORM(Object Relational Mapping)对象关系映射;

    一个实体类对应一张表,一个实体类对象对应表中的一条记录;

    所以ORM思想实现了可以通过实体的变化映射为表的变化

    轻量级:程序在启动期间所需要消耗的资源相对较少

  • MyBatis将实体的变化翻译成SQL脚本,执行到数据库中去,也就是将实体的变化映射到了表的变化

  • MyBatis对JDBC进行了封装,屏蔽了JDBC API底层访问细节,开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等JDBC繁杂的过程

  • MyBatis解决了JDBC在项目中的问题:

    • 数据库配置信息硬编码问题:当需要改变配置信息时,需要改动源代码,就要重新打包部署项目

      解决思路:使用配置文件

    • 频繁创建释放数据库连接

      解决思路:使用数据库连接池

    • SQL语句硬编码问题

      解决思路:使用配置文件

    • 返回的结果集需要手动封装成JavaBean

      解决思路:反射和内省

二、MyBatis的传统开发方式

1. 开发步骤

  1. 创建数据库和表

  2. 创建Maven工程,导入相关依赖(MySQL驱动、MyBatis、JUnit)

  3. 编写实体类(成员变量名必须与数据库表中的字段相同)

  4. 编写映射配置文件

    命名规则:实体类名+Mapper.xml

  5. 编写核心配置文件:主要完成数据库环境配置映射关系配置的引入

    命名规则:SqlMapConfig.xml

  6. 用MyBatis API编写测试代码

2. 开发示例

  • 基于MyBatis进行CRUD操作

    编写映射配置文件

    mapper中使用了不同的子标签select、insert、update、delete分别定义了CRUD的SQL操作

    mapper标签属性namespace和子标签中的id共同组成唯一标识

    resultType属性指的是使用select语句查询后的结果将要封装为JavaBean对象的类型,一般写全类名;

    parameterType属性指的是用于insert、update、delete语句中,传入对象的类型,一般写全类名,

    SQL语句中占位符用#{xxx}表示,需要指定传入对象中的字段名,若传入的仅仅是像Integer或String类型的数据,则占位符中的参数名可以设置任意的名字

    <?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="UserMapper">
        <!--查询所有-->
        <select id="findAll" resultType="com.example.domain.User">
            select * from user
        </select>
    
        <insert id="saveUser" parameterType="com.example.domain.User">
            insert into user(username, birthday, sex, address) values (#{username}, #{birthday},#{sex}, #{address})
        </insert>
    
        <update id="update" parameterType="com.example.domain.User">
            update user set username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address} where id = #{id}
        </update>
    
        <delete id="delete" parameterType="java.lang.Integer">
            delete from user where id = #{id}
        </delete>
    </mapper>
    

    编写核心配置文件

    核心配置文件与映射配置文件中的约束头不同

    需要在核心配置文件中引入映射配置文件

    在XML中用&amp;表示&,在properties配置文件中正常使用&

    <?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>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"></transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"></property>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis_db?useSSL=false&amp;characterEncoding=UTF-8"></property>
                    <property name="username" value="root"></property>
                    <property name="password" value="sqlcony"></property>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper resource="UserMapper.xml"></mapper>
        </mappers>
    </configuration>
    

    写测试类测试CRUD

    主要方式:通过SqlSessionFactory获取SqlSession对象,根据需求调用SqlSession中的CRUD方法(传入映射配置中的namespace.id字符串),调用SqlSession中的方法commit手动提交事务,调用SqlSession中的方法close关闭资源

    在insert、update、delete操作中需要调用SqlSession中的方法commit手动提交事务,select操作不用提交事务

    public class MyBatisTest {
        @Test
        public void mybatisQuickStart() throws IOException {
            // 加载核心配置文件
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            // 获取sqlSessionFactory工厂对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            // 获取sqlSession会话对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 执行sql参数
            List<User> users = sqlSession.selectList("UserMapper.findAll");
            // 遍历打印结果
            for (User user : users) {
                System.out.println(user);
            }
            // 关闭资源
            sqlSession.close();
        }
    
        @Test
        public void testSave() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            User user = new User();
            user.setUsername("Jack");
            user.setBirthday(new Date());
            user.setSex("男");
            user.setAddress("Chicago");
    
            sqlSession.insert("UserMapper.saveUser", user);
            sqlSession.commit();
            sqlSession.close();
        }
    
        @Test
        public void testUpdate() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            User user = new User();
            user.setId(3);
            user.setUsername("Jessica");
            user.setBirthday(new Date());
            user.setSex("女");
            user.setAddress("Detroit");
    
            sqlSession.update("UserMapper.update", user);
            sqlSession.commit();
            sqlSession.close();
        }
    
        @Test
        public void testDelete() throws IOException {
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    
            sqlSession.delete("UserMapper.delete", 4);
            sqlSession.commit();
            sqlSession.close();
        }
    }
    

三、MyBatis的核心配置文件

1. 核心配置文件概述

2. environments标签

  • 在实际开发中可以有生产环境、测试环境、开发环境,所以在environments中可以定义多个environment

    通过id属性指定环境名称

  • environment中的子标签包括transactionManager和dataSource

    • transactionManager可以指定事务管理类型,type属性可以指定JDBC或MANAGED

    • dataSource可以指定数据源的类型,type属性可以是UNPOOLED(非连接池)、POOLED(连接池)、JDNI,在dataSource中可以定义property子标签用来指定数据库的连接信息

3. properties标签

  • 在实际开发中environment中的dataSource中的property中的配置信息要单独分离写在项目resources文件夹中的.properties文件中,在核心配置文件中使用properties标签引入即可,environment中的dataSource的property中name属性指定.properties文件中的key,value属性使用${key}来引入.properties文件中的value值

4. typeAliases标签

  • 为了简化映射配置文件需要用到的JavaBean类型,MyBatis提供了一些基本类型的别名

  • 除了使用MyBatis提供的基本类型的别名,还可以使用typeAliases标签自定义别名

    通过使用package还可以对同一个包下实体类起别名,别名就是类名,且不区分大小写

    <!--设置别名-->
    <typeAliases>
        <!--方式一:给单个实体起别名-->
        <!-- <typeAlias type="com.example.domain.User" alias="user"></typeAlias>-->
        <!--方式二:批量起别名 别名就是类名,且不区分大小写-->
        <package name="com.example.domain"/>
    </typeAliases>
    

5. mappers标签

  • 主要用于在核心配置文件中加载映射配置文件

    <mappers>
        <mapper resource="UserMapper.xml"></mapper>
    </mappers>
    
  • mappers标签中加载映射的三种方式

四、MyBatis API和基本原理

  • MyBatis主要的配置文件

    核心配置文件主要内容:properties标签引入数据库连接信息配置文件、环境标签(配置开发环境)、mappers标签引入映射配置文件

    映射配置文件主要内容:SQL操作标签(主要包含要执行的SQL语句、传入参数parameterType、传出参数resultType或resultMap)

  • 工作流程

    1. InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml")

      将核心配置文件以输入流的形式加载到内存中

    2. new SqlSessionFactoryBuilder().build()

      使用dom4j解析了配置文件,将映射配置文件中的配置封装为了一个一个的MappedStatement对象

      将这些MappedStatement对象封装成map集合,然后创建了SqlSessionFactory对象

      MappedStatement对象中包含了id、resultType、parameterType等映射配置信息

      MappedStatement对象中的key由statement id(namespace + id)组成

    3. sqlSessionFactory.openSession()

      openSession有两个重载方法,空参是默认开启事务并且需要手动提交,传入布尔值true代表自动提交事务

    4. sqlSession.selectList("UserMapper.findAll")

      通过SqlSession调用方法本身不会直接操作数据库,需要委派给执行器executor(使用了JDBC实现)来操作

      通过SqlSession调用方法中传入的是key,会在map中取出对应的value(MappedStatement类型对象)

  • 示例Code

    public class MyBatisTest {
        @Test
        public void mybatisQuickStart() throws IOException {
            // 加载核心配置文件
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            // 获取sqlSessionFactory工厂对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            // 获取sqlSession会话对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 执行sql参数
            List<User> users = sqlSession.selectList("UserMapper.findAll");
            // 遍历打印结果
            for (User user : users) {
                System.out.println(user);
            }
            // 关闭资源
            sqlSession.close();
        }
    }
    

五、MyBatis在Dao层的开发方式

1. 在Dao层的传统开发方式

  • 通过接口、接口实现类、核心配置文件、映射配置文件的开发方式
  • 缺点:接口实现类中读取配置文件和调用接口方法时传入参数存在硬编码问题

2. 在Dao层的代理开发方式

  • 通过接口、核心配置文件、映射配置文件的开发方式

  • 优点:实现了消除接口实现类,直接使用接口中的方法与映射配置文件来动态生成接口实现类的对象(代理对象)

    传统开发方式以下代码固定在实现类中,代理开发方式直接可以在测试类中获取接口类型对象并调用接口中的指定方法

    InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    XxxMapper mapper = sqlSession.getMapper(XxxMapper.class);
    
  • 代理开发方式的原理:底层实现是JDK动态代理,使用getMapper方法获取代理对象,代理对象调用接口中的任意方法,会执行底层的invoke方法,invoke方法的内部还是去调用SqlSession类型的方法去完成CRUD

  • 实现方式

    • 目录结构

      com.example.mapper下创建xxxMapper接口文件,相应地在项目resources文件夹下com.example.mapper下创建映射配置文件xxxMapper.xml,在resources根目录下创建核心配置文件SqlMapConfig.xml

      xxx为java目录下com.example.domain下的实体类名

    • 3个主要文件的格式

      接口文件:示例UserMapper.java

      public interface UserMapper {
          /**
           * 查询所有用户
           * @return
           */
          public List<User> findAll() throws IOException;
      
          /**
           * 根据id查询用户
           * @param id
           * @return
           */
          public User findUserById(int id);
      
          /**
           * 根据id和姓名查询用户
           * @param id
           * @param username
           * @return
           */
          public List<User> findByIdAndUsername1(int id, String username);
      
          public List<User> findByIdAndUsernameIf(User user);
      
          public void updateIf(User user);
      }
      

      映射配置文件:示例UserMapper.xml

      注意:namespace属性需要加上项目resources文件夹下的全类名

      <?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">
          <select id="findUserById" parameterType="int" resultType="user">
          
          </select>
      
          <update id="updateIf" parameterType="user">
      
          </update>
      </mapper>
      

      核心配置文件:示例sqlMapperConfig.xml

      注意:使用mappers标签引入映射配置文件,resource属性中对映射配置文件需要加上项目resources文件夹下的包含全路径的文件名

      <?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>
          <properties resource="jdbc.properties"></properties>
      
          <typeAliases>
      <!--        <typeAlias type="com.example.domain.User" alias="user"></typeAlias>-->
              <package name="com.example.domain"></package>
          </typeAliases>
      
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"></transactionManager>
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"></property>
                      <property name="url" value="${jdbc.url}"></property>
                      <property name="username" value="${jdbc.username}"></property>
                      <property name="password" value="${jdbc.password}"></property>
                  </dataSource>
              </environment>
          </environments>
      
          <mappers>
      <!--        <mapper resource="UserMapper.xml"></mapper>-->
              <mapper resource="com/example/mapper/UserMapper.xml"></mapper>
          </mappers>
      </configuration>
      
    • 测试代理开发方式:示例MyBatisTest.java

      在测试类中使用JUnit进行测试

      主要过程:通过SqlSessionFactory获取SqlSession对象,调用getMapper方法(传入对应接口的Class对象)获取到接口类型的对象(代理对象),代理对象可以调用接口中的方法执行SQL操作(insert、update、delete需要手动提交事务),关闭SqlSession对象

      @Test
      public void testUpdateIF() throws IOException {
          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
          SqlSession sqlSession = sqlSessionFactory.openSession();
      
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          User user = new User();
          user.setId(10);
          user.setUsername("Jeff");
          mapper.updateIf(user);
          sqlSession.commit();
      }
      

六、复杂映射

1. resultMap标签

  • resultMap和resultType相同特性

    都可以作为映射配置文件中SQL操作标签中的属性表示输入参数的类型,其中resultType为指定的实体类型的全类名

  • 如果实体的属性名与表中字段名不一致,可以使用resutlMap实现手动封装到实体类中

    在映射配置文件中,添加指定id属性的resultMap标签,同时为select标签的resultMap属性添加属性值为resultMap标签的id

2. 多个参数动态SQL

  • 三种方式:
    1. 在接口方法中参数列表包含多个参数,使用#{arg0}#{param1}开始的占位符用于SQL语句
    2. 在接口方法中参数列表包含多个参数,在接口方法中的每个形参前面添加@Param(xxx)注解,在SQL中占位符使用#{xxx}
    3. 在接口的方法中传入的参数是一个JavaBean,使用pojo对象传递参数,在映射配置文件中操作SQL标签中添加parameterType属性(属性值为传入对象类型的全类名),SQL语句占位符#{}中使用pojo对象的字段名

3. 模糊查询中使用的占位符和取值符

  • #{}${}区别

    占位符#{}:可以将parameterType传入的内容自动进行Java类型和JDBC类型转换

    如果 parameterType 传输单个简单类型值, #{} 括号中可以是value 或其它名称

    取值符${}:可以将parameterType传入的内容拼接在SQL中且不进行JDBC类型转换

    如果 parameterType 传输单个简单类型值, ${} 括号中只能是value

    Java类型和JDBC类型转换是指实体类中的成员变量类型和数据库表中的字段类型转换

    自动转换会为传入的参数添加'',无需在SQL字符串的#{}外添加''

    非自动转换需要在SQL字符串中为${}外手动添加''

  • #{}${}相同特性

    #{}${}都可以接收简单类型值或pojo(Plain Old Java Object简单Java对象)属性值

七、映射配置文件中的SQL操作

1. SQL操作后返回主键值到实体对象中

  • 案例:调用接口抽象方法向其中传入实体类对象,insert操作后,将数据库表中新增行主键值放入实体对象中

    解决方式1:为映射配置中的insert标签添加属性useGeneratedKeys、keyProperty

    解决方式1仅支持主键自增的数据库如MySQL、SQL Server,不支持Oracle

    解决方式2:为映射配置中的insert标签添加selectKey子标签

2. 动态SQL条件部分的拼接

  • if标签:在映射配置文件中条件查询时,需要拼接多个参数条件,if标签通常和where标签搭配使用(代替了where后面加1=1防止错误)

    查询的条件中若有if中的字段值,就拼接到SQL上

    <select id="findByIdAndUsernameIf" parameterType="user" resultType="user">
        select * from user
        <where>
            <if test="id != null">
                AND id = #{id}
            </if>
            <if test="username != null">
                AND username = #{username}
            </if>
        </where>
    </select>
    
  • set标签:在映射配置文件中update时,可能需要set多个字段,使用set标签配合if可以为符合条件的每个if添加set,还能去除最后的一组字段后面的,

    注意:在update标签中set标签后要加where条件,防止全面更新

    <update id="updateIf" parameterType="user">
        update user
        <set>
            <if test="username != null">
                username = #{username},
            </if>
            <if test="birthday != null">
                birthday = #{birthday},
            </if>
            <if test="sex != sex">
                sex = #{sex},
            </if>
            <if test="address">
                address = #{address},
            </if>
        </set>
        where id = #{id}
    </update>
    
  • foreach标签:用于select标签中,为select语句拼接SQL关键字in

    接口方法中可以传入集合、数组、pojo代表in关键字的范围,foreach中的collection属性值根据传入方法的参数类型而变化

3. 对重复SQL的提取和复用

  • sql标签:映射文件中可将重复的 SQL 提取出来,使用时用 include 引用即可,最终达到 SQL 重用的目的

    使用方式:在映射配置文件中mapper标签下定义sql标签,在CRUD标签中添加include子标签来引用sql标签中的内容

    使用场景:在映射配置文件中的多个select标签下包含重复的SQL语句片段,可以使用sql标签进行抽取

八、核心配置文件中安装插件

  • MyBatis可以安装第三方插件来对功能进行扩展

    安装主要步骤:添加Maven依赖,向核心配置文件中添加plugins标签

  • 案例:核心配置文件中使用plugins标签安装插件PageHelper(分页工具)

    • PageHelper工具中的常用方法

      • 静态方法PageHelp.startPage():在调用接口类型对象的方法前,设置分页参数

      • PageInfo对象的方法:总条数getTotoal()、总页数getPages()、...

        PageInfo对象构造方法需要传入SQL查询结果的List集合

        PageInfo对象的泛型需要指定当前项目的实体类型

    • 示例Code

      添加Maven依赖

      <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
      <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper</artifactId>
          <version>3.7.5</version>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/com.github.jsqlparser/jsqlparser -->
      <dependency>
          <groupId>com.github.jsqlparser</groupId>
          <artifactId>jsqlparser</artifactId>
          <version>0.9.1</version>
      </dependency>
      

      核心配置文件中添加plugins标签(注意标签的顺序必须在environments之前)

      <plugins>
          <plugin interceptor="com.github.pagehelper.PageHelper">
              <property name="dialect" value="mysql"></property>
          </plugin>
      </plugins>
      

      在测试类中测试

      startPage(当前的页数,当前页中显示的数据个数)

      @Test
      public void testFindAllResultMap() throws IOException {
          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
          SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
          SqlSession sqlSession = sqlSessionFactory.openSession();
      
          PageHelper.startPage(1, 3);
      
          UserMapper mapper = sqlSession.getMapper(UserMapper.class);
          List<User> allResultMap = mapper.findAllResultMap();
          for (User user : allResultMap) {
              System.out.println(user);
          }
      }
      

九、多表查询和嵌套查询

1. 一对一(多对一)

  • 多对一特例:一个用户可以有多个订单,从数据层面看一个订单只从属于一个用户,所以可以将多对一看成一对一

  • 案例:查询一个订单,与此同时查询出该订单所属的用户

    实体类实现方式:在多方实体(orders)中,添加一方属性

    实体的属性名与表中字段名不一致,可以使用resutlMap实现手动封装到实体类中

    id属性代表实体类中的字段,column代表数据库表中的字段

    association中子标签id的column属性值会和resultMap中的id的column属性值同名重复,可以使用orders表的

    外键uid(=主键id)作为association中子标签id的column属性值;

    也可以通过为SQL起别名的方式,来区分查询结果中的两个重名的id字段

2. 一对多

  • 一对多:一个用户可以有多个订单

  • 案例:查询一个用户,与此同时查询出该用户具有的订单

    实体类实现方式:在一方实体(user)中,添加多方的集合作为属性

    实体的属性名与表中字段名不一致,可以使用resutlMap实现手动封装到实体类中

3. 多对多

  • 用户表(user)和角色表(sys_role)的关系为,一个用户有多个角色,一个角色被多个用户使用

  • 案例:查询用户同时查询出该用户的所有角色

    实体类实现方式:在一方实体(user)中,添加多方的集合作为属性

    实体的属性名与表中字段名不一致,可以使用resutlMap实现手动封装到实体类中

4. 嵌套查询

  • 嵌套查询概述

    嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用 MyBatis 的语法嵌套在一起

  • 嵌套查询优缺点:

    优点:简化多表查询操作

    缺点:执行多次sql 语句,浪费数据库性能

  • 一对一(多对一)案例:查询一个订单,与此同时查询出该订单所属的用户

    select标签中当传入参数类型为基本数据类型或String类型时,parameterType属性可以省略

  • 一对多案例:查询一个用户,与此同时查询出该用户具有的订单

    SQL分两步执行

    -- 先查询用户
    SELECT * FROM `user`;
    -- 再根据用户 id 主键,查询订单列表
    SELECT * FROM orders where uid = #{用户 id};
    

    映射配置文件UserMapper.xml

    <resultMap id="userOrderMap" type="user">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="birthday" column="birthday"></result>
        <result property="sex" column="sex"></result>
        <result property="address" column="address"></result>
    
        <collection property="ordersList" ofType="orders" select="com.example.mapper.OrderMapper.findByUid" column="id">			</collection>
    </resultMap>
    
    <select id="findAllWithOrder2" resultMap="userOrderMap">
        select * from user
    </select>
    

    collection标签中:

    property属性代表实体类User中定义的private List<Orders> ordersList;

    ofType属性代表property中元素的全类名(核心配置文件中定义了别名)

    select属性代表statement id

    column属性代表传入参数的值(User表中id字段)

  • 多对多案例:查询用户同时查询出该用户的所有角色

    SQL分两步执行

    -- 先查询用户
    SELECT * FROM `user`;
    -- 再根据用户 id 主键,查询角色列表
    SELECT * FROM role r INNER JOIN user_role ur ON r.`id` = ur.`rid`
    WHERE ur.`uid` = #{用户 id};
    

    映射配置文件UserMapper.xml

    <!--多对多嵌套查询-->
    <resultMap id="userAndRoleMap" type="user">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="birthday" property="birthday"></result>
        <result column="sex" property="sex"></result>
        <result column="adress" property="address"></result>
        <!--根据用户 id,查询角色列表-->
        <collection property="roleList" column="id" ofType="role"
    select="com.example.mapper.RoleMapper.findByUid"></collection>
    </resultMap>
    
    <select id="findAllWithRole" resultMap="userAndRoleMap">
    	SELECT * FROM `user`
    </select>
    

十、MyBatis加载策略

1. 延迟加载(懒加载)概述

  • 延迟加载(lazy):当前查询对象中的关联对象,在查询时不立即加载

    立即加载(eager):当前查询对象中的关联对象,在查询时立即加载

  • 延迟加载需要基于嵌套查询

  • 使用场景:一对多、多对多

2. 局部延迟加载

  • 设置延迟加载后只有当调用查询结果的实体类对象中的方法后,才会实现延迟加载

  • 实现步骤:

    • 在当前查询对象中的关联对象标签中加入fetchType属性,属性值设置为lazy

    • 在核心配置文件中设置触发延迟加载的方法(查询结果的实体类对象中的方法)

      <settings>
          <setting name="lazyLoadTriggerMethods" value="toString()"></setting>
      </settings>
      
  • 示例Code

    测试延迟加载

    public class MybatisTest {
        @Test
        public void test() throws IOException {
            // 加载核心配置文件
            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            // 构建 sqlSessionFactory 对象
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            // 获取会话对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 获取代理对象
            ArticleMapper mapper = sqlSession.getMapper(ArticleMapper.class);
    
            // 使用代理对象调用接口中的方法
            List<Article> allWithComment = mapper.findAllWithComment();
            for (Article article : allWithComment) {
                // 调用实体类 Article 的 toString 实现延迟加载
                System.out.println(article);
                // 未调用实体类 Article 的 toString 实现立即加载
                System.out.println(article.getCommentList() + "\n");
            }
    
            // 关闭会话
            sqlSession.close();
        }
    }
    

3. 全局延迟加载

  • 实现步骤:

    • 在核心配置文件中设置全局延迟加载

      <settings> 
          <!--开启全局延迟加载功能--> 
          <setting name="lazyLoadingEnabled" value="true"/> 
      </settings>
      
  • 使用场景

    在核心配置文件中为映射配置文件中的所有标签设置延迟加载,若是一对一查询在association中加入fetchType属性,属性值设置为eager代表执行立即加载;若是一对多查询不添加fetchType默认使用全局延迟加载的配置

    若同时设置了局部和全局的延迟加载,局部延迟加载优先级高于全局的延迟加载

十一、MyBatis缓存

1. MyBatis缓存的作用

  • 查询需要频繁查询并且固定不变的数据,会增加网络连接资源的损耗和高并发访问的问题,通过缓存策略来减少数据库的查询次数,从而提高性能

2. 一级缓存

  • 一级缓存是SqlSession级别的缓存,一级缓存默认开启

  • SqlSession缓存底层实现由Map组成,首先会查询一级缓存,缓存中没有数据(map为空),第一次查询就会查询数据库,因为一级缓存的存在,导致第二次查询相同内容时,并没有发出SQL语句从数据库中查询数据,而是从一级缓存中查询,所以两次查询操作最后只执行了一次数据库操作

    使用log4j和IDEA debugger可以观察到两次查询过程中是否有发送SQL语句

  • 一级缓存是SqlSession范围的缓存,执行SqlSession的 C(增加)、U(更新)、D(删除) 操作(事务操作),或者调用clearCache()commit()close()方法,都会清空缓存

3. 二级缓存

  • 二级缓存是namspace级别(跨SqlSession,两个SqlSession可以共享缓存区域)的缓存,是默认不开启的
  • 二级缓存开启步骤:
    1. 在核心配置文件中settings中添加属性名为cacheEnabled的setting标签,value属性值为true
    2. 在映射配置文件中添加<cahche></cache>
    3. 在映射配置文件中SQL操作的标签中添加useCache属性并且属性值为true
  • 二级缓存是mapper映射级别的缓存,多个SqlSession去执行同一个Mapper映射的SQL操作,多个SqlSession可以共用二级缓存,所以二级缓存是跨SqlSession的
  • 只有执行SqlSession.commit()SqlSession.close(),一级缓存中的内容才会刷新到二级缓存中
  • 执行SqlSession的 C(增加)、U(更新)、D(删除) 操作(事务操作)会清空二级缓存
  • MyBatis开启了二级缓存后,那么查询顺序:二级缓存--》一级缓存--》数据库
  • MyBatis的二级缓存在进行多表查询时会存在脏读问题,需要使用第三方的缓存技术(Redis)

十二、MyBatis注解

1. 基于注解实现CRUD

  • 使用注解实现可以代替XML映射配置文件

  • 使用方式:

    @Select@Insert@Update@Delete注解上的value属性中存放SQL字符串

    注解中只有一个值且是value时,可以省略

2. 基于注解实现映射

3. 基于注解实现二级缓存

4. 基于注解实现延迟加载

5. 注解开发和XML配置的使用场景

  • 单表开发使用注解,编写简洁、高效

    多表开发使用XML,比注解开发维护性更强(注解如果要修改,必须修改源码)

posted @   Ramentherapy  阅读(359)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
点击右上角即可分享
微信分享提示