学静思语
Published on 2025-02-15 / 0 Visits
0
0

Mybatis框架【持久层的框架】

MyBatis框架【持久层的框架】

一、MyBatis框架的基本使用

1. MyBatis框架的基本介绍

  • MyBatis 中文手册:https://mybatis.org/mybatis-3/zh_CN/index.html
  • MyBatis是一个持久层框架
  • 前身是ibatis,在ibatis3.x时,更名为MyBatis
  • MyBatis在Java和sql之间提供更灵活的映射方案
  • MyBatis可以将对数据表的操作(sql,方法)等等直接剥离,写到XMl配置文件,实现和Java代码的解耦
  • MyBatis通过SQL操作DB,建库建表的工作需要程序员完成

2. MyBatis框架的工作示意图以及分析

3. MyBatis框架快速入门

3.1 创建数据库表

create database `mybatis`;

use `mybatis`;

create table `monster`(
    `id` INT NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `gender`TINYINT NOT NULL,
    `age` INT NOT NULL,
    `birthday` DATE DEFAULT NULL,
    `email` VARCHAR(255) NOT NULL,
    `salary` DOUBLE NOT NULL,
    PRIMARY KEY (`id`)
)charset=utf8;

-- 插入语句
insert into `monster` (`name`,`gender`,`age`,`birthday`,`email`,`salary`)
values ('牛魔王',1,100,null,'niumo@shouhu.com',1000);

-- 修改语句
update `monster` set `name`= '铁扇公主',`gender`=0,`age`=100,`birthday`=null,`email`='niuniu@shouhu.com',`salary` = 100 where `id` = 1;

-- 删除语句
delete from `monster` where `id` = 1;

-- 查询语句
select * from `monster` where `id` = 2 ;

select * from `monster`;

3.2 Entity包

package com.leon.entity;

import java.util.Date;

/**
 * ClassName:Monster
 * Package:com.leon.entity
 * Description:
 *         1. 一个普通的POJO类
 *         2. 使用原生态的sql语句查询结果还是要封装成对象
 *         3. 要求大家这里的实体类属性名和表名字段保持一致
 * @Author: leon-->ZGJ
 * @Create: 2024/5/5 22:20
 * @Version: 1.0
 */
public class Monster {

    //属性要和表字段有对应关系
    private Integer id;

    private String name;

    private Integer age;

    private Integer gender;

    private String email;

    private Date birthday;

    private Double salary;

    public Monster() {
    }

    public Monster(Integer id, String name, Integer age, Integer gender, String email, Date birthday, Double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.email = email;
        this.birthday = birthday;
        this.salary = salary;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }

}

3.3 mapper包

package com.leon.mapper;

import com.leon.entity.Monster;

import java.util.List;

/**
 * ClassName:MonsterMapper
 * Package:com.leon.mapper
 * Description:
 *          1.这是一个接口
 *          2.该接口用于声明操作monster表的方法
 *          3.这些方法可以通过注解或者XML文件来实现
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:06
 * @Version: 1.0
 */
public interface MonsterMapper {

    //插入一条信息
    public void insertMonster(Monster monster);

    //修改一条信息
    public void updateMonster(Monster monster);

    //删除一条信息
    public void deleteMonster(Integer id);

    //通过Id查询一条信息
    public Monster getMonsterById(int id);

    //查询所有的信息
    public List<Monster> getAllMonsters();








}

======================================================================================

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.MonsterMapper">
        <!--配置插入语句
            1.id 表示绑定的是哪一个方法,属性值是接口的方法名称
            2.parameterType 表示形参的类型,需要填写全类名,
              也可以简写,但是要去mybatis-config.xml文件中配置对应的简写名称,还可以不写,mybatis可以自动去检测
            3.`name`,`gender`,`age`,`birthday`,`email`,`salary`表的字段名称
            4.#{name},#{gender},#{age},#{birthday},#{email},#{salary} 这些是获取Monster实参的数据,语法是
                #{对应的字段名称}
            5. useGeneratedKeys 表示是否要获取表中的自增长的值,它的默认值为false
            6. keyProperty 表示要将获取的自增长值赋值给Monster哪个属性值
        -->
        <insert id="insertMonster" parameterType="com.leon.entity.Monster" useGeneratedKeys="true" keyProperty="id">
            insert into `monster` (`name`,`gender`,`age`,`birthday`,`email`,`salary`)
            values (#{name},#{gender},#{age},#{birthday},#{email},#{salary});
        </insert>

        <update id="updateMonster" parameterType="com.leon.entity.Monster">
            update `monster` set
            `name`= #{name},
            `gender`= #{gender},
            `age`= #{age},
            `birthday`= #{birthday},
            `email`= #{email},
            `salary` = #{salary}
            where `id` = #{id};
        </update>

        <!--
            1. parameterType 如果是基本类型和包装类可以简写
        -->
        <delete id="deleteMonster" parameterType="Integer">
            delete from `monster` where `id` = #{id};
        </delete>

        <!--
            1.resultType 表示你要返回的什么结果
        -->
        <select id="getMonsterById" resultType="Monster">
            select * from `monster` where `id` = #{id} ;
        </select>

        <!--
            1.如果你返回的是一个结合,这个resultType设置的要是集合中所存放的数据类型
        -->
        <select id="getAllMonsters" resultType="Monster">
            select * from `monster`;
        </select>
</mapper>

3.4 Utils包

package com.leon.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * ClassName:MyBatisUtils
 * Package:com.leon.utils
 * Description:
 *          1.这是一个关于mybatis的工具类
 *          2.这个类用于获取SqlSession对象
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:44
 * @Version: 1.0
 */
public class MyBatisUtils {

   //创建一个SqlSessionFactory接口引用
   public static SqlSessionFactory sqlSessionFactory;

   //静态代码块,用于初始化
   static {
      //创建一个配置文件名称的变量
      String resource = "mybatis-config.xml";

       try {
           //获取到配置文件mybatis-config.xml,对应的InputStream
           InputStream inputStream = Resources.getResourceAsStream(resource);

           //通过SqlSessionFactoryBuilder来获取SqlSessionFactory对象
           sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

       } catch (IOException e) {
           throw new RuntimeException(e);
       }


   }

   //获取SqlSession对象
   public static SqlSession getSqlSession() {
      //返回SqlSession对象
      return sqlSessionFactory.openSession();
   }






}

3.5 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!--配置Mybatis自带的日志
        1.注意这个标签要放在最上面
    -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--配置别名-->
    <typeAliases>
        <typeAlias type="com.leon.entity.Monster" alias="Monster"/>
    </typeAliases>



    <environments default="development">
        <environment id="development">

            <!--配置事务管理器-->
            <transactionManager type="JDBC"/>

            <!--配置数据源-->
            <dataSource type="POOLED">
                <!--配置连接驱动-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--配置连接mysql-url
                    1. jdbc:mysql 表示使用什么协议
                    2. 127.0.0.1:3306 表示指定连接Mysql的IP地址和端口号(port)
                    3. mybatis 表示连接的数据库的名称
                    4. useSSL=true 表示使用安全连接
                    5. &amp; 表示是一个&符,是一个转义符
                    6. useUnicode=true 表示使用unicode 作用是防止编码错误
                    7. characterEncoding=UTF-8 表示指定使用utf-8编码,防止中文乱码
                -->
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <!--
                    1. 配置连接的数据库用户名和密码
                -->
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置需要关联的Mapper.xml文件-->
    <mappers>
        <mapper resource="com/leon/mapper/MonsterMapper.xml"/>
    </mappers>
</configuration>

3.6 test包

package com.leon.mapper;

import com.leon.entity.Monster;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * ClassName:MonsterMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:56
 * @Version: 1.0
 */
public class MonsterMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private MonsterMapper monsterMapper;

    /*
    * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
    * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);


    }


    @Test
    public void insertMonster(){

        for (int i = 0; i < 2; i++) {

            Monster monster = new Monster();

            monster.setName("牛魔王"+i);

            monster.setAge(100);

            monster.setGender(1);

            monster.setBirthday(new Date());

            monster.setEmail("niu@shouhu.com");

            monster.setSalary(10000.00);

            monsterMapper.insertMonster(monster);

            System.out.println(monster.getAge());
        }

        //判断是否为空
        if(sqlSession != null){

            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }


    @Test
    public void updateMonster(){

        Monster monster = new Monster();

        monster.setName("猪八戒");

        monster.setAge(100);

        monster.setGender(1);

        monster.setBirthday(new Date());

        monster.setEmail("hou@shouhu.com");

        monster.setSalary(10000.00);

        monster.setId(14);

        monsterMapper.updateMonster(monster);

        //判断是否为空
        if(sqlSession != null){

            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }

    @Test
    public void deleteMonster(){



        monsterMapper.deleteMonster(12);

        //判断是否为空
        if(sqlSession != null){

            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }



    @Test
    public void selectMonster(){


        Monster monsterById = monsterMapper.getMonsterById(9);

        System.out.println(monsterById);

        //判断是否为空
        if(sqlSession != null){

            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }

    @Test
    public void selectAllMonster(){


        List<Monster> allMonsters = monsterMapper.getAllMonsters();

        for (Monster allMonster : allMonsters) {

            System.out.println(allMonster);
        }

        //判断是否为空
        if(sqlSession != null){

            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }




}

4. 原生API的调用

4.1 初始化方法

 //创建一个SqlSession的接口引用
    private SqlSession sqlSession;


    //使用junit测试工具的Before注解
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        System.out.println(sqlSession);

    }

4.1 增加

    @Test
    public void insertData(){

        //创建要插入的数据
        Monster monster = new Monster(null,"玉兔精",1000,0,"yutu@shouhu.com",new Date(),1000.00);
        //使用原生API进行插入数据
        /*
        *   @Override
        *   public int insert(String statement, Object parameter) {
        *       return update(statement, parameter);
        *   }
        * 1. statement 表示接口方法的完整声明
        * 2. parameter 表示你要插入的数据
        * */
        int insert = sqlSession.insert("com.leon.mapper.MonsterMapper.insertMonster", monster);

        System.out.println(insert);
        //判断是否为空
        if(sqlSession != null){
            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }

4.2 删除

    @Test
    public void deleteMonster(){

        //使用原生API进行删除
        int delete = sqlSession.delete("com.leon.mapper.MonsterMapper.deleteMonster", 9);

        System.out.println(delete);


        //判断是否为空
        if(sqlSession != null){
            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }
    }

4.3 修改

    @Test
    public void updateData(){

        //创建要插入的数据
        Monster monster = new Monster(13,"白骨精",1000,0,"bg@shouhu.com",new Date(),1000.00);
        //使用原生API进行修改
        int update = sqlSession.update("com.leon.mapper.MonsterMapper.updateMonster", monster);

        System.out.println(update);

        //判断是否为空
        if(sqlSession != null){
            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }
    }

4.4 查询

    @Test
    public void selectAllMonster(){
        //使用原生API进行查询操作
        List<Monster> monsters = sqlSession.selectList("com.leon.mapper.MonsterMapper.getAllMonsters");
        //遍历查询后的结果
        for (Monster monster : monsters) {

            System.out.println(monster);

        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();
        }

   }

4.5 注意事项

  • 使用原生API的前提需要有该目标接口及方法
  • 使用原生API的前提还需要配置了Mapper.xml配置文件,否则底层无法实现增删改查的功能,因为失去了sql语句的支撑

5. 使用注解方式操作和配置Mapper接口

5.1 增

   /*
        <insert id="insertMonster" parameterType="com.leon.entity.Monster" useGeneratedKeys="true" keyProperty="id">
            insert into `monster` (`name`,`gender`,`age`,`birthday`,`email`,`salary`)
            values (#{name},#{gender},#{age},#{birthday},#{email},#{salary});
        </insert>
        1.不需要写返回参数类型,因为标注在方法之上MyBatis可以通过反射的方式获取到
        2.如果想要获取并返回数据库的自增长字段数据,可以设置Options这个注解,
             它的使用方式和标签的使用方式相类似
        3.useGeneratedKeys = true 表示是否要获取表中的自增长的值,默认值是true
        4.keyProperty = "id" 表示要将获取的自增长值赋值给Monster哪个属性值
        5.keyColumn = "id" 表示是表中自增长的字段
        6.如果自增长字段和对象属性名相同,则可以省略,但是建议还是带上更好,显得明确
    */
    //插入一条信息
    @Insert("insert into `monster` (`name`,`gender`,`age`,`birthday`,`email`,`salary`) " +
            " values (#{name},#{gender},#{age},#{birthday},#{email},#{salary});")
    @Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
    public void insertMonster(Monster monster);

5.2 删

 /*
         <delete id="deleteMonster" parameterType="Integer">
            delete from `monster` where `id` = #{id};
        </delete>
        1.不需要设置参数类型,MyBatis通过反射可以获取
    * */
    //删除一条信息
    @Delete("delete from `monster` where `id` = #{id};")
    public void deleteMonster(Integer id);

5.3 改

 /*
        <update id="updateMonster" parameterType="com.leon.entity.Monster">
            update `monster` set
            `name`= #{name},
            `gender`= #{gender},
            `age`= #{age},
            `birthday`= #{birthday},
            `email`= #{email},
            `salary` = #{salary}
            where `id` = #{id};
         </update>
         1.不需要设置参数类型,MyBatis通过反射可以获取
    * */
    //修改一条信息
    @Update("update `monster` set `name`= #{name},`gender`= #{gender},`age`= #{age},`birthday`= #{birthday}," +
            "`email`= #{email},`salary` = #{salary} where `id` = #{id};")
    public void updateMonster(Monster monster);

5.4 查

    /*
         <select id="getMonsterById" resultType="Monster">
            select * from `monster` where `id` = #{id} ;
        </select>
        1.不需要设置参数类型,MyBatis通过反射可以获取
        2.不需要设置返回参数了,MyBatis可以通过反射来获取
    * */
    //通过Id查询一条信息
    @Select("select * from `monster` where `id` = #{id} ;")
    public Monster getMonsterById(int id);
======================================================================================
    /*
         <select id="getAllMonsters" resultType="Monster">
            select * from `monster`;
        </select>
         1.不需要设置参数类型,MyBatis通过反射可以获取
        2.不需要设置返回参数了,MyBatis可以通过反射来获取
    * */
    //查询所有的信息
    @Select(" select * from `monster`;")
    public List<Monster> getAllMonsters();

5.5 测试

package com.leon.mapper;

import com.leon.entity.Monster;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * ClassName:MonsterMapperAnnotationTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/15 10:37
 * @Version: 1.0
 */
public class MonsterMapperAnnotationTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private MonsterMapperAnnotation monsterMapperAnnotation;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        monsterMapperAnnotation = sqlSession.getMapper(MonsterMapperAnnotation.class);

    }
    @Test
    public void insertMonster(){

        //创建要插入的数据
        Monster monster = new Monster(null,"蛇精",1000,0,"se@shouhu.com",new Date(),1000.00);
        //调用方法
        monsterMapperAnnotation.insertMonster(monster);

    }
    @Test
    public void updateMonster(){
        //创建要插入的数据
        Monster monster = new Monster(10,"红孩儿",1000,1,"sc@shouhu.com",new Date(),1000.00);
        //调用目标方法
        monsterMapperAnnotation.updateMonster(monster);

    }
    @Test
    public void deleteMonster(){
        //调用目标方法
        monsterMapperAnnotation.deleteMonster(2);

    }
    @Test
    public void selectMonster(){
        //调用目标方法
        Monster monster = monsterMapperAnnotation.getMonsterById(13);
        System.out.println("monster = " + monster);
    }
    @Test
    public void selectAllMonster(){
        //调用目标方法
        List<Monster> allMonsters = monsterMapperAnnotation.getAllMonsters();
        //循环遍历结果集合
        for (Monster monster : allMonsters) {

            System.out.println(monster);
        }
    }


    @After
    public void destroy(){
        //判断是否为空
        if(sqlSession != null){
            //提交数据
            sqlSession.commit();
            //关闭资源
            sqlSession.close();

        }

    }

}

5.6 注意事项和细节说明

  • 如果通过注解的方式,就不再使用MonsterMapper.xml文件,但是需要在mybatis-config.xml文件中注册含注解的类/接口

    <!--配置需要关联的Mapper.xml文件-->
      <mappers>
          <!--<mapper resource="com/leon/mapper/MonsterMapper.xml"/>-->
          <!--&lt;!&ndash;-->
          <!--    1.如果通过注解使用,则不需要使用Mapper.xml-->
          <!--    2.但是需要在mybatis-config.xml文件中的mappers引入或者注册含有注解的类-->
          <!--    3.如果没有引用则不能使用-->
          <!--&ndash;&gt;-->
          <!--<mapper class="com.leon.mapper.MonsterMapperAnnotation"/>-->
    
          <!--
              1. 当一个包下有很多的Mapper.xml文件和基于注解实现的接口时
                  为了方便,可以以包的方式进行注册
              2. 这样的话,包下面有的xml文件和注解接口都会进行注册
          -->
          <package name="com.leon.mapper"/>
      </mappers>
    
  • 添加时,如果要返回自增长id值或其他自增长值,可以使用@Options注解,组合使用

    @Insert("insert into `monster` (`name`,`gender`,`age`,`birthday`,`email`,`salary`) " +
              " values (#{name},#{gender},#{age},#{birthday},#{email},#{salary});")
      @Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
      public void insertMonster(Monster monster);
    

6. mybatis-config.xml配置文件详解

6.1 说明

  • mybatis的核心配置文件(mybatis-config.xml),比如配置JDBC连接信息,注册Mapper等等,因此需要对这个配置文件有详细的了解
  • 文档地址:https://mybatis.org/mybatis-3/zh_CN/index.html

6.2 properties 属性

6.2.1 说明
  • properties属性既可以在内部进行配置也可以在外部配置,也就是引入properties文件
6.2.2 mybatis-config.xml代码
 <!--引入外部properties文件-->
    <properties resource="jdbc.properties"/>
 <environments default="development">
        <environment id="development">

            <!--配置事务管理器-->
            <transactionManager type="JDBC"/>

            <!--配置数据源-->
            <dataSource type="POOLED">

                <!--
                    1.使用外部的properties文件来设置相关信息
                    2.这个属性文件,需要统一的放在resources目录下,mybatis会自动加载
                -->
                <!--配置连接驱动-->
                <!--<property name="driver" value="com.mysql.jdbc.Driver"/>-->
                <property name="driver" value="${jdbc.driver}"/>
                <!--配置连接mysql-url
                    1. jdbc:mysql 表示使用什么协议
                    2. 127.0.0.1:3306 表示指定连接Mysql的IP地址和端口号(port)
                    3. mybatis 表示连接的数据库的名称
                    4. useSSL=true 表示使用安全连接
                    5. &amp; 表示是一个&符,是一个转义符
                    6. useUnicode=true 表示使用unicode 作用是防止编码错误
                    7. characterEncoding=UTF-8 表示指定使用utf-8编码,防止中文乱码
                -->
                <!--<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>-->
                <property name="url" value="${jdbc.url}"/>
                <!--
                    1. 配置连接的数据库用户名和密码
                -->
                <!--<property name="username" value="root"/>-->
                <property name="username" value="${jdbc.username}"/>
                <!--<property name="password" value="root"/>-->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
6.2.3 pom.xml配置
  • 需要在pom文件中配置加载properties文件
<build>
        <!--
            解读
            1.意思是将src/main/java目录下的所有以.xml结尾的文件都加载到真实的工作目录
            2.这个需要写在父项目的pom文件中
        -->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <!--
                        1.**表示多级目录
                        2.*.xml表示任意名称以.xml结尾的文件
                    -->
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>
6.2.4 jdbc.properties 文件
#注意:在url中就不需要带上转义符&amp,直接使用&就可以
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&userUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

6.3 settings 全局参数定义

6.3.1 mybatis-config.xml配置
  <!--配置Mybatis自带的日志
        1.注意这个标签要放在最上面
    -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
6.3.2 其他配置
  • 网址:https://mybatis.org/mybatis-3/zh_CN/configuration.html#settings

6.4 typeAliases 别名处理器

6.4.1 如果一个包下面的类很少使用如下配置
<!--配置别名-->
    <typeAliases>
        <typeAlias type="com.leon.entity.Monster" alias="Monster"/>
    </typeAliases>
6.4.2 如果一个包下面的类狠多使用如下配置
<typeAliases>
        <!--<typeAlias type="com.leon.entity.Monster" alias="Monster"/>-->
        <!--
            1.如果一个包下面有很多的类,可以直接引入包,这样该包下的所有类,可以直接使用类名
        -->
        <package name="com.leon.entity"/>
    </typeAliases>

6.5 typeHandlers 类型处理器

6.5.1 基本介绍
  • 用于java类型和JDBC类型映射
  • Mybatis的映射基本已经满足,不太需要重新定义
  • 使用默认即可,也就是mybatis会自动的将Java和JDBC类型进行转换
  • Java类型和JDBC类型映射关系一览表:https://mybatis.org/mybatis-3/zh_CN/configuration.html#typeHandlers

6.6 environments 环境

6.6.1 resource 注册Mapper文件:xxxMapper.xml文件
<mapper resource="com/leon/mapper/MonsterMapper.xml"/>
6.6.2 class:接口注解实现
<!--
            1.如果通过注解使用,则不需要使用Mapper.xml
            2.但是需要在mybatis-config.xml文件中的mappers引入或者注册含有注解的类
            3.如果没有引用则不能使用
        -->
        <mapper class="com.leon.mapper.MonsterMapperAnnotation"/>
6.6.3 url:外部路径,使用很少,不推荐
6.6.4 package 方式注册:
  <!--配置需要关联的Mapper.xml文件-->
    <mappers>
        <!--<mapper resource="com/leon/mapper/MonsterMapper.xml"/>-->
        <!--&lt;!&ndash;-->
        <!--    1.如果通过注解使用,则不需要使用Mapper.xml-->
        <!--    2.但是需要在mybatis-config.xml文件中的mappers引入或者注册含有注解的类-->
        <!--    3.如果没有引用则不能使用-->
        <!--&ndash;&gt;-->
        <!--<mapper class="com.leon.mapper.MonsterMapperAnnotation"/>-->

        <!--
            1. 当一个包下有很多的Mapper.xml文件和基于注解实现的接口时
                为了方便,可以以包的方式进行注册
            2. 这样的话,包下面有的xml文件和注解接口都会进行注册
        -->
        <package name="com.leon.mapper"/>
    </mappers>

7. XML映射器

7.1 基本介绍

  • MyBatis 的真正强大在于它的语言映射(在XXXMapper.xml配置),由于它的异常强大,如果拿它跟具有相同功能的JDBC代码进行对比,你会立即发现省掉了将近95%的代码,MyBatis致力于减少使用成本,让用户能更专注于SQL代码
  • SQL映射文件常用的几个顶级元素(按照应被定义的顺序列出):
  • cache - 该命名空间的缓存配置
  • cache-ref - 引用其它命名空间的缓存配置
  • resultMap - 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素
  • parameterType - 将会传入这条语句的参数的类全限定名或别名
  • sql-可被其它语句引用的可重用语句块
  • insert - 映射插入语句
  • update - 映射更新语句
  • delete - 映射删除语句
  • select - 映射查询语句

7.2 parameterType(输入参数类型)

7.2.1 基本说明
  • 传入简单类型,比如按照id查询Monster

  • 传入POJO类型,查询时需要有多个筛选条件

  • 当有多个条件时,传入的参数就是POJO类型的Java对象,比如这里的Monster对象

  • 当传入的参数类型是String时,也可以使用${}方法来接收参数

  • 如果是模糊查询的时候,要使用${}

7.2.2 代码
7.2.2.1 接口代码
 //通过id或者名字查询
    public List<Monster> selectMonsterByNameOrId(Monster monster);
    //通过名字查询
    public List<Monster> selectMonsterByName(String name);
7.2.2.2 MonsterMapper.xml
 <!--
         //通过id或者名字查询
         public List<Monster> selectMonsterByNameOrId(Monster monster);
         1.这里为什么直接使用类名,不适用全类名,因为在mybatis-config.xml配置了别名包
         2.#{id} 表示获取出入的实参Monster对象的id属性,#{name}也是如此
         3.`id` 表示表中的字段
    -->
    <select id="selectMonsterByNameOrId" resultType="Monster" parameterType="Monster">
        select * from `monster` where `id` = #{id} or `name` = #{name};
    </select>
    <!--
         //通过名字查询
         public List<Monster> selectMonsterByName(String name);
        1. 如果使用模糊查询,要获取传入的实参值,要使用${实参名}语法来获取
           #{实参名}获取不到
        2. 如果是模糊插叙,要使用${}来获取值
        3. '%${name}%' 这个可以是单引号,也可以是双引号
    -->
    <select id="selectMonsterByName" resultType="Monster" parameterType="String">
        select * from `monster` where `name` like '%${name}%';
    </select>
7.2.2.3 测试
package com.leon.mapper;

import com.leon.entity.Monster;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * ClassName:MonsterMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:56
 * @Version: 1.0
 */
public class MonsterMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private MonsterMapper monsterMapper;

    /*
    * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
    * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);


    }

    @Test
    public void selectMonsterByNameOrId(){
        //设置要传入的参数值
        Monster monster = new Monster();
        monster.setId(10);
        monster.setName("玉兔精");
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByNameOrId(monster);

        //循环遍历结果集
        for (Monster monster1 : monsters) {

            System.out.println(monster1);
        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

    @Test
    public void selectMonsterByName(){
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByName("精");
        //循环遍历
        for (Monster monster : monsters) {
            System.out.println(monster);
        }
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }



}

7.3 Map 入参类型

7.3.1 介绍
  • HashMap传入参数更加灵活,比如可以灵活的增加查询的属性,而不受限于Monster这个Pojo属性本身
7.3.2 代码
7.3.2.1 接口代码
 //查询id > 10 并且 salary 大于40,要求传入参数是HashMap
    public List<Monster> selectMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);
7.3.2.2 MonsterMapper.xml
  <!--
         //查询id > 10 并且 salary 大于40,要求传入参数是HashMap
         public List<Monster> selectMonsterByIdAndSalary_ParameterHashMap(Map<String, Object> map);
        1. 如果传入的参数是Map类型的参数,在sql语句中的#{id} 表示在Map集合中有一个key值为id的键值对
        2. 入参是Map即是灵活,受限制,因为取值时的名称,在Map集合中必须要有对应的key名称相同的键值对,否则会报错
    -->
    <select id="selectMonsterByIdAndSalary_ParameterHashMap" resultType="Monster" parameterType="map">
        select * from `monster` where `id` > #{id} and `salary` >  #{salary};
    </select>
7.3.2.3 测试
 @Test
    public void selectMonsterByIdAndSalary_ParameterHashMap(){
        //创建集合存放形参值
        Map<String, Object> map = new HashMap<>();
        map.put("id",10);
        map.put("salary",40);
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByIdAndSalary_ParameterHashMap(map);
        //循环遍历
        for (Monster monster : monsters) {
            System.out.println(monster);
        }
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }
    }

7.4 返回类型是Map

7.4.1 说明
  • 返回值的类型List>,其中的一个Map集合对应的就是一行数据
7.4.2 代码
7.4.2.1 接口代码
    //查询id > 10 并且 salary 大于40,要求传入参数是HashMap
    public List<Map<String,Object>> selectMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(Map<String, Object> map);
7.4.2.2 MonsterMapper.xml
<!--
        //查询id > 10 并且 salary 大于40,要求传入参数是HashMap
        public List<Map<String,Object>> selectMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(Map<String, Object> map);
    -->
    <select id="selectMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap" parameterType="map" resultType="map">
        select * from `monster` where `id` > #{id} and `salary` >  #{salary};
    </select>
7.4.2.3 测试
@Test
    public void selectMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(){

        //创建集合存放形参值
        Map<String, Object> map = new HashMap<>();
        map.put("id",10);
        map.put("salary",40);
        //调用目标方法
        List<Map<String,Object>> maps =  monsterMapper.selectMonsterByIdAndSalary_ParameterHashMap_ReturnHashMap(map);
        //循环遍历结果
        for (Map<String, Object> stringObjectMap : maps) {

            for (Map.Entry<String, Object> entry : stringObjectMap.entrySet()) {

                System.out.print(entry.getKey() +" == " + entry.getValue() +"      ");

            }
            System.out.println();

        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

7.5 ResultMap

7.5.1 基本介绍
  • 当实体类的属性和表的字段名字不一致时,可以通过resultMap进行映射,从而屏蔽属性名和表的字段名的不同
7.5.2 代码
7.5.2.1 数据库表的创建
create table `user`(
    `user_id` INT NOT NULL AUTO_INCREMENT,
    `user_email` varchar(255) DEFAULT(''),
    `user_name` varchar(255) DEFAULT(''),
    PRIMARY KEY(`user_id`)
)charset=utf8
7.5.2.2 entity
package com.leon.entity;

/**
 * ClassName:User
 * Package:com.leon.entity
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/15 17:15
 * @Version: 1.0
 */
public class User {

    private Integer user_id;

    private String userName;

    private String userEmail;

    public User() {
    }

    public User(Integer user_id, String userName, String userEmail) {
        this.user_id = user_id;
        this.userName = userName;
        this.userEmail = userEmail;
    }

    public Integer getUser_id() {
        return user_id;
    }

    public void setUser_id(Integer user_id) {
        this.user_id = user_id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    @Override
    public String toString() {
        return "User{" +
                "user_id=" + user_id +
                ", userName='" + userName + '\'' +
                ", userEmail='" + userEmail + '\'' +
                '}';
    }
}
7.5.2.3 接口代码
package com.leon.mapper;

import com.leon.entity.User;

import java.util.List;

/**
 * ClassName:UserMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/15 17:18
 * @Version: 1.0
 */
public interface UserMapper {

    //添加有一条数据
    public void insertUser(User user);
    //查询所有数据
    public List<User> selectAllUsers();


}
7.5.2.4 UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.UserMapper">

    <!--public void insertUser(User user);-->
    <insert id="insertUser" parameterType="User">
        insert into `user`(`user_name`,`user_email`) VALUES(#{userName},'userEmail');
    </insert>


    <!--
        //查询所有数据
         public List<User> selectAllUsers();
         1. 如果直接进行查询的话会发现,只有user_id,有数据其它的属性没有数据都是默认值,为什么会这样能
            因为属性和表字段不一样,不能完成赋值,所有赋值失败
         2. 刻印使用别名的方式阿里设置,但是这种方式有限制,如果很多的sql语句的话就会很麻烦,没有很好的复用性
         3. 使用resultMap标签,也表示我们使用了resultMap
         4. id="selectAllUserMap" 表示这个resultMap的名称,提供给外部标签引用的名称
         5. type="User" 表示要返回的数据类型,为什么没有使用全类名,是因为在mybatis-config.xml配置了类型别名
         6. result 标签 表示结果映射
         7. column="user_name" 表示表的字段名称
         8. property="userName" 表示返回类型的属性名称

     -->
    <resultMap id="selectAllUserMap" type="User">
        <result column="user_name" property="userName"/>
        <result column="user_email" property="userEmail"/>
    </resultMap>
    <select id="selectAllUsers" resultMap="selectAllUserMap">
        select * from `user`;
    </select>
    <!--使用别名来解决问题,能解决但是不是最好的办法,因为复用性差-->
    <!--<select id="selectAllUsers" resultType="User">-->
    <!--    select `user_id`,`user_name` as `userName`,`user_email` as `userEmail` from `user`;-->
    <!--</select>-->
</mapper>
7.5.2.5 测试
package com.leon.mapper;

import com.leon.entity.Monster;
import com.leon.entity.User;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ClassName:MonsterMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:56
 * @Version: 1.0
 */
public class UserMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private UserMapper userMapper;

    /*
    * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
    * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        userMapper = sqlSession.getMapper(UserMapper.class);

    }

    @Test
    public void insertUser(){
        //创建User对象,作为实参传入方法
        User user = new User(null,"张三","zhansan@shouhu.com");
        //调用目标方法
        userMapper.insertUser(user);
        //判断是否为空
        if(sqlSession != null ){
            //提交事务
            sqlSession.commit();
            //关闭资源
            sqlSession.close();
        }

    }

    @Test
    public void selectAllUser(){
        //调用目标方法
        List<User> users = userMapper.selectAllUsers();
        //循环遍历
        for (User user : users) {
            System.out.println(user);
        }

        //判断是否为空
        if(sqlSession != null ){
            //关闭资源
            sqlSession.close();
        }

    }

}
7.5.3 注意事项和使用细节
  • 解决表字段和对象属性名不一致,也可以使用字段别名,但是不建议使用,因为复用性差

    <!--使用别名来解决问题,能解决但是不是最好的办法,因为复用性差-->
      <!--<select id="selectAllUsers" resultType="User">-->
      <!--    select `user_id`,`user_name` as `userName`,`user_email` as `userEmail` from `user`;-->
      <!--</select>-->
    
  • 如果是MyBatis-Plus处理就比较简单,可以使用注解@TableField 来解决实体类属 性和表字段名不一致的问题,还可以使用@TableName来解决实体类名和表名不一致的问题

8. 动态SQL

8.1 说明

  • 网址:https://mybatis.org/mybatis-3/zh_CN/dynamic-sql.html
  • 动态SQL是MyBatis的最强大特性之一
  • 使用JDBC或其它类似的框架,根据不同条件拼接SQL语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等
  • SQL映射语句中的强大的动态SQL语言,可以很好的解决这个问题

8.2 基本介绍

  • 基本介绍
  • 在一个实际的项目中,sql语句往往是比较复杂的
  • 为了满足更加复杂的业务需求,MyBatis的设计者,提供了动态生成SQL的功能
  • 动态SQL必要性
  • 比如我们查询Monster时,如果程序员输入的age不大于0,我们的sql语句就不带age
  • 更新Monster对象时,没有设置的新的属性值,就保持原来的值,设置新的值,才更新
  • 解决方案分析
  • 从上面的需求我们可以看出,有时我们在生成sql语句时,在同一方法中,还要根据不同的情况生成不同的sql语句
  • 解决方案:MyBatis提供的动态SQL机制
  • 动态SQL常用标签
  • 动态SQL提供了如下几种常用的标签,类似我们Java的控制语句:
    • if【判断】
    • where【拼接where子句】
    • choose/when/otherwise【类似Java的switch语句,注意是单分支】
    • foreach【类似in】
    • trim【替换关键字/定制元素的功能】
    • set【在update的set中,可以保证进入set标签的属性被修改,而没有进入set的,保持原来的值】

8.3 动态SQL-if标签

8.3.1 接口代码
 //请查询age大于10的所有妖怪,如果输入的age不大于0(也就是小于零)
    public List<Monster> selectMonsterByAge( Integer i);
8.3.2 Mapper.xml
<!--
         //请查询age大于10的所有妖怪,如果输入的age不大于0(也就是小于零)
         public List<Monster> selectMonsterByAge(@Param("age") Integer age);
         1. 在if标签中的属性test,在里面使用#{目标参数名称}是取不出来的,因而直接写形参的名称就可以获取到传入的数据
         2. 一般将where关键字放在if标签外面
         3. @Param注解的使用是当参数过多时,MyBatis不好识别,则使用@Param来取别名,方便引用
    -->
    <select id="selectMonsterByAge" resultType="Monster" parameterType="Integer">
        select * from `monster` where 1=1
        <if test="age >= 0">
            and age > #{age} ;
        </if>
    </select>
8.3.3 测试
@Test
    public void selectMonsterByAge(){
        //调用目标方法
        List<Monster> monsters =  monsterMapper.selectMonsterByAge(10);
        //循环遍历
        for (Monster monster : monsters) {
            System.out.println(monster);
        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }

8.4 动态SQL-where标签

8.4.1 接口代码
//查询id大于10的,并且名字是"大象精"的所有妖怪,注意:如果名字为空或者输入的id小于0,
    // 则不拼接sql语句(意思就是如果名字为空就不带名字条件,id小于0也不带id条件)
    public List<Monster> selectMonsterByNameAndId(Monster monster);
8.4.2 Mapper.xml
  <!--
        //查询id大于10的,并且名字是"大象精"的所有妖怪,注意:如果名字为空或者输入的id小于0,
        // 则不拼接sql语句(意思就是如果名字为空就不带名字条件,id小于0也不带id条件)
        public List<Monster> selectMonsterByNameAndId(Monster monster);
        1.where 标签会自动替换成where关键字,前提是where标签的子元素会有内容返回才会替换成where关键字
          ,而且它也会去除开头为and或or
        2.在where标签中,如果有一个子标签携带了and或or关键字,其他的子标签也要携带上and或or关键字,这样才是标准的写法
    -->
    <select id="selectMonsterByNameAndId" resultType="Monster" parameterType="Monster">
        select * from `monster`
        <where>
            <if test="id != null and id >= 0">
                and `id` > #{id}
            </if>
            <if test="name != null and name != ''">
                and `name` = #{name}
            </if>
        </where>
    </select>
8.4.3 测试
  @Test
    public void selectMonsterByNameAndId(){
        //创建Monster对象
        Monster monster = new Monster();
        monster.setId(11);
        monster.setName("大象精");
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByNameAndId(monster);
        //循环遍历
        for (Monster monster1 : monsters) {
            System.out.println(monster1);
        }
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

8.5 动态SQL-choose标签

8.5.1 接口代码
/*
    * 1.如果给的name不为空,就按名字查询妖怪
    * 2.如果指定的id > 0,就按照id来查询妖怪
    * 3.如果前面两个条件都不满足,就默认查询salary>1000的
    * */
    public List<Monster> selectMonsterByNameAndId_choose(Map<String,Object> map);
8.5.2 Mapper.xml
<!--
        1.如果给的name不为空,就按名字查询妖怪
        2.如果指定的id > 0,就按照id来查询妖怪
        3.如果前面两个条件都不满足,就默认查询salary>1000的
        public List<Monster> selectMonsterByNameAndId_choose(Map<String,Object> map);

        1. choose标签中只会执行一个子句,如果其中一个子句满足条件其他子句则不会执行,
           它们的优先顺序是定义的顺序
    -->
    <select id="selectMonsterByNameAndId_choose" resultType="Monster" parameterType="map">
        select * from `monster`
        <choose>
            <when test="name != null and name != ''">
                where  `name` = #{name}
            </when>
            <when test="id > 0 and id != null">
                where `age` > #{age}
            </when>
            <otherwise>
                where `salary` > 1000
            </otherwise>

        </choose>
    </select>
8.5.3 测试
@Test
    public void selectMonsterByNameAndId_choose(){
        //创建一个Map集合
        Map<String, Object> params = new HashMap<String, Object>();
        //添加参数
        params.put("id",1);
        params.put("name",null);
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByNameAndId_choose(params);
        //循环遍历
        for (Monster monster : monsters) {
            System.out.println(monster);
        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

8.6 动态SQL-foreach标签

8.6.1 接口代码
 //查询id为10,14,19的妖怪
    public List<Monster> selectMonsterById_ForEach(Map<String,Object> map);
8.6.2 Mapper.xml
 <!--
        //查询id为10,14,19的妖怪
        public List<Monster> selectMonsterById_ForEach(Map<String,Object> map);

        1. 传入的参数形式是ids-[10,14,19],也就是一个key值对应一个List集合
        2. collection="ids" 对应的是设置在Map集合中的key值,也就是说Map集合中要有一个key值是ids
        3. item="id" 表示在遍历集合时,每次取出的值,都会赋值给这个id,然后通过#{id}就能取出值
        4. open="(" 对应的就是sql语句中in关键字的第一个(
        5. separator="," 表示取出来的值用逗号隔开
        6. close=")"对应的就是sql语句中in关键字的最后一个)
        7. #{id} 表示取出item="id" 中的值
    -->
    <select id="selectMonsterById_ForEach" resultType="Monster" parameterType="map">
        select * from `monster`
        <if test="ids != null and ids.size > 0">
            <where>
                `id` in
                <foreach collection="ids" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </where>
        </if>

    </select>
8.6.3 测试
 @Test
    public void selectMonsterById_ForEach(){
        //创建一个Map集合
        Map<String, Object> params = new HashMap<String, Object>();
        //存放数据
        params.put("ids", Arrays.asList(10,14,19));
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterById_ForEach(params);

        //循环遍历
        for (Monster monster : monsters) {
            System.out.println(monster);
        }

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

8.7 动态SQL-trim标签

8.7.1 接口代码
 //按名字和年龄查询,如果SQL语句开头有and|or 就替换成where
    public List<Monster> selectMonsterByName_Trim(Map<String,Object> map);
8.7.2 Mapper.xml
 <!--
        //按名字和年龄查询,如果SQL语句开头有and|or 就替换成where
        public List<Monster> selectMonsterByName_Trim(Map<String,Object> map);
        1.where 标签只会去除and或or,不会去除其他的一些关键字,如果想要去除可以使用trim标签
          它能够拓展where的去除功能
        2.prefix="where"表示前缀,也就是表示如过子标签有内容返回,它就会添加一个where关键字作为前缀
        3. prefixOverrides="and|or|hsp" 表示哪些多余出来的要进行去除,这个表示如果and|or|hsp多余出来了
           就进行去除
        4.还有后缀,但是这个trim标签使用较少,知道即可
    -->
    <select id="selectMonsterByName_Trim" resultType="Monster" parameterType="map">
        select * from `monster`
            <trim prefix="where" prefixOverrides="and|or|hsp">
            <if test="name != null and name != ''">
                hsp name = #{name}
            </if>
            <if test="age != null and age > 0">
                and age > #{age}
            </if>
            </trim>


    </select>
8.7.3 测试
    public void selectMonsterByName_Trim(){
        //创建一个Map集合
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("age",10);
        params.put("name","大象精");
        //调用目标方法
        List<Monster> monsters = monsterMapper.selectMonsterByName_Trim(params);
        //循环遍历
        for (Monster monster1 : monsters) {
            System.out.println(monster1);
        }
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }

8.8 动态SQL-set标签

8.8.1 接口代码
//请对指定id的妖怪进行修改,如果没有设置新的属性,则保持原来的值
    public void updateMonster_set(Map<String,Object> map);
8.8.2 Mapper.xml
  <!--
          //请对指定id的妖怪进行修改,如果没有设置新的属性,则保持原来的值
         public void updateMonster_set(Map<String,Object> map);
         1.set标签会去除多余的逗号
    -->
    <update id="updateMonster_set" parameterType="map">
        update `monster`
        <set>
            <if test="name != null and name != ''">
                `name`= #{name},
            </if>
            <if test="gender != null and gender >= 0">
                `gender`=#{gender},
            </if>
            <if test="age != null and age > 0">
                `age`=#{age},
            </if>
            <if test="birthday != null ">
                `birthday`=#{birthday},
            </if>
            <if test="email != null and email != ''">
                `email`=#{email},
            </if>
            <if test="salary != null and salary > 0">
                `salary` = #{salary} ,
            </if>
        </set>
         where `id` = #{id};

    </update>
8.8.3 测试
  @Test
    public void updateMonster_set(){
        //创建一个Map集合
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("id",19);
        params.put("birthday", new Date());
        params.put("age",1000);
        //调用目标方法
        monsterMapper.updateMonster_set(params);

        //判断是否为空
        if(sqlSession != null){
            //提交事务
            sqlSession.commit();
            //关闭资源
            sqlSession.close();

        }

    }

9. 映射关系一对一

9.1基本介绍

  • 项目中1对1的关系是一个基本的映射关系
  • 映射方式
  • 通过配置xxxMapper.xml实现1对1配置【xml配置方式】
  • 通过注解的方式实现1对1【注解方式】

9.2 基本配置

9.2.1 数据库代码
create table `person`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(32) NOT NULL DEFAULT '',
    `card_id` INT
)charset utf8;

create table `identitycard`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `card_sn` VARCHAR(32) NOT NULL DEFAULT ''
)charset utf8;

insert into `identitycard` VALUES(1,'111111111111110');
insert into `person` VALUES(1,'张三',1);

select * from `identitycard`;
select * from `person` ;

select * from `person`,`identitycard` where `person`.id = 1 and `person`.card_id = `identitycard`.id ;

select person.id,`name`,`card_id`,`identitycard`.id as hsp_id,`card_sn` from `person`,`identitycard`
        where `person`.id = 1 and `person`.card_id = `identitycard`.id ;

9.3 配置Mapper.xml的方式【有两种方式】

9.3.1 方式一
9.3.1.1 mapper接口代码
import com.leon.entity.Person;

/**
 * ClassName:PersonMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:34
 * @Version: 1.0
 */
public interface PersonMapper {

    //通过Person的id获取到Person,包含这个Person关联的IdentityCard对象【级联查询】
    public Person selectPersonById(Integer id);
}
9.3.1.2 Mapper.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.mapper.PersonMapper">
    <!--
        //通过Person的id获取到Person,包含这个Person关联的IdentityCard对象【级联查询】
        public Person selectPersonById(Integer id);
        1. 这里为什么直接使用类名是因为在mybatis-config.xml文件中配置了别名
        2. 如果这里直接使用resultType="Person",Person对象属性IdentityCard是没有值的
        3. 使用配置的第一种方式,用resultMap,这样需要使用多表联查,也就是需要改变sql语句
        4. 这里为什么还是使用Person作为返回类型【type="Person"】,首先:方法的返回值是Person对象,其次:IdentityCard对象是
            Person对象的属性,所以使用Person作为返回值
        5. <result property="id" column="id"/>和<result property="name" column="name"/>
            配置和之前的一样
        6. association – 一个复杂类型的关联;许多结果将包装成这种类型
            ①property="card" 表示在Person对象中的属性名称
            ②javaType="IdentityCard"    一个 Java 类的全限定名,或一个类型别名,
               因为在mybatis-config.xml配置了别名,所以这里直接写类名
            ③<result property="id" column="id"/>he<result property="card_sn" column="card_sn"/>
              和之前的配置一样
            ④column="id" 表示sql语句查询到的字段名称,这个是可变的,因为可以设置别名
        7. <id property="id" column="id"/>
            ① 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
            ② property="id" 表示Person属性id 通常是主键
            ③ column="id" 表示表字段名称

    -->
    <resultMap id="personFiledMapper" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="card" javaType="IdentityCard">
            <!--<result property="id" column="hsp_id"/>-->
            <result property="id" column="id"/>
            <result property="card_sn" column="card_sn"/>
        </association>
    </resultMap>
    <select id="selectPersonById" parameterType="Integer" resultMap="personFiledMapper">
        <!--select * from `person` where `id` = #{id}-->
        <!--select person.id,`name`,`card_id`,`identitycard`.id as hsp_id,`card_sn` from `person`,`identitycard`-->
        <!--where `person`.id = #{id} and `person`.card_id = `identitycard`.id ;-->
        select * from `person`,`identitycard`
        where `person`.id = #{id} and `person`.card_id = `identitycard`.id ;
    </select>
</mapper>
9.3.1.3 测试
package com.leon.mapper;

import com.leon.entity.IdentityCard;
import com.leon.entity.Person;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:IdentityCardMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:38
 * @Version: 1.0
 */
public class PersonMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private PersonMapper personMapper;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        personMapper = sqlSession.getMapper(PersonMapper.class);
    }

    @Test
    public void selectPersonById(){
        //d调用目标方法
        Person person = personMapper.selectPersonById(1);
        System.out.println(person);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }
    }
}
9.3.1 方式二
9.3.2.1 mapper接口代码
package com.leon.mapper;

import com.leon.entity.IdentityCard;

/**
 * ClassName:IdentityCardMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:34
 * @Version: 1.0
 */
public interface IdentityCardMapper {
    //根据id查询IdentityCard对象信息
    public IdentityCard selectIdentityCardById(Integer id);
}
======================================================================================

package com.leon.mapper;

import com.leon.entity.Person;

/**
 * ClassName:PersonMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:34
 * @Version: 1.0
 */
public interface PersonMapper {

    //通过Person的id获取到Person,包含这个Person关联的IdentityCard对象【级联查询】
    public Person selectPersonById2(Integer id);
}
9.3.2.2 Mapper.xml配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.IdentityCardMapper">
    <!--
        //根据id查询IdentityCard对象信息
    public IdentityCard selectIdentityCardById(Integer id);
    -->
    <select id="selectIdentityCardById" parameterType="Integer" resultType="IdentityCard">
        select * from `identitycard` where `id` = #{id}
    </select>

</mapper>
======================================================================================
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.mapper.PersonMapper">
    <!--
        //通过Person的id获取到Person,包含这个Person关联的IdentityCard对象【级联查询】
        public Person selectPersonById2(Integer id);
        方式2
        1. 先通过 select * from `person` where `id` = #{id}进行查询
        2. 然后通过查询到的card_id,再调用IdentityCardMapper接口的指定的方法,通过IdentityCardMapper配置文件
           查询到的IdentityCard数据进行数据的填充
        3. 第二种方式就是将多表联查,拆分成单表联查,简化了查询操作,这样不仅更加简洁,而且便于维护
        4. <association property="card" column="card_id" select="com.leon.mapper.IdentityCardMapper.selectIdentityCardById"/>
           ① property="card" 表示Person对象中,IdentityCard对象属性名称
           ② column="card_id" 这个是select * from `person` where `id` = #{id} 语句查询到的字段名称
              这个是会改变的,如果起了别名也是要随着字段的改变而改变的,这个并不是固定的,与查询到的字段有关
           ③ select="com.leon.mapper.IdentityCardMapper.selectIdentityCardById" 表示要调用的目标方法
              因为在配置文件中进行了方法的配置,所以能够获取的数据,并且封装成identityCard对象
    -->
    <resultMap id="personFiledMapper2" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="card" column="card_id" select="com.leon.mapper.IdentityCardMapper.selectIdentityCardById"/>
    </resultMap>
    <select id="selectPersonById2" parameterType="Integer" resultMap="personFiledMapper2">
        select * from `person` where `id` = #{id}
    </select>

</mapper>
9.3.2.3 测试
package com.leon.mapper;

import com.leon.entity.IdentityCard;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:IdentityCardMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:38
 * @Version: 1.0
 */
public class IdentityCardMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private IdentityCardMapper identityCardMapper;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        identityCardMapper = sqlSession.getMapper(IdentityCardMapper.class);
    }

    @Test
    public void selectIdentityCardById(){
        //调用目标方法
        IdentityCard identityCard = identityCardMapper.selectIdentityCardById(1);
        //打印结果
        System.out.println(identityCard);

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

    }
}
======================================================================================
package com.leon.mapper;

import com.leon.entity.IdentityCard;
import com.leon.entity.Person;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:IdentityCardMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:38
 * @Version: 1.0
 */
public class PersonMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private PersonMapper personMapper;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        personMapper = sqlSession.getMapper(PersonMapper.class);
    }

    @Test
    public void selectPersonById2(){
        //d调用目标方法
        Person person = personMapper.selectPersonById2(1);
        System.out.println(person);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }
    }
}

9.4 注解方式

9.4.1 代码
9.4.1.1 接口代码
package com.leon.mapper;

import com.leon.entity.IdentityCard;
import org.apache.ibatis.annotations.Select;

/**
 * ClassName:IdentityCardMapperAnnotation
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 10:39
 * @Version: 1.0
 */
public interface IdentityCardMapperAnnotation {

    @Select("select * from `identitycard` where `id` = #{id}")
    public IdentityCard selectIdentityCardById(Integer id);

}
======================================================================================
package com.leon.mapper;

import com.leon.entity.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

/**
 * ClassName:PersonMappperAnnotation
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 10:42
 * @Version: 1.0
 */
public interface PersonMapperAnnotation {


    /*
    * 1.使用Results注解,该注解有一个Result数组
    * 2.@Result注解配置和xml配置中的result标签的配置类似
    * 3.id = true 表示该字段和属性是主键
    * 4.@Result 注解中的one属性表示是1对1的关系映射,它需要@One注解
    *   @One注解中的select属性和 xml配置中的配置association标签的select属性类似
    * */
    @Select("select * from `person` where `id` = #{id}")
    @Results({
            @Result(id = true, property = "id", column = "id"),
            @Result(property = "name", column = "name"),
            @Result(property = "card",column = "card_id",one = @One(select = "com.leon.mapper.IdentityCardMapperAnnotation.selectIdentityCardById"))
    })
    public Person selectPersonById(Integer id);
}
9.4.1.2 测试
package com.leon.mapper;

import com.leon.entity.IdentityCard;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:IdentityCardMapperAnnotationTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 10:40
 * @Version: 1.0
 */
public class IdentityCardMapperAnnotationTest {


    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private IdentityCardMapperAnnotation identityCardMapperAnnotation;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        identityCardMapperAnnotation = sqlSession.getMapper(IdentityCardMapperAnnotation.class);
    }

    @Test
    public void selectIdentityCardById(){
        //调用目标方法
        IdentityCard identityCard = identityCardMapperAnnotation.selectIdentityCardById(1);

        System.out.println(identityCard);

        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();

        }
    }


}
======================================================================================
package com.leon.mapper;

import com.leon.entity.Person;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:IdentityCardMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/16 22:38
 * @Version: 1.0
 */
public class PersonMapperAnnotationTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private PersonMapperAnnotation personMapperAnnotation;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        personMapperAnnotation = sqlSession.getMapper(PersonMapperAnnotation.class);
    }

    @Test
    public void selectPersonById(){
        //d调用目标方法
        Person person = personMapperAnnotation.selectPersonById(1);
        System.out.println("注解:-----="+person);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }
    }


}

9.5 注意事项和细节

  • 表是否设置外键,对MyBatis进行对象/级联映射没有影响
  • 推荐使用xml配置方式的第二种方式

10. 映射关系多对一(或一对多)

10.1 基本介绍

  • 项目中多对一的关系是一个基本的映射关系,多对一也可以理解成一对多
  • User–Pet :一个用户可以养多只宠物
  • Dep–Emp:一个部门可以有多个员工
  • 注意细节
  • 直接学习双向的多对一的关系,单向的多对一比双向的多对一简单
  • 在实际的项目开发中,要求会使用双向的多对一的映射关系
  • 什么是双向的多对一的关系:比如通过User可以直接查询到对应的Pet,反过来通过Pet也可以级联查询到对应的User信息
  • 多对多的关系,是在多对一的基础上扩展即可

10.2 数据库表的设计和entity类

10.2.1 数据库表的设计
-- 多对一
create table `mybatis_user`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `name` VARCHAR(32) NOT NULL DEFAULT ''
)charset=utf8 

create table `mybatis_pet`(
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `nickname` VARCHAR(32) NOT NULL DEFAULT '',
    `user_id` INT
)charset=utf8;

insert into `mybatis_user` VALUES(null,'李强'),(null,'张飞');
insert into `mybatis_pet` VALUES(null,'黑背',1),(null,'二哈',1);
insert into `mybatis_pet` VALUES(null,'波斯猫',2),(null,'贵妃猫',2);

select * from `mybatis_user`;
select * from `mybatis_pet`;
10.2.2 entity类
package com.leon.entity;

import java.util.List;

/**
 * ClassName:User
 * Package:com.leon.entity
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 14:36
 * @Version: 1.0
 */
public class User {

    private String id;

    private String name;

    private List<Pet> pets;

    public User() {
    }

    public User(String id, String name, List<Pet> pets) {
        this.id = id;
        this.name = name;
        this.pets = pets;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Pet> getPets() {
        return pets;
    }

    public void setPets(List<Pet> pets) {
        this.pets = pets;
    }

    //记住这里不要设置pets的输出,因为形成相互调用然后,造成死循环,然后报错
    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}
======================================================================================
package com.leon.entity;

/**
 * ClassName:Pet
 * Package:com.leon.entity
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 14:36
 * @Version: 1.0
 */
public class Pet {

    private Integer id;

    private String nickname;

    private User user;

    public Pet() {
    }

    public Pet(Integer id, String nickname, User user) {
        this.id = id;
        this.nickname = nickname;
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    //记住这里不要设置User的输出,不然会造成死循环,然后报错
    @Override
    public String toString() {
        return "Pet{" +
                "id=" + id +
                ", nickname='" + nickname + '\'' +
                '}';
    }
}

10.3 XML配置方式

10.3.1 接口代码
package com.leon.mapper;

import com.leon.entity.User;

/**
 * ClassName:UserMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 15:47
 * @Version: 1.0
 */
public interface UserMapper {
    //通过id查询User信息
    public User selectUserById(Integer id);

}
======================================================================================
package com.leon.mapper;

import com.leon.entity.Pet;

import java.util.List;

/**
 * ClassName:PetMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 15:48
 * @Version: 1.0
 */
public interface PetMapper {
    //通过user_id
    public List<Pet> selectPetByUserId(Integer userId);
    //通过id查询Pet
    public Pet selectPetById(Integer id);

}
10.3.2 Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.mapper.UserMapper">
    <!--
        //通过id查询User信息
        public User selectUserById(Integer id);
        1. 因为pets属性是集合,因此这里需要使用collection标签来处理
        2. collection  一个复杂类型的集合,多对一关系映射就使用这个标签
        3. ofType="Pet" 表示返回的数据类型,也就是集合中要存放的类型
        4. property="pets" 表示User对象的pets属性
        5. column="id" 表示select * from `mybatis_user` where `id` = #{id}语句
            查询到的id字段,这个是可变的,因为可以取别名
    -->
    <resultMap id="UserFiledMapper" type="User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="pets" column="id"
                    ofType="Pet" select="com.leon.mapper.PetMapper.selectPetByUserId"/>
    </resultMap>

    <select id="selectUserById" resultMap="UserFiledMapper" parameterType="Integer">
        select * from `mybatis_user` where `id` = #{id}
    </select>

</mapper>
======================================================================================
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leon.mapper.PetMapper">
    <!--
            //通过user_id
            public List<Pet> selectPetByUserId(Integer userId);

    -->
    <resultMap id="PetFiledMapper" type="Pet">
        <id property="id" column="id"/>
        <result property="nickname"  column="nickname"/>
        <association property="user" column="user_id"  select="com.leon.mapper.UserMapper.selectUserById"/>
    </resultMap>
    <select id="selectPetByUserId" resultMap="PetFiledMapper" resultType="Integer">
        select * from `mybatis_pet` where `user_id` = #{userId};
    </select>

    <!--
            //通过id查询Pet
            public Pet selectPetById(Integer id);
    -->
    <select id="selectPetById" resultMap="PetFiledMapper" parameterType="Integer">
        select * from `mybatis_pet` where `id` = #{id};
    </select>

</mapper>
10.3.3 测试
package com.leon.mapper;

import com.leon.entity.Pet;
import com.leon.entity.User;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * ClassName:WifeMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 22:02
 * @Version: 1.0
 */
public class PetMapperTest {


    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private PetMapper petMapper;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init() {

        //获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        petMapper = sqlSession.getMapper(PetMapper.class);
    }

    @Test
    public void selectPetByUserId() {
        //调用目标方法
        List<Pet> pets = petMapper.selectPetByUserId(2);
        //循环遍历结果
        for (Pet pet : pets) {
            System.out.println("pet==> " + pet + " user== " + pet.getUser());
        }
        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

    @Test
    public void selectPetById() {
        //调用目标方法
        Pet pet = petMapper.selectPetById(2);

        System.out.println("pet==> " + pet + " user== " + pet.getUser());
        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

}
====================================================================================
package com.leon.mapper;

import com.leon.entity.User;
import com.leon.entity.Wife;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:WifeMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 22:02
 * @Version: 1.0
 */
public class UserMapperTest {


    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private UserMapper userMapper;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        userMapper = sqlSession.getMapper(UserMapper.class);
    }

    @Test
    public void selectUserById(){
      //调用到目标方法
      User user = userMapper.selectUserById(1);

        System.out.println(user);
        System.out.println(user.getPets());

        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

}

10.4 注解方式

10.4.1 接口代码
package com.leon.mapper;

import com.leon.entity.User;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

/**
 * ClassName:UserMapperAnnotation
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 16:54
 * @Version: 1.0
 */
public interface UserMapperAnnotation {

   /*
   *    <!--
        //通过id查询User信息
        public User selectUserById(Integer id);
        1. collection  一个复杂类型的集合,多对一关系映射就使用这个标签
        2. ofType="Pet" 表示返回的数据类型
    -->
    <resultMap id="UserFiledMapper" type="User">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="pets" column="id"
                    ofType="Pet" select="com.leon.mapper.PetMapper.selectPetByUserId"/>
    </resultMap>

    <select id="selectUserById" resultMap="UserFiledMapper" parameterType="Integer">
        select * from `mybatis_user` where `id` = #{id}
    </select>
   * 1.many 表示多对一的关系
   * 2.@Many 注解的使用方式和@One的使用方式类似
   * */
   //通过id查询User信息
   @Select(" select * from `mybatis_user` where `id` = #{id}")
   @Results({
           @Result(id = true,property = "id",column = "id"),
           @Result(property = "name",column = "name"),
           @Result(property = "pets",column = "id" ,many = @Many(select = "com.leon.mapper.PetMapperAnnotation.selectPetByUserId"))
   })
   public User selectUserById(Integer id);

}
======================================================================================
package com.leon.mapper;

import com.leon.entity.Pet;
import org.apache.ibatis.annotations.*;

import java.util.List;

/**
 * ClassName:PetMapperAnnotation
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/18 16:58
 * @Version: 1.0
 */
public interface PetMapperAnnotation {

    /*
    *  <!--
            //通过user_id
            public List<Pet> selectPetByUserId(Integer userId);

    -->
    <resultMap id="PetFiledMapper" type="Pet">
        <id property="id" column="id"/>
        <result property="nickname"  column="nickname"/>
        <association property="user" column="user_id"  select="com.leon.mapper.UserMapper.selectUserById"/>
    </resultMap>
    <select id="selectPetByUserId" resultMap="PetFiledMapper" resultType="Integer">
        select * from `mybatis_pet` where `user_id` = #{userId};
    </select>
    * 1. id = "petMapper" 表示该Results注解的名称,可以提供给其它的方法引用,类似于XML配置文件的id属性
    * */
    //通过user_id
    @Select("select * from `mybatis_pet` where `user_id` = #{userId};")
    @Results(id = "petMapper",value = {
            @Result(id = true,property = "id" ,column = "id"),
            @Result(property = "nickname",column = "nickname"),
            @Result(property = "user",column = "user_id",one = @One(select = "com.leon.mapper.UserMapperAnnotation.selectUserById"))
    })
    public List<Pet> selectPetByUserId(Integer userId);

    /*
    * <!--
            //通过id查询Pet
            public Pet selectPetById(Integer id);
    -->
    <select id="selectPetById" resultMap="PetFiledMapper" parameterType="Integer">
        select * from `mybatis_pet` where `id` = #{id};
    </select>
    *  1. ResultMap 注解使用来引用Results注解的(类似于XML配置中resultMap标签)
    * */
    //通过id查询Pet
    @Select("select * from `mybatis_pet` where `id` = #{id};")
    @ResultMap("petMapper")
    public Pet selectPetById(Integer id);

}
10.4.3 测试
package com.leon.mapper;

import com.leon.entity.Pet;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * ClassName:WifeMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 22:02
 * @Version: 1.0
 */
public class PetMapperAnnotionTest {


    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private PetMapperAnnotation petMapperAnnotation;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init() {

        //获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        petMapperAnnotation = sqlSession.getMapper(PetMapperAnnotation.class);
    }

    @Test
    public void selectPetByUserId() {
        //调用目标方法
        List<Pet> pets = petMapperAnnotation.selectPetByUserId(2);
        //循环遍历结果
        for (Pet pet : pets) {
            System.out.println("pet==> " + pet + " user== " + pet.getUser());
        }
        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

    @Test
    public void selectPetById() {
        //调用目标方法
        Pet pet = petMapperAnnotation.selectPetById(2);

        System.out.println("pet==> " + pet + " user== " + pet.getUser());
        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

}
======================================================================================
package com.leon.mapper;

import com.leon.entity.User;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

/**
 * ClassName:WifeMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/17 22:02
 * @Version: 1.0
 */
public class UserMapperAnnotationTest {


    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private UserMapperAnnotation userMapperAnnotation;

    /*
     * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
     * */
    @Before
    public void init(){

        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        userMapperAnnotation = sqlSession.getMapper(UserMapperAnnotation.class);
    }

    @Test
    public void selectUserById(){
        //调用目标方法
        User user = userMapperAnnotation.selectUserById(1);
        System.out.println(user);
        System.out.println(user.getPets());

        //判断是否为空
        if (sqlSession != null) {
            //关闭资源
            sqlSession.close();
        }
    }

}

11. 缓存-提高检索效率的利器

11.1 一级缓存

11.1.1 基本介绍
  • 默认情况下,mybatis是启用一级缓存的【本地缓存、local Cache】,它是SqlSession级别的

  • 同一个SqlSession接口对象调用了相同的select语句,会直接从缓存里获取,而不是再去查询数据库,因为操作数据库是网络连接,如果网络不好会影响效率的

  • 原理图

11.1.2 一级缓存-快速入门
11.1.2.1 entity
package com.leon.entity;

import java.util.Date;

/**
 * ClassName:Monster
 * Package:com.leon.entity
 * Description:
 *         1. 一个普通的POJO类
 *         2. 使用原生态的sql语句查询结果还是要封装成对象
 *         3. 要求大家这里的实体类属性名和表名字段保持一致
 * @Author: leon-->ZGJ
 * @Create: 2024/5/5 22:20
 * @Version: 1.0
 */
public class Monster {

    //属性要和表字段有对应关系
    private Integer id;

    private String name;

    private Integer age;

    private Integer gender;

    private String email;

    private Date birthday;

    private Double salary;

    public Monster() {
    }

    public Monster(Integer id, String name, Integer age, Integer gender, String email, Date birthday, Double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.email = email;
        this.birthday = birthday;
        this.salary = salary;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }

}
11.1.2.2 mapper 接口
package com.leon.mapper;

import com.leon.entity.Monster;

import java.util.List;

/**
 * ClassName:MonsterMapper
 * Package:com.leon.mapper
 * Description:
 *          1.这是一个接口
 *          2.该接口用于声明操作monster表的方法
 *          3.这些方法可以通过注解或者XML文件来实现
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:06
 * @Version: 1.0
 */
public interface MonsterMapper {

    //修改一条信息
    public void updateMonster(Monster monster);

    //通过Id查询一条信息
    public Monster getMonsterById(int id);

    //查询所有的信息
    public List<Monster> getAllMonsters();

}
11.1.2.3 mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.MonsterMapper">


        <update id="updateMonster" parameterType="com.leon.entity.Monster">
            update `monster` set
            `name`= #{name},
            `gender`= #{gender},
            `age`= #{age},
            `birthday`= #{birthday},
            `email`= #{email},
            `salary` = #{salary}
            where `id` = #{id};
        </update>
        <!--
            1.resultType 表示你要返回的什么结果
        -->
        <select id="getMonsterById" resultType="Monster">
            select * from `monster` where `id` = #{id} ;
        </select>

        <!--
            1.如果你返回的是一个结合,这个resultType设置的要是集合中所存放的数据类型
        -->
        <select id="getAllMonsters" resultType="Monster">
            select * from `monster`;
        </select>
</mapper>
11.1.2.4 测试
package com.leon.mapper;

import com.leon.entity.Monster;
import com.leon.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.Date;
import java.util.List;

/**
 * ClassName:MonsterMapperTest
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:56
 * @Version: 1.0
 */
public class MonsterMapperTest {

    //创建一个SqlSession对象属性
    private SqlSession sqlSession;
    //创建一个MonsterMapper对象属性
    private MonsterMapper monsterMapper;

    /*
    * 1.标注了Before注解之后每次其他测试方法执行时,都会执行init()方法
    * */
    @Before
    public void init(){
        //获取SqlSession对象
        sqlSession =  MyBatisUtils.getSqlSession();
        //获取到MonsterMapper对象
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);
    }

    @Test
    public void getMonsterById(){
        //调用目标方法
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);

        //这里会发出sql语句,因为一级缓存中没有该id的数据
        monsterMapper.getMonsterById(13);

        //调用目标方法
        System.out.println("==因为一级缓存时默认打开的,所以查询相同id的,不会发出sql");
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById1);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }

}
11.1. 3一级缓存存储结构

image-20240519204932503

11.1.4 一级缓存失效分析
11.1.4.1 关闭SqlSession会话后,再次查询会到数据库去查询
 @Test
    public void getMonsterById2(){
        //调用目标方法
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);


        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

        //因为关闭了SqlSession所以需要再次获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);

        //调用目标方法
        System.out.println("==关闭SqlSession会话后,再次查询,会到数据库查询==");
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById1);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }
}
11.1.4.2 当执行SqlSession的clearCache() 会使一级缓存失效
   @Test
    public void getMonsterById3(){
        //调用目标方法
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);


       //这里使用clearCache方法清除缓存,不需要重新获取SqlSession会话
       sqlSession.clearCache();

        //调用目标方法
        System.out.println("==如果执行clearCache()方法,再次查询,会到数据库查询==");
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        Monster monsterById2 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById1);
        System.out.println(monsterById2);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }
11.1.4.3 当对同一个Monster进行修改,该对象在一级缓存会失效
@Test
    public void getMonsterById4(){
        //调用目标方法
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);

        /*
        *   1.如果你执行修改语句,之前同id的对象将会失效
        *   2.再次查询到的结果,如果没有提前commit,而是最后面commit,
        *     在commit之前查询的的数据虽然是新的,但是数据库因为没有commit数据并没有变化,
        *     只有在commit后数据才会更新,但是此时的一级缓存是没有该对象的数据,MyBatis会发出sql
        *     查询到新的数据,并缓存,因为数据在数据库虽然没有刷新,但是修改的数据却提交到数据库了
        *     只是没有commit而已
        * */
        monsterById.setName("孙悟空");

        monsterMapper.updateMonster(monsterById);

        //调用目标方法
        System.out.println("==如果对该对象进行修改,再次查询,会到数据库查询==");
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        Monster monsterById2 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById1);
        System.out.println(monsterById2);
        //判断是否为空
        if(sqlSession != null){
            //提交事务
            sqlSession.commit();
            //关闭资源
            sqlSession.close();

        }


    }

11.2 二级缓存

11.2.1 基本介绍
  • 二级缓存和一级缓存都是为了提高检索效率的技术

  • 最大的区别就是作用域的范围不一样,一级缓存的作用域是SqlSession会话级别,在一次会话有效,而二级缓存作用域是全局范围,针对不用的会话都有效

  • 精准度 = 查询到的次数 / 总的查询次数

  • 原理图

11.2.2 二级缓存-快速入门
11.2.2.1 entity
package com.leon.entity;

import java.io.Serializable;
import java.util.Date;

/**
 * ClassName:Monster
 * Package:com.leon.entity
 * Description:
 *         1. 一个普通的POJO类
 *         2. 使用原生态的sql语句查询结果还是要封装成对象
 *         3. 要求大家这里的实体类属性名和表名字段保持一致
 * @Author: leon-->ZGJ
 * @Create: 2024/5/5 22:20
 * @Version: 1.0
 */
//因为有的第三方二级缓存工具,需要使用序列化
public class Monster implements Serializable {

    //属性要和表字段有对应关系
    private Integer id;

    private String name;

    private Integer age;

    private Integer gender;

    private String email;

    private Date birthday;

    private Double salary;

    public Monster() {
    }

    public Monster(Integer id, String name, Integer age, Integer gender, String email, Date birthday, Double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.email = email;
        this.birthday = birthday;
        this.salary = salary;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }

}
11.2.2.2 mapper接口代码
package com.leon.mapper;

import com.leon.entity.Monster;

import java.util.List;

/**
 * ClassName:MonsterMapper
 * Package:com.leon.mapper
 * Description:
 *          1.这是一个接口
 *          2.该接口用于声明操作monster表的方法
 *          3.这些方法可以通过注解或者XML文件来实现
 * @Author: leon-->ZGJ
 * @Create: 2024/5/9 9:06
 * @Version: 1.0
 */
public interface MonsterMapper {

    //修改一条信息
    public void updateMonster(Monster monster);

    //通过Id查询一条信息
    public Monster getMonsterById(int id);

    //查询所有的信息
    public List<Monster> getAllMonsters();

}
11.2.2.3 Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.MonsterMapper">

    <!--
        配置MyBatis的二级缓存
        1. eviction 表示清除策略
            ① LRU – 最近最少使用:移除最长时间不被使用的对象。
            ② FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
            ③ SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
            ④ WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
        2. flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量
            也就是设置刷新缓存的时间,这里也就是60秒刷新一次
        3. size 引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
            也就是可以保存多少个对象,如果达到预定值,就会执行清除策略
        4. readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。
            因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)
            返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
    -->
    <cache
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true"/>


        <update id="updateMonster" parameterType="com.leon.entity.Monster">
            update `monster` set
            `name`= #{name},
            `gender`= #{gender},
            `age`= #{age},
            `birthday`= #{birthday},
            `email`= #{email},
            `salary` = #{salary}
            where `id` = #{id};
        </update>
        <!--
            1.resultType 表示你要返回的什么结果
        -->
        <select id="getMonsterById" resultType="Monster">
            select * from `monster` where `id` = #{id} ;
        </select>

        <!--
            1.如果你返回的是一个结合,这个resultType设置的要是集合中所存放的数据类型
        -->
        <select id="getAllMonsters" resultType="Monster">
            select * from `monster`;
        </select>
</mapper>
11.2.2.4 测试
 @Test
    public void level2CacheTest(){
        //调用目标方法
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);

        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }

        //因为关闭了SqlSession所以需要再次获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);

        //调用目标方法
        System.out.println("==虽然关闭了会话,但是配置了二级缓存,再次查询,依然不会到数据库查询==");
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        Monster monsterById2 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById1);
        System.out.println(monsterById2);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }
11.2.3 注意事项
  • 理解二级缓存策略的参数:

  • 解析参数

  • 创建了FIFO策略,每隔60秒刷新一次,最多存放512个对象而且返回的对象被认为是只读的

  • eviction:缓存的回收策略

  • flushInterval:每次刷新的时间间隔,单位是毫秒

  • size:引用数目,内存大可以多配置点,要记住缓存的对象数目和你运行环境的可用内存资源数目有关,默认值是1024

  • readOnly:(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

  • 四大策略

  • LRU – 最近最少使用:移除最长时间不被使用的对象。默认

  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。

  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

  • 如何禁用二级缓存

  • 在全局配置文件中的setting中配置cacheEnabled属性为false,则全局的禁用了二级缓存

      <!--
                全局性地开启或关闭所有映射器配置文件中已配置的任何缓存
                1. 在默认情况下就是true,所以可以配置
            -->
            <setting name="cacheEnabled" value="false"/>
    
  • 在目标的Mapper.xml文件中,将进行删除或注释掉,就会禁用该Mapper.xml的二级缓存,注意:这个只是禁用了目标Mapper.xml文件,其他Mapper.xml并没有被禁用,当然前提是你在全局配置文件中没有禁用二级缓存

     <!--<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    
  • 更加细粒度,在配置方法上指定禁用二级缓存,这个只是禁用了目标方法的二级缓存,其他的方法并没有被禁用,当然前提是全局配置文件和Mapper.xml文件都开启了二级缓存,设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出说sql去查询,默认情况下是true,即该sql使用二级缓存,注意:一般我们不需要去修改,使用默认的即可

     <!--
                1.resultType 表示你要返回的什么结果
            -->
            <select id="getMonsterById" resultType="Monster" useCache="false">
                select * from `monster` where `id` = #{id} ;
            </select>
    
  • MyBatis刷新二级缓存的设置,insert、update、delete操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读默认为true,默认情况下为true即刷新缓存,一般不用修改

    <update id="updateMonster" parameterType="com.leon.entity.Monster" flushCache="true">
              update `monster` set
              `name`= #{name},
              `gender`= #{gender},
              `age`= #{age},
              `birthday`= #{birthday},
              `email`= #{email},
              `salary` = #{salary}
              where `id` = #{id};
          </update>
    

11.3 缓存执行顺序

11.3.1 MyBatis一级缓存和二级缓存的执行顺序
  • 二级 > 一级 > 数据库
  • 是先从二级缓存查询数据,如果二级缓存没有,再去一级缓存查询,如果都没有才去数据库查询
11.3.2 缓存执行顺序细节
  • 不会出现一级缓存和二级缓存中间有同一个数据。因为二级缓存是一级缓存关闭之后才有的,也就是只有在关闭一级缓存时,一级缓存才会把数据放入到二级缓存中
  • 如果没有关闭一级缓存,二级缓存的命中率一直是0.0,因而可以得出二级缓存的数据是只有一级缓存关闭时才会放入到二级缓存
11.3.3 测试
@Test
    public void cacheSeqTest(){
        System.out.println("查询第1次");
        //调用目标方法
        //去数据库查询数据
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);

        //判断是否为空
        if(sqlSession != null){
            //关闭资源,当关闭一级缓存时,如果配置了二级缓存,一级缓存中的数据会放入到二级缓存中
            sqlSession.close();

        }

        //因为关闭了SqlSession所以需要再次获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);

        //调用目标方法
        System.out.println("==虽然关闭了会话,但是配置了二级缓存,再次查询,依然不会到数据库查询==");
        System.out.println("查询第2次");
        //因为关闭了SqlSession,一级缓存没有数据,二级缓存有数据据,不会发出SQL,二级缓存
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        System.out.println(monsterById1);
        System.out.println("查询第2次");
        //因为关闭了SqlSession,一级缓存没有数据,二级缓存有数据据,不会发出SQL,二级缓存
        Monster monsterById2 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById2);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }

11.4 EhCache

11.4.1 基本介绍
  • EhCache 是一个纯Java的缓存框架,具有快速、精干等特点

  • MyBatis有自己默认的二级缓存,但是在实际项目中,往往使用的是更加专业的第三方缓存产品作为MyBatis的二级缓存,EhCache就是非常优秀的缓存产品

11.4.2 代码
11.4.2.1 依赖
 <dependencies>
        <!--引入ehcache核心库依赖-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.11</version>
        </dependency>
        <!--引入slf4j,日志输出-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <!--引入mybatis整合ehcache-->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.1</version>
        </dependency>
    </dependencies>
11.4.2.2 Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--
    解读
    1.这是一个mapper.xml文件,该文件可以去实现对应的接口的方法
    2.mapper标签只能有一个,一个mapper标签对应着一个mapper接口实现类
    3.namespace 指定该xml文件和哪个接口对应, 属性值是接口的全路径
-->
<mapper namespace="com.leon.mapper.MonsterMapper">
    <!--配置ehcache-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
</mapper>
11.4.2.3 mybatis-config.xml
<!--
            全局性地开启或关闭所有映射器配置文件中已配置的任何缓存
            1. 在默认情况下就是true,所以可以配置
        -->
        <setting name="cacheEnabled" value="true"/>
11.4.2.4 ehcache.xml
  • 参考网址:https://www.cnblogs.com/zqyanywn/p/10861103.html
  • 参考网址:https://www.taobye.com/f/view-11-23.html
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <!--
     另一个版本:
       name:缓存名称。
       maxElementsInMemory:缓存最大个数。
       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
       可理解为: TTI用于设置对象在cache中的最大闲置时间,就是 在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间。

       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
       可理解为: TTL用于设置对象在cache中的最大存活时间,就是 无论对象访问或是不访问(闲置),这个对象在cache中的存活时间。

       overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
       maxElementsOnDisk:硬盘最大缓存个数。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
       clearOnFlush:内存数量最大时是否清除。
    -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>
11.4.2.5 测试
 @Test
    public void ehcacheTest(){
        System.out.println("查询第1次");
        //调用目标方法
        //去数据库查询数据
        Monster monsterById = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById);

        //判断是否为空
        if(sqlSession != null){
            //关闭资源,当关闭一级缓存时,如果配置了二级缓存,一级缓存中的数据会放入到二级缓存中
            sqlSession.close();

        }

        //因为关闭了SqlSession所以需要再次获取SqlSession对象
        sqlSession = MyBatisUtils.getSqlSession();
        monsterMapper = sqlSession.getMapper(MonsterMapper.class);

        //调用目标方法
        System.out.println("==虽然关闭了会话,但是配置了二级缓存,再次查询,依然不会到数据库查询==");
        System.out.println("查询第2次");
        //因为关闭了SqlSession,一级缓存没有数据,二级缓存有数据据,不会发出SQL,二级缓存
        Monster monsterById1 = monsterMapper.getMonsterById(10);
        System.out.println(monsterById1);
        System.out.println("查询第2次");
        //因为关闭了SqlSession,一级缓存没有数据,二级缓存有数据据,不会发出SQL,二级缓存
        Monster monsterById2 = monsterMapper.getMonsterById(10);
        //输出对象信息
        System.out.println(monsterById2);
        //判断是否为空
        if(sqlSession != null){
            //关闭资源
            sqlSession.close();

        }


    }
11.4.3 细节说明
  • MyBatis提供了一个接口Cache【org.apache.ibatis.cache.Cache】
  • 只要实现了Cache接口,就可以作为二级缓存产品和MyBatis整合使用,Ehcache就是实现了该接口
  • MyBatis默认情况(即一级缓存)是使用PerpetualCache类实现Cache接口的,是核心类
  • 当使用了Ehcache后,就是EhCacheCache类实现Cache接口的,是核心类
  • 查看源码,缓存的的本质就是:Map cache = new HashMap<>()

二、手动实现MyBatis部分机制

1. MyBatis核心框架示意图

示意图

2. 搭建开发环境

2.1 依赖导入

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leon</groupId>
    <artifactId>make_simple_mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


    <!--导入依赖-->
    <dependencies>
        <!--导入dom4j依赖,用来读取XML文件-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.4</version>
        </dependency>

        <!--导入数据库连接-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.1.0</version>
        </dependency>

        <!--导入lombokm,简化entity/javabean/pojo开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <!--导入单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <!--
           解读
           1.意思是将src/main/java目录下的所有以.xml结尾的文件都加载到真实的工作目录
           2.这个需要写在父项目的pom文件中
            -->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <!--
                       1.**表示多级目录
                       2.*.xml表示任意名称以.xml结尾的文件
                   -->
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>

    </build>

</project>

2.2 数据库创建

create database `mybatis`;

use `mybatis`;

create table `monster`(
    `id` INT NOT NULL AUTO_INCREMENT,
    `name` varchar(255) NOT NULL,
    `gender`TINYINT NOT NULL,
    `age` INT NOT NULL,
    `birthday` DATE DEFAULT NULL,
    `email` VARCHAR(255) NOT NULL,
    `salary` DOUBLE NOT NULL,
    PRIMARY KEY (`id`)
)charset=utf8;

3. 自己写MyBatis的部分底层机制实现设计

4. 代码实现

4.1 entity包

package com.leon.entity;

import lombok.*;

import java.util.Date;

/**
 * ClassName:Monster
 * Package:com.leon.entity
 * Description:
 *      1.@Getter 会标注的类(也就是被标注的JavaBean)的所有属性生成对应的getter方法
 *      2.@Setter 会标注的类(也就是被标注的JavaBean)的所有属性生成对应的setter 方法
 *      3.@ToString 会给目标类(也就是被标注的JavaBean)生成ToString方法
 *      4.@NoArgsConstructor 会给目标类(也就是被标注的JavaBean)生成一个无参构造器
 *      5.@AllArgsConstructor 会给目标类(也就是被标注的JavaBean)生一个全参构造器
 *      6.@Data 注解包含了 getter\setter\hashCode\equals\canEqual\toString\无参构造器,
 *        如果需要全参构造器,则需要添加全参构造器的注解,也需要注意原来的无参构造器会被覆盖,
 *        所以也要添加无参构造器的注解
 * @Author: leon-->ZGJ
 * @Create: 2024/5/5 22:20
 * @Version: 1.0
 */

//@Getter
//@Setter
//@ToString
//@NoArgsConstructor
//@AllArgsConstructor
//@Data
public class Monster {

    //属性要和表字段有对应关系
    private Integer id;

    private String name;

    private Integer age;

    private Integer gender;

    private String email;

    private Date birthday;

    private Double salary;

    public Monster() {
    }

    public Monster(Integer id, String name, Integer age, Integer gender, String email, Date birthday, Double salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.email = email;
        this.birthday = birthday;
        this.salary = salary;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", email='" + email + '\'' +
                ", birthday=" + birthday +
                ", salary=" + salary +
                '}';
    }
}

4.2 mapper包

4.2.1 mapper类
package com.leon.mapper;

import com.leon.entity.Monster;

/**
 * ClassName:MonsterMapper
 * Package:com.leon.mapper
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 16:06
 * @Version: 1.0
 */
public interface MonsterMapper {

    public Monster selectMonsterById(int id);

}
4.2.2 mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.leon.mapper.MonsterMapper">
    <!--配置接口方法selectMonsterById-->
    <select id="selectMonsterById" resultType="com.leon.entity.Monster">
        select * from `monster` where `id` = ?
    </select>
</mapper>

4.3mybatis包

4.3.1 config包
package com.leon.mybatis_v1.config;

/**
 * ClassName:Function
 * Package:com.leon.mybatis.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 19:52
 * @Version: 1.0
 */
public class Function {

    private String sqlType;

    private String methodName;

    private String sql ;

    private Object resultType;

    private String paramsType;


    public Function() {
    }

    public String getSqlType() {
        return sqlType;
    }

    public void setSqlType(String sqlType) {
        this.sqlType = sqlType;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

    public Object getResultType() {
        return resultType;
    }

    public void setResultType(Object resultType) {
        this.resultType = resultType;
    }

    public String getParamsType() {
        return paramsType;
    }

    public void setParamsType(String paramsType) {
        this.paramsType = paramsType;
    }

    @Override
    public String toString() {
        return "Function{" +
                "sqlType='" + sqlType + '\'' +
                ", methodName='" + methodName + '\'' +
                ", sql='" + sql + '\'' +
                ", resultType='" + resultType + '\'' +
                ", paramsType='" + paramsType + '\'' +
                '}';
    }
}
======================================================================================
package com.leon.mybatis_v1.config;

import java.util.concurrent.ConcurrentHashMap;

/**
 * ClassName:MapperBean
 * Package:com.leon.mybatis.config
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 19:57
 * @Version: 1.0
 */
public class MapperBean {

    private String interfaceName;

    private ConcurrentHashMap<String,Function> functions = new ConcurrentHashMap<>();


    public MapperBean() {
    }

    public String getInterfaceName() {
        return interfaceName;
    }

    public void setInterfaceName(String interfaceName) {
        this.interfaceName = interfaceName;
    }

    public ConcurrentHashMap<String, Function> getFunctions() {
        return functions;
    }

    public void setFunctions(ConcurrentHashMap<String, Function> functions) {
        this.functions = functions;
    }

    @Override
    public String toString() {
        return "MapperBean{" +
                "interfaceName='" + interfaceName + '\'' +
                ", functions=" + functions +
                '}';
    }
}
4.3.2 session包
package com.leon.mybatis_v1.session;

import com.leon.entity.Monster;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * ClassName:BaseExecutor
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 15:34
 * @Version: 1.0
 */
public class BaseExecutor implements Executor {

    //创建Configuration对象
    private Configuration  configuration = new Configuration();

    @Override
    public <T> T query(String sql, Object params) {
        //获取Connection连接
        Connection connection = getConnection();

        //创建结果集对象引用
        ResultSet resultSet = null;
        //创建预编译对象
        PreparedStatement preparedStatement = null;

        //创建Monster对象
        Monster monster = new Monster();

        try {
            //给预编译对象引用赋值
            preparedStatement = connection.prepareStatement(sql);
            //给占位符设置值
            preparedStatement.setString(1,params.toString());
            //执行sql语句
            resultSet = preparedStatement.executeQuery();

            //封装数据
            while (resultSet.next()){

                //设置id
                monster.setId(resultSet.getInt("id"));
                //设置name
                monster.setName(resultSet.getString("name"));
                //设置age
                monster.setAge(resultSet.getInt("age"));
                //设置gender
                monster.setGender(resultSet.getInt("gender"));
                //设置salary
                monster.setSalary(resultSet.getDouble("salary"));
                //设置birthday
                monster.setBirthday(resultSet.getDate("birthday"));
                //设置email
                monster.setEmail(resultSet.getString("email"));

            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {

            try {
                //判断是否为空,防止空指针
                if (connection != null){
                    //关闭资源
                    connection.close();
                }
                //判断是否为空,防止空指针
                if(resultSet != null){
                    //关闭资源
                    resultSet.close();
                }
                //判断是否为空,防止空指针
                if(preparedStatement != null){
                    //关闭资源
                    preparedStatement.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }

        }

        return (T)monster;
    }
    //返回数据库连接
    private Connection getConnection() {
        return configuration.build("mybatis_config.xml");
    }
}
======================================================================================
package com.leon.mybatis_v1.session;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.leon.mybatis_v1.config.Function;
import com.leon.mybatis_v1.config.MapperBean;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ClassName:Configuration
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 11:04
 * @Version: 1.0
 */
public class Configuration {

    //创建一个类加载器
    private ClassLoader classLoader = ClassLoader.getSystemClassLoader();



    public Connection build(String fileName) {

        //创建Connection连接引用
        Connection connection = null ;

        try {
            //获取配置文件的绝对路径,也可以理解为真实的工作目录
            InputStream inputStream = classLoader.getResourceAsStream(fileName);

            //创建解析器
            SAXReader saxReader = new SAXReader();
            //读取Document树
            Document document = saxReader.read(inputStream);
            //获取根节点
            Element rootElement = document.getRootElement();
            //获取Connection连接
            connection = getConnection(rootElement);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }


        return connection;
    }

    private Connection getConnection(Element rootElement) {

        //判断是否是指定的根节点
        if(!"database".equals(rootElement.getName())){
            //抛出异常
            throw new RuntimeException("rootElement is not a database");

        }

        //获取子类节点的迭代器
        Iterator<Element> elementIterator = rootElement.elementIterator();

        //创建Driver
        String driver = null;
        //创建Url
        String url = null;
        //创建用户
        String userName = null;
        //创建密码
        String password = null;

        //遍历Iterator
        while (elementIterator.hasNext()) {

            //获取子节点
            Element element = elementIterator.next();

            //获取字节点的name属性值
            String name = element.attributeValue("name");
            //获取直接点的Value属性值
            String value = element.attributeValue("value");

            switch (name) {

                case "driver":
                    driver = value;
                    break;
                case "url":
                    url = value;
                    break;
                case "username":
                    userName = value;
                    break;
                case "password":
                    password = value;
                    break;
            }
        }

        //创建properties
        Properties properties = new Properties();
        //添加数据
        properties.setProperty("driver",driver);
        properties.setProperty("url",url);
        properties.setProperty("username",userName);
        properties.setProperty("password",password);
        //创建数据源
        DataSource dataSource = null;
        //创建数据库连接引用
        Connection connection = null;
        try {
            //获取数据源
            dataSource = DruidDataSourceFactory.createDataSource(properties);

            //返回Connection连接
            connection = dataSource.getConnection();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return connection;

    }

    public MapperBean getMapperBeanByReaderXMl(String filePath) {

        //创建Mapper对象
        MapperBean mapperBean = new MapperBean();
        //创建存放function
        ConcurrentHashMap<String, Function> functionMap = new ConcurrentHashMap<>();

        try {
            //获取目标文件的绝对路径
            InputStream  inputStream = classLoader.getResourceAsStream(filePath);
            //创建解析器
            SAXReader saxReader = new SAXReader();
            //读取Document树
            Document document = saxReader.read(inputStream);
            //获取根节点
            Element rootElement = document.getRootElement();
            //获取接口信息
            String namespace = rootElement.attributeValue("namespace").trim();
            mapperBean.setInterfaceName(namespace);
            //获取子节点迭代器
            Iterator<Element> elementIterator = rootElement.elementIterator();
            //循环遍历迭代器
            while (elementIterator.hasNext()){

                //创建Function对象
                Function function = new Function();
                //获取子节点
                Element element = elementIterator.next();
                //获取方法名
                String methodName = element.attributeValue("id").trim();
                function.setMethodName(methodName);
                //获取sql类型
                String sqlType = element.getName().trim();
                function.setSqlType(sqlType);
                //返回类型
                String resultType = element.attributeValue("resultType").trim();
                //获取sql
                String sql = element.getText().trim();
                function.setSql(sql);


                try {
                    //创建返回类型对象
                    Class<?> clazz = Class.forName(resultType);
                    //获取构造器
                    Constructor<?> declaredConstructor = clazz.getDeclaredConstructor();
                    //设置允许访问
                    declaredConstructor.setAccessible(true);
                    //创建对象
                    Object instance = declaredConstructor.newInstance();
                    //给Function赋值
                    function.setResultType(instance);
                    //保存function
                    functionMap.put(methodName, function);

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            //将集合设置进MapperBean
            mapperBean.setFunctions(functionMap);

        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }


        return mapperBean;
    }

}
======================================================================================
package com.leon.mybatis_v1.session;

/**
 * ClassName:Executor
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 15:32
 * @Version: 1.0
 */
public interface Executor {

   public <T> T query(String statement , Object params);

}
======================================================================================
package com.leon.mybatis_v1.session;

import com.leon.mybatis_v1.config.Function;
import com.leon.mybatis_v1.config.MapperBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ClassName:HspMapperProxy
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 21:04
 * @Version: 1.0
 */
public class MapperProxy implements InvocationHandler {

    private SqlSession sqlSession;

    private String mapperFile;

    private Configuration configuration;


    public MapperProxy(SqlSession sqlSession, Configuration configuration, Class clazz) {
        this.sqlSession = sqlSession;
        this.configuration = configuration;
        //通过Class来获取简单类名
        mapperFile = "com/leon/mapper/"+ clazz.getSimpleName() + ".xml";
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //获取mapperBean
        MapperBean mapperBean =
                configuration.getMapperBeanByReaderXMl(mapperFile);

        //判断是否是xml文件对应的接口
        if(!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
            return null;
        }

        //取出mapperBean的functions
        ConcurrentHashMap<String, Function> functions = mapperBean.getFunctions();
        //判断当前mapperBean解析对应MapperXML后,有方法
        if(null != functions && 0 != functions.size()){

            //判断是否包含该方法
            if(functions.containsKey(method.getName())){

                //获取function对象
                Function function = functions.get(method.getName());

                //判断sql类型
                if("select".equals(function.getSqlType())){
                    //返回结果集
                    return sqlSession.selectOne(function.getSql(), String.valueOf(args[0]));
                }
            }

        }

        return null;
    }
}
======================================================================================

package com.leon.mybatis_v1.session;

import java.lang.reflect.Proxy;

/**
 * ClassName:SqlSession
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 16:03
 * @Version: 1.0
 */
public class SqlSession {

    private Configuration configuration = new Configuration();

    private BaseExecutor executor = new BaseExecutor();


    public <T> T selectOne(String statement, Object parameter) {

        return executor.query(statement, parameter);

    }

    public <T> T getMapper(Class<T> clazz) {

        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
                new Class[]{clazz},
                new MapperProxy(this,configuration,clazz));
    }

}
======================================================================================
package com.leon.mybatis_v1.session;

/**
 * ClassName:SqlSessionFactory
 * Package:com.leon.mybatis.session
 * Description:
 *
 * @Author: leon-->ZGJ
 * @Create: 2024/5/11 21:51
 * @Version: 1.0
 */
public class SqlSessionFactory {


    public static SqlSession openSqlSession() {
        return new SqlSession();
    }

}

Comment