一篇带你了解MyBatis的使用(适合小白)

导读:本篇文章讲解 一篇带你了解MyBatis的使用(适合小白),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

文章目录

一、MyBatis介绍

什么是MyBatis?

在这里插入图片描述

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。

IBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层(Dao 数据访问层)框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

下载地址: MyBatis下载地址:https://github.com/mybatis/mybatis-3/releases

使用版本:3.4.5

Hibernate 冬眠   全自动框架  SQL语句可以自动生成,不用人工书写SQL! 灵活度,性能差,笨重。SQL定制麻烦

MyBatis          半自动     SQL语句还是需要自己书写,后期有一些插件可以自动生成SQL! 灵活  定制SQL!

MyBatis Plus 插件:自动生成SQL、代码生成器!

ORM概念: Object Ralation Mapping 对象关系映射 框架

数据库类和程序中的实体类有对应关系(映射关系)的框架,叫做ORM框架(对象关系映射)

      数据库表  tb_user ------>实体类  User
​     数据库表中字段 username----->实体类的属性 username
​     数据库表中字段的类型 varchar(20) ----->实体类中属性的类型  String 

数据库表和程序实体类有对应关系的持久层框架 就叫ORM框架!

常见的ORM框架哪些:

MyBatis
Hibernate
Spring Data  JPA
......

二、MyBatis特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql基本上可以实现我们不使用数据访问框架可以实现的所有功能,或许更多。
  • 插件丰富,开源框架,有很多第三方插件(MyBatis Plus),辅助开发,甚至MyBatis自定义插件!

三、MyBatis基础应用

3.1 搭建MyBatis环境

3.1.1 环境准备

  • Jdk环境:jdk1.8
  • Ide环境:IDEA
  • 数据库环境:MySQL 5.5.X
  • Mybatis:3.4.5
  • maven:3.6.X

3.1.2 下载MyBatis

mybaits的代码由github.com管理,下载地址:https://github.com/mybatis/mybatis-3/releases
Mybatis-3.4.5.jar:mybatis的核心包

<?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.bruceliu.mybatis</groupId>
    <artifactId>mybatis-20190902</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--导入MyBatis开发环境的依赖-->
    <dependencies>

        <!-- myBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

        <!-- mysql驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

        <!-- Junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--Lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

3.1.4 准备数据库

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL COMMENT '用户名称',
  `birthday` date DEFAULT NULL COMMENT '生日',
  `sex` char(1) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '王五', '2018-09-06', '1', '四川成都');
INSERT INTO `user` VALUES ('10', '张三', '2014-07-10', '1', '北京市');
INSERT INTO `user` VALUES ('16', '张小明', '2018-09-06', '1', '河南郑州');
INSERT INTO `user` VALUES ('22', '陈小明', '2018-09-05', '1', '河南郑州');
INSERT INTO `user` VALUES ('24', '张三丰', '2018-09-13', '1', '河南郑州');
INSERT INTO `user` VALUES ('25', '陈小明', '2018-09-12', '1', '河南郑州');
INSERT INTO `user` VALUES ('26', '王五', '2018-09-05', '0', '河南郑州');

3.1.5.创建主配置文件:mybatis-config.xml

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

    <!--加载外部的属性文件-->
    <properties resource="jdbc.properties"></properties>

    <!--MyBatis数据库配置信息-->
    <environments default="mysql">

        <!--数据库的配置环境:mysql-->
        <environment id="mysql">
            <!--事务管理器 JDBC-->
            <transactionManager type="JDBC"/>
            <!--数据库连接池:表示MyBatis内置的连接池,后期可以切换为Druid-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--指向Mapper.xml文件位置 -->
    <!--映射Mapper文件-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"></mapper>
    </mappers>

</configuration>

jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://124.222.12.151:3306/SSM?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root

3.2 实现MyBatis的查询

3.2.1 获取SqlSession对象(核心对象)

MyBatis框架中涉及到的几个API
SqlSessionFactoryBuilder:该对象负责根据MyBatis配置文件mybatis-config.xml构建SqlSessionFactory实例 负责生产session
SqlSessionFactory:每一个MyBatis的应用程序都以一个SqlSessionFactory对象为核心。该对象负责创建SqlSession对象实例。
SqlSession:该对象包含了所有执行SQL操作的方法,用于执行已映射的SQL语句
在这里插入图片描述

新建一个maven工程:

目录结构如下:

3.2.2 创建实体类 User

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String username;
    private String birthday;
    private String sex;
    private String address;
}

3.2.3 创建接口层 UserMapper

public interface UserMapper {

    public List<User> getList();
}

3.2.4 创建接口实现层UserMapperImpl

public class UserMapperImpl {
    public List<User> getList() {
        InputStream inputStream = null;
        SqlSessionFactory sqlSessionFactory = null;
        SqlSession session = null;
        try {
            String resource = "mybatis-config.xml"; // 配置文件
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            session = sqlSessionFactory.openSession();  //获取session

            List<User> list = session.selectList("com.bruceliu.dao.UserMapper.getList");
            return list;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                session.close();
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

3.2.5 测试类

public class MybatisTest {
    /**
     * 测试查询集合
     */
    @Test
    public void testList(){
        UserMapper um=new UserMapperImpl();
        List<User> users = um.getList();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

3.2.6.Mybatis使用步骤总结:

  1. 创建SqlSessionFactory
  2. 通过SqlSessionFactory创建SqlSession对象
  3. 通过SqlSession操作数据库
  4. 调用session.commit()提交事务
  5. 调用session.close()关闭会话

3.2.7.封装获取MyBatis中Session的工具类

/**
 * 封装一个MyBatis工具类
 */
public class MyBatisUtils {

    static InputStream inputStream=null;
    static SqlSessionFactory sqlSessionFactory=null;

    //定义Threadlocal存储类型为SqlSession
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();

    static {
        try {
            //1.mybatis的核心配置文件
            String resources="mybatis-config.xml";
            //2.使用外部的配置文件构建sqlsession工厂
            inputStream = Resources.getResourceAsStream(resources);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 01-获取sqlSession
     * @return
     */
    public static SqlSession getSqlSession(){
        //1.从线程里面获取共享的SqlSession对象
        SqlSession sqlSession = threadLocal.get();
        //2.对象为空,创建一个新的sqlSession写入线程共享ThreadLocal中
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession(true); //true:表示自动提交事务 默认值false
            //System.out.println(sqlSession + ",初始化SqlSession资源");
            threadLocal.set(sqlSession);
        }
        //3.返回有效对象
        return sqlSession;
    }

    /**
     * 02-获取sqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession){
        try {
            if(sqlSession!=null){
                sqlSession.close();
            }
            if(inputStream!=null){
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }


}

调用示范例:

/**
 * 封装一个MyBatis工具类
 */
public class MyBatisUtils {

    static InputStream inputStream=null;
    static SqlSessionFactory sqlSessionFactory=null;

    //定义Threadlocal存储类型为SqlSession
    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>();

    static {
        try {
            //1.mybatis的核心配置文件
            String resources="mybatis-config.xml";
            //2.使用外部的配置文件构建sqlsession工厂
            inputStream = Resources.getResourceAsStream(resources);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 01-获取sqlSession
     * @return
     */
    public static SqlSession getSqlSession(){
        //1.从线程里面获取共享的SqlSession对象
        SqlSession sqlSession = threadLocal.get();
        //2.对象为空,创建一个新的sqlSession写入线程共享ThreadLocal中
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession(true); //true:表示自动提交事务 默认值false
            //System.out.println(sqlSession + ",初始化SqlSession资源");
            threadLocal.set(sqlSession);
        }
        //3.返回有效对象
        return sqlSession;
    }

    /**
     * 02-获取sqlSession
     * @param sqlSession
     */
    public static void closeSqlSession(SqlSession sqlSession){
        try {
            if(sqlSession!=null){
                sqlSession.close();
            }
            if(inputStream!=null){
                inputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }
    }


}

四、编写DAO的实现类发现的问题

  • 冗余的代码多;
  • 调用Statement不方便
  • 通用性不好,实现方法非常的类似

4.1 使用动态代理实现接口的实现类(不需要Mapper实现类)

如何得到动态代理:

public class MybatisTest {
    //接口:没有实现类
    //解决办法:Java有个JDK动态代理设计模式,可以根据接口生成实现类对象!
    UserMapper2 um = null;
    SqlSession sqlSession = null;

    /**
     * 初始化:在执行@Test测试方法之前,自动执行@Before内容
     */
    @Before
    public void init() {
        sqlSession = MyBatisUtils.getSqlSession();
        //um是底层使用JDK动态代理给UserMapper接口生成的实现类,具体原理暂时保留,后面讲!
        um = sqlSession.getMapper(UserMapper2.class);
    }

    /**
     * 销毁:在执行@Test测试方法之后,自动执行@After
     */
    @After
    public void destory() {
        MyBatisUtils.closeSqlSession(sqlSession);
    }

    @Test
    public void testList() {

        List<User> users = um.getList();
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 新增用户
     */
    @Test
    public void insertUser() {
        User u = new User(null, "张三", LocalDate.now().toString(), 1, "北京");
        System.out.println(u);
        int count = um.insertUser(u);
        System.out.println(count > 0 ? "新增成功" : "新增失败");
    }
    /**
     * 根据ID查询
     */
    @Test
    public void getById(){
        User user = um.getById(10);
        System.out.println(user);
    }
    /**
     * 根据ID更新
     */
    @Test
    public void testUpdateById(){
        User user = um.getById(31);
        System.out.println("更新之前:"+user);
        user.setUsername("李四");
        user.setBirthday(LocalDate.now().toString());
        user.setAddress("中国");
        user.setSex(1);
        System.out.println("更新之后:"+user);
        int count = um.updateById(user);
        System.out.println(count>0?"更新成功":"更新失败");
    }

    /**
     *根据查询
     */
    @Test
    public void testListBySex(){
        List<User> users = um.listBySex(0);
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 根据性别和地址查询
     */
    @Test
    public void testlistBySexAndAddress(){
        User u=new User();
        u.setSex(1);
        u.setAddress("河南郑州");
        //传入的查询参数为一个对象,直接取里面的属性,即可:#{address}
        List<User> users = um.listBySexAndAddress(u);
        for (User user : users) {
            System.out.println(user);
        }
    }
    //传入的参数有两个只,取值注意为:#{参数1},#{参数2},参数任意取,只要能区分,如p1,p2都可以
    @Test
    public void listByBirthday(){
        List<User> users = um.listByBirthday("2022-01-10", "2022-01-20");
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    public void testlistByBirthday2(){
        Map<String, Object> map=new HashMap<String, Object>();
        map.put("startDate","2022-01-10");
        map.put("endDate","2022-01-20");
        //传入的参数为一个map,直接#{key}
        List<User> users = um.listByBirthday2(map);
        for (User user : users) {
            System.out.println(user);
        }
    }


}

改动项目,项目目录结构为:

UserMapper2.java

public interface UserMapper2 {

    public List<User> getList();
    /**
     * 02-新增
     * @return
     */
    int insertUser(User u);
    /**
     * 03-根据ID查询
     * @param id
     * @return
     */
    User getById(Integer id);

    /**
     * 传入各种参数查询结果的情况
     */

    /**
     * 04-根据ID删除
     * @param id
     * @return
     */

    int deleteById(Integer id);
    /**
     * 05-根据ID更新
     * @param user
     * @return
     */
    int updateById(User user);
    /**
     * 06-根据性别查询
     * @param sex
     * @return
     */
    List<User> listBySex(Integer sex);
    /**
     * 07-根据性别和地址查询
     * @return
     */
    List<User> listBySexAndAddress(User u);
    /**
     * 08-查询数据库中 生日在 2022-01-10 到 2022-01-25 范围内的所有
     * @return
     */
    List<User> listByBirthday(String startDate,String endDate);

    List<User> listByBirthday1(@Param("xxx") String startDate,
                               @Param("ooo") String endDate);

    /**
     * 09-传入的参数是map
     * @param map
     * @return
     */
    List<User> listByBirthday2(Map<String,Object> map);
}

UserMapper2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haidi8.mapper.UserMapper2">
    <!--1.查询 list
         查询有返回值类型,查询一定要有返回值类型 resultType=""
         查询参数类型: 可以省略,MyBatis自动推断类型 parameterType=""
    -->
    <select id="getList" resultType="com.haidi8.pojo.User">
        select * from user
    </select>

    <!--2.新增  占位符 #{对象的属性名字}  增删改需要提交事务!!! -->
    <insert id="insertUser">
        insert into user values(null,#{username},#{birthday},#{sex},#{address})
    </insert>

    <select id="getById" resultType="com.haidi8.pojo.User">
        select * from user where id=#{id}
    </select>

    <!--根据ID删除  增 删改 默认返回int  不需要写返回值-->
    <delete id="deleteById">
        delete  from user where id=#{id}
    </delete>

    <update id="updateById">
        update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    </update>

    <select id="listBySex" resultType="com.haidi8.pojo.User">
        select * from user where sex=#{sex111}
    </select>

    <select id="listBySexAndAddress" resultType="com.haidi8.pojo.User">
        select * from user where sex=#{sex} and address=#{address}
    </select>

    <select id="listByBirthday" resultType="com.haidi8.pojo.User">
        <!--标记一下CDATA内容是文本不要瞎识别-->
        <![CDATA[
             select * from user where birthday>=#{param1} and birthday<=#{param2}
        ]]>
    </select>

    <select id="listByBirthday1" resultType="com.haidi8.pojo.User">
        select * from user where birthday between #{xxx} and #{ooo}
    </select>

    <select id="listByBirthday2" resultType="com.haidi8.pojo.User">
        select * from user where birthday
                                        between #{startDate} and #{endDate}
    </select>


</mapper>

测试类:

public class MybatisTest {
    //接口:没有实现类
    //解决办法:Java有个JDK动态代理设计模式,可以根据接口生成实现类对象!
    UserMapper2 um = null;
    SqlSession sqlSession = null;

    /**
     * 初始化:在执行@Test测试方法之前,自动执行@Before内容
     */
    @Before
    public void init() {
        sqlSession = MyBatisUtils.getSqlSession();
        //um是底层使用JDK动态代理给UserMapper接口生成的实现类,具体原理暂时保留,后面讲!
        um = sqlSession.getMapper(UserMapper2.class);
    }

    /**
     * 销毁:在执行@Test测试方法之后,自动执行@After
     */
    @After
    public void destory() {
        MyBatisUtils.closeSqlSession(sqlSession);
    }

    @Test
    public void testList() {

        List<User> users = um.getList();
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 新增用户
     */
    @Test
    public void insertUser() {
        User u = new User(null, "张三", LocalDate.now().toString(), 1, "北京");
        System.out.println(u);
        int count = um.insertUser(u);
        System.out.println(count > 0 ? "新增成功" : "新增失败");
    }
    /**
     * 根据ID查询
     */
    @Test
    public void getById(){
        User user = um.getById(10);
        System.out.println(user);
    }
    /**
     * 根据ID更新
     */
    @Test
    public void testUpdateById(){
        User user = um.getById(31);
        System.out.println("更新之前:"+user);
        user.setUsername("李四");
        user.setBirthday(LocalDate.now().toString());
        user.setAddress("中国");
        user.setSex(1);
        System.out.println("更新之后:"+user);
        int count = um.updateById(user);
        System.out.println(count>0?"更新成功":"更新失败");
    }

    /**
     *根据查询
     */
    @Test
    public void testListBySex(){
        List<User> users = um.listBySex(0);
        for (User user : users) {
            System.out.println(user);
        }
    }

    /**
     * 根据性别和地址查询
     */
    @Test
    public void testlistBySexAndAddress(){
        User u=new User();
        u.setSex(1);
        u.setAddress("河南郑州");
        //传入的查询参数为一个对象,直接取里面的属性,即可:#{address}
        List<User> users = um.listBySexAndAddress(u);
        for (User user : users) {
            System.out.println(user);
        }
    }
    //传入的参数有两个只,取值注意为:#{参数1},#{参数2},参数任意取,只要能区分,如p1,p2都可以
    @Test
    public void listByBirthday(){
        List<User> users = um.listByBirthday("2022-01-10", "2022-01-20");
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    public void testlistByBirthday2(){
        Map<String, Object> map=new HashMap<String, Object>();
        map.put("startDate","2022-01-10");
        map.put("endDate","2022-01-20");
        //传入的参数为一个map,直接#{key}
        List<User> users = um.listByBirthday2(map);
        for (User user : users) {
            System.out.println(user);
        }
    }


}

注意:
1、保证命名空间和接口的全路径一致;

2、Statement的id和接口中的方法名一致

3、加入到mybatis-config.xml中

4.2 使用动态代理总结

​ 使用mapper接口不用写接口实现类即可完成数据库操作,使用非常简单,也是官方所推荐的使用方法。
使用mapper接口的必须具备以几个条件:

1) Mapper的namespace必须和mapper接口的全路径一致。
  2) Mapper接口的方法名必须和sql定义的id一致。
  3) Mapper接口中方法的输入参数类型必须和sql定义的parameterType一致。
  4) Mapper接口中方法的输出参数类型必须和sql定义的resultType一致。

注意:

在这里插入图片描述

在这里插入图片描述

MyBatis获取自增的ID

    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into user values(null,#{username},#{birthday},#{sex},#{address})
    </insert>
如果id主键是自增的,那么做如下配置:
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
理解:useGeneratedKeys="true"  开启自增长映射
keyProperty: 指定id所对应对象中的属性名 新增成功之后的id值赋值类对象的id属性
    <insert id="insertUser">
        <selectKey keyProperty="id" resultType="Integer" order="AFTER">
            <!--查询最近一次新增的id:注意这其中的查询和新增是在一个线程里面进行的,
            而且考虑到并发总线程阻塞等,所以能获取新增的Id
            -->
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into user values(null,#{username},#{birthday},#{sex},#{address})
    </insert>
如果id主键不自增,

五、Mybatis-Config配置

  • properties 属性
  • settings 设置
  • typeAliases 类型别名
  • typeHandlers 类型处理器
  • objectFactory 对象工厂
  • plugins 插件
  • environments 环境
    environment 环境变量
    transactionManager 事务管理器
    dataSource 数据源
  • mappers 映射器
    Mybatis的配置文件中配置项是有顺序的,即按照上面的顺序;

5.1 Properties

在这里插入图片描述

5.2 typeAliases(别名)

类型别名是为 Java 类型命名一个短的名字。 它只和 XML 配置有关, 只用来减少类完全限定名的多余部分。
自定义别名:

    <!--实体类取别名-->
    <typeAliases>
        <!--直接给所有的实体类取别名。默认的实体类的别名就是类名(不区分小大写)
          User实体类:User、user、USER
      -->
        <package name="com.haidi8.pojo"/>
    </typeAliases>

注意:
使用定义的别名是不区分大小写的,但一般按java规则去使用即可,即user或者User

5.3 mappers

mapper映射文件的引入有3种方式:
路径相对于资源目录跟路径:

	<mappers>
		<mapper resource="com/haidi8/dao/UserMapper.xml" />
	</mappers>

使用完整的文件路径:

	<mappers>
		<mapper class="com.haidi8.dao.UserMapper"/>
	</mappers>

注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中
可直接配个扫描包:

    <!--指向Mapper.xml文件位置 -->
    <!--映射Mapper文件-->
    <mappers>
        <!--        <mapper resource="mappers/UserMapper.xml"></mapper>-->
        <!--        <mapper resource="mappers/AddressMapper.xml"></mapper>-->
        <!--统一引用mapper:约定:xxxMapper.xml需要放在com/haidi8/mapper/-->
        <package name="com.haidi8.mapper"/>
    </mappers>

注意采用接口和mapper文件放在一个目录里面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHNVcihL-1662043329232)(C:\Users\administrater\AppData\Roaming\Typora\typora-user-images\image-20220822231801508.png)]

pom里面还得做配置

<build>
    <resources>
        <!-- mapper.xml文件在java目录下 -->
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <!--指定src下的内容编译-->
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*</include>
            </includes>
        </resource>
    </resources>
</build>

六、Mapper XML 文件

Mapper映射文件是在实际开发过程中使用最多的,也是我们学习的重点。
Mapper文件中包含的元素有:
cache – 配置给定命名空间的缓存。
cache-ref – 从其他命名空间引用缓存配置。
resultMap – 映射复杂的结果对象。
sql – 可以重用的 SQL 块,也可以被其他语句引用。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句

6.1 CRUD

6.1.1 select

    <select id="getById" parameterType="int" resultType="User">
          select * from user where id=#{id}
    </select>

select标签叫Statement
id,必要属性,在当前的命名空间下不能重复。
指定输出类型,resultType
parameterType (不是必须)如果不指定,自动识别。

6.1.2 insert

<!-- 增删改返回的都是int值 不用写返回值 -->
<insert id="addUser">
   INSERT INTO USER VALUES (null,#{username},#{birthday},#{sex},#{address})
</insert>

insert标签叫Statement
id,必要属性,在当前的命名空间下不能重复。
parameterType (不是必须)如果不指定,自动识别。

6.1.3 update

<update id="updateUser" >
       update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#           {address} where id=#{id}
</update>

update标签叫Statement
id,必要属性,在当前的命名空间下不能重复。
parameterType (不是必须)如果不指定,自动识别。

6.1.4 delete

<delete id="deleteUser" parameterType="int">
     delete from user where id=#{id}
</delete>

delete标签叫Statement
id,必要属性,在当前的命名空间下不能重复。
parameterType (不是必须)如果不指定,自动识别。

七、#和$区别(面试题)

在映射文件配置标签执行查询操作。
注意:

  • {}:相当于占位符
    {id}:其中的id可以表示输入参数的名称,如果是简单类型名称可以任意
  • ${}:表示拼接sql语句
  • ${value}:表示输入参数的名称,如果参数是简单类型,参数名称必须是value
  <!-- 
	根据id查询用户,User findById(int id)
		select:配置查询语句
			id:可以通过id找到执行的statement,statement唯一标识
			parameterType:输入参数类型
			resultType:输出结果类型	
			#{}:相当于占位符
			#{id}:其中的id可以表示输入参数的名称,如果是简单类型名称可以任意
	 -->
	<select id="getById" parameterType="int" resultType="User" >
		 select * from user where id=#{id}
	</select>

   <!-- 
	根据用户名称来模糊查询用户信息列表;
		${}:表示拼接sql语句
		${value}:表示输入参数的名称,如果参数是简单类型,参数名称必须是value
	 -->
	 <select id="findByUsername" parameterType="java.lang.String"
	 	resultType="User">
	 	select * from user where username like '%${value}%'	
	 </select>


查询需求:
<!--    ==>  Preparing: select * from user where address like ?-->
<!--    ==> Parameters: %郑州(String)-->
    <select id="findUsersByAddress" resultType="com.haidi8.pojo.User" parameterType="java.lang.String">
          select * from user where address like #{address}
    </select>
<!--    ==>  Preparing: select * from user where address like '%郑州'-->
<!--    ==> Parameters:-->
    <select id="findUsersByAddress1" resultType="com.haidi8.pojo.User" parameterType="java.lang.String">
        select * from  user where  address like '%${name}'
    </select>

查询测试:
  /**
     * 模糊查询
     */
    @Test
    public void testfindUsersByAddress(){
        List<User> users = um.findUsersByAddress("%郑州");
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    public void testfindUsersByAddress1(){
        List<User> users = um.findUsersByAddress1("郑州");
        for (User user : users) {
            System.out.println(user);
        }
    }

八、parameterType的传入参数

传入类型有三种:
    1、简单类型,string、long、integer等
    2、Pojo类型,User等
    3、HashMap类型。

8.1 .传入参数是HashMap类型

查询需求:

	 <select id="getUsers" parameterType="map" resultType="User">
	     select * from user where birthday between #{startdate} and #{enddate}
	 </select>

查询测试:

@Test
public void test7(){
	HashMap<String, Object> map=new HashMap<String, Object>();
	map.put("startdate", "2018-09-07");
	map.put("enddate", "2018-09-25");
	List<User> users = mapper.getUsers(map);
	for (User user : users) {
		System.out.println(user);
	}
}

注意:map的key要和sql中的占位符保持名字一致

8.2 分页查询

查询需求:

	<!-- 分页:map传参 -->
    <select id="getPage" resultType="com.haidi8.pojo.User" parameterType="map">
        select  * from user limit #{pageStart},#{pageSize}
    </select>

接口:

/**
 * 根据分页参数查询
 * @param paramList 分页参数
 * @return 分页后的用户列表
 */
List<User> selectAuthorByPage(Map<String, Object> paramList);

测试:

    /**
     * 分页查询
     */
    @Test
    public void testgetPage(){
        int pageIndex=1;//当前页码
        int pageSize=5; //页面大小
        int totalCount = um.getTotalCount();
        int totalPage=(totalCount%pageSize==0)?(totalCount/pageSize):(totalCount/pageSize+1);

        Map<String,Object> map=new HashMap<String,Object>();
        map.put("pageStart",(pageIndex-1)*pageSize);
        map.put("pageSize",pageSize);
        System.out.println("当前页码:"+pageIndex);
        System.out.println("页面大小:"+pageSize);
        System.out.println("总数:"+totalCount);
        System.out.println("总页数:"+totalPage);
        System.out.println("---------------------------每页数据--------------------------");
        List<User> users = um.getPage(map);
        for (User user : users) {
            System.out.println(user);
        }
    }

8.2.1使用注解

注意:mapper文件中的参数占位符的名字一定要和接口中参数的注解保持一致
mapper:

<!-- 分页:map传参 -->
<select id="selectUserByPage2" resultType="User">
	SELECT * FROM USER LIMIT #{offset}, #{pagesize}
</select

接口:

/**
 * 根据分页参数查询
 * @param offset 偏移量
 * @param pagesize  每页条数
 * @return 分页后的用户列表
 */
List<User> selectUserByPage2(@Param(value = "offset") int offset, @Param(value = "pagesize") int pagesize);

测试:

@Test
public void testSelectAuthorByPage2() {

	List<User> authorList = mapper.selectUserByPage2(0, 2);

	for (int i = 0; i < authorList.size(); i++) {
		System.out.println(authorList.get(i));
		System.out.println("----------------------");
	}
}

8.2.2 使用参数顺序

注意:mapper文件中参数占位符的位置编号一定要和接口中参数的顺序保持一致
mapper:

<!-- 分页:传参顺序 -->
<select id="selectUserByPage3" parameterType="map" resultType="User">
    SELECT * FROM USER LIMIT #{param1},#{param2}
</select>

接口:

/**
     * 根据分页参数查询
     * @param offset 偏移量
     * @param pagesize 每页条数
     * @return 分页后的用户列表
     */
    List<User> selectUserByPage3(int offset, int pagesize);

测试:

@Test
	public void testSelectAuthorByPage3() {
		List<User> users = mapper.selectUserByPage3(1, 1);
		for (int i = 0; i < users.size(); i++) {
			System.out.println(users.get(i));
			System.out.println("----------------------");
		}
	}

九、返回Map类型查询结果

Mybatis中查询结果集为Map的功能,只需要重写ResultHandler接口,,然后用SqlSession 的select方法,将xml里面的映射文件的返回值配置成 HashMap 就可以了。具体过程如下

9.1 返回一条数据的情况

<select id="getResult" resultMap="map">
	select count(*) r1, max(birthday) r2 from user
</select>

接口:

public HashMap<String, Object> getResult();

测试:

@Test
public void test8(){
	HashMap<String, Object> result = mapper.getResult();
	System.out.println(result);
}

返回多个值:

<select id="getResult" resultType="map">
		select count(*) r1, max(birthday) r2,min(id) r3 from user
</select>

9.2 返回多条数据的情况

9.2.1 用List+Map来接收返回值

<select id="findMaxAndMinBirthdayGroup" resultType="map">
    select min(birthday) 'min',max(birthday) 'max',sex from user GROUP BY sex
</select>

接口:

/**
 * select min(birthday),max(birthday),sex from tb_user GROUP BY sex
 */
List<Map<String, Object>> findMaxAndMinBirthdayGroup();

测试:

@Test
public void testfindMaxAndMinBirthdayGroup(){
    List<Map<String, Object>> mapList = um.findMaxAndMinBirthdayGroup();
    for (Map<String, Object> map : mapList) {
        Object maxBirthday = map.get("max");
        Object minBirthday = map.get("min");
        Object sex = map.get("sex");
        System.out.println("最大生日:"+maxBirthday);
        System.out.println("最小生日:"+minBirthday);
        System.out.println("性别:"+sex);
        System.out.println("-------------------------");
    }
}

9.2.1 用List+Object 来接收返回值

<select id="findMaxAndMinBirthdayGroup1" resultType="QueryBean">
    select min(birthday) 'min',max(birthday) 'max',sex from user GROUP BY sex
</select>

封装对象:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class QueryBean {

    private Date min;
    private Date max;
    private Integer sex;

}

接口:

/**
 * select min(birthday),max(birthday),sex from tb_user GROUP BY sex
 */
List<QueryBean> findMaxAndMinBirthdayGroup1();

测试:

@Test
public void testfindMaxAndMinBirthdayGroup1(){
    List<QueryBean> beanList = um.findMaxAndMinBirthdayGroup1();
    for (QueryBean queryBean : beanList) {
        System.out.println(queryBean);
    }

十、解决数据库字段和实体类属性不同

在平时的开发中,我们表中的字段名和表对应实体类的属性名称不一定都是完全相同的,下面来演示一下这种情况下的如何解决字段名与实体类属性名不相同的冲突。

上面的测试代码演示当实体类中的属性名和表中的字段名不一致时,使用MyBatis进行查询操作时无法查询出相应的结果的问题以及针对问题采用的两种办法:

解决办法一:

通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,这样就可以表的字段名和实体类的属性名一一对应上了,这种方式是通过在sql语句中定义别名来解决字段名和属性名的映射关系的。

解决办法二:

通过来映射字段名和实体类属性名的一一对应关系。这种方式是使用MyBatis提供的解决方式来解决字段名和属性名的映射关系的。

十一、MyBatis整体架构

Mybatis是一个类似于Hibernate的ORM持久化框架,支持普通SQL查询,存储过程以及高级映射。Mybatis通过使用简单的XML或注解用于配置和原始映射,将接口和POJO对象映射成数据库中的记录。
由于Mybatis是直接基于JDBC做了简单的映射包装,所有从性能角度来看:
JDBC > Mybatis > Hibernate

在这里插入图片描述

1、配置2类配置文件,其中一类是:Mybatis-Config.xml (名字不是写死,随便定义),另一类:Mapper.xml(多个),定义了sql片段;
2、通过配置文件得到SqlSessionFactory
3、通过SqlSessionFactory得到SqlSession(操作数据库)
4、通过底层的Executor(执行器)执行sql,Mybatis提供了2种实现,一种是基本实现,另一种带有缓存功能的实现;

十二、动态SQL

12.1 动态SQL简介

MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句有多么痛苦。拼接的时候要确保不能忘了必要的空格,还要注意省掉列名列表最后的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。

在这里插入图片描述

通常使用动态 SQL 不可能是独立的一部分,MyBatis 当然使用一种强大的动态 SQL 语言来改进这种情形,这种语言可以被用在任意的 SQL 映射语句中。

动态SQL:SQL语句可以根据用户的条件动态的生成和变化

动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多的元素需要来了解。MyBatis 3 大大提升了它们,现在用不到原先一半的元素就可以了。MyBatis 采用功能强大的基于 OGNL(Struts2语法) 的表达式来消除其他元素。

​ mybatis 的动态sql语句是基于OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类:

  • if 语句 (简单的条件判断)
  • choose (when,otherwise) ,相当于java 语言中的 switch ,与 jstl 中的choose 很类似.
  • trim (对包含的内容加上 prefix,或者 suffix 等,前缀,后缀)
  • where (主要是用来简化sql语句中where条件判断的,能智能的处理 and or ,不必担心多余导致语法错误)
  • set (主要用于更新时)
  • foreach (在实现 mybatis in 语句查询时特别有用)

12.2 分支判断

建表SQL语句:

CREATE TABLE `tb_film` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `price` double(10,2) DEFAULT NULL,
  `type` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `actor` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

12.2.1 if元素

根据 type和 sex 来查询数据。如果type为空,那么将只根据sex来查询;反之只根据type来查询
首先不使用 动态SQL 来书写

    <select id="findFilm" parameterType="com.haidi8.pojo.Film" resultType="com.haidi8.pojo.Film">
        select * from tb_film where   type=#{type} and actor=#{actor}>
    </select>

上面的查询语句,我们可以发现,如果 #{type} 为空,那么查询结果也是空,如何解决这个问题呢?使用 if 来判断

<select id="findFilm" parameterType="com.haidi8.pojo.Film" resultType="com.haidi8.pojo.Film">
    <if test="type!=null and type!=''">
        type=#{type}
    </if>
    <if test="actor!=null and actor!=''">
        and actor=#{actor}
    </if>
</select>

这样写我们可以看到,如果 sex 等于 null,那么查询语句为 select * from user where type=#{username},但是如果usename 为空呢?那么查询语句为 select * from user where and sex=#{sex},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句

12.2.2 动态SQL:if+where 语句

    <select id="findFilm1" resultType="com.haidi8.pojo.Film" parameterType="com.haidi8.pojo.Film">
        select * from tb_film
        <where>
            <if test="type!=null and type!=''">
                type=#{type}
            </if>
            <if test="actor!=null and actor!=''">
                actor=#{actor}
            </if>
        </where>
    </select>

如果type为空,则SQL语句为: Preparing: select * from tb_film WHERE actor=?

如果type和actor都为空,则SQL语句为:select * from tb_film

12.2.3 动态SQL:if+set 语句

<!-- 根据 id 更新 user 表的数据 -->
	<update id="updateUserById" parameterType="User">
		update user u
		<set>
			<if test="username != null and username != ''">
				u.username = #{username},
			</if>
			<if test="sex != null and sex != ''">
				u.sex = #{sex},
			</if>
		</set>
		where id=#{id}
	</update>

这样写,如果第一个条件 username 为空,那么 sql 语句为:update user u set u.sex=? where id=?
如果第一个条件不为空,那么 sql 语句为:update user u set u.username = ? ,u.sex = ? where id=?
如果出现多余逗号 会自动去掉!!

12.2.4 动态SQL:choose(when,otherwise) 语句 了解

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句

	<select id="selectUserByChoose" resultType="User" parameterType="User">
		select * from user
		<where>
			<choose>
				<when test="id !='' and id != null">
					id=#{id}
				</when>
				<when test="username !='' and username != null">
					and username=#{username}
				</when>
				<otherwise>
					and sex=#{sex}
				</otherwise>
			</choose>
		</where>
	</select>

也就是说,这里我们有三个条件,id,username,sex,只能选择一个作为查询条件
如果 id 不为空,那么查询语句为:select * from user where id=?
如果 id 为空,那么看username 是否为空,如果不为空,那么语句为 select * from user where username=?;
如果 username 为空,那么查询语句为 select * from user where sex=?

12.2.5 动态SQL:trim 语句 了解

trim标记是一个格式化的标记,可以完成set或者是where标记的功能
①、用 trim 改写上面第二点的 if+where 语句

<select id="selectUserByUsernameAndSex" resultType="user" parameterType="User">
	select * from user
	<trim prefix="where" prefixOverrides="and | or">
		<if test="username != null">
			and username=#{username}
		</if>
		<if test="sex != null">
			and sex=#{sex}
		</if>
	</trim>
</select>

prefix:前缀      
prefixoverride:去掉第一个and或者是or
②、用 trim 改写上面第三点的 if+set 语句

<!-- 根据 id 更新 user 表的数据 -->
<update id="updateUserById" parameterType="User">
	update user u
	<trim prefix="set" suffixOverrides=",">
		<if test="username != null and username != ''">
			u.username = #{username},
		</if>
		<if test="sex != null and sex != ''">
			u.sex = #{sex},
		</if>
	</trim>
	where id=#{id}
</update>

suffix:后缀  
suffixoverride:去掉最后一个逗号(也可以是其他的标记,就像是上面前缀中的and一样)

12.2.6 动态SQL: SQL 片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。
比如:假如我们需要经常根据用户名和性别来进行联合查询,那么我们就把这个代码抽取出来,如下:

<!-- 定义 sql 片段 -->
<sql id="selectUserByUserNameAndSexSQL">
	<if test="username != null and username != ''">
		AND username = #{username}
	</if>
	<if test="sex != null and sex != ''">
		AND sex = #{sex}
	</if>
</sql>

引用 sql 片段

<select id="selectUserByUsernameAndSex" resultType="user"
		parameterType="User">
		select * from user
		<trim prefix="where" prefixOverrides="and | or">
			<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
			<include refid="selectUserByUserNameAndSexSQL"></include>
			<!-- 在这里还可以引用其他的 sql 片段 -->
		</trim>
	</select>

注意:
①、最好基于 单表来定义 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where

12.2.7 动态SQL: foreach 语句

需求:我们需要查询 user 表中 id 分别为1,2,3的用户
sql语句:select * from user where id=1 or id=2 or id=3
     select * from user where id in (1,2,3)

  • 单参数List的类型
<select id="dynamicForeachTest" resultType="User">
		select * from user where id in
		<foreach collection="list" index="index" item="item" open="("
			separator="," close=")">
			#{item}
		</foreach>
	</select>

接口:

 public List<User> dynamicForeachTest(List<Integer> ids);

测试:

@Test
public void testSelectUserByListId1(){
    List<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    List<User> listUser = mapper.dynamicForeachTest(ids);
    for(User u : listUser){
        System.out.println(u);
    }
}
  • 单参数array数组的类型
<select id="dynamicForeach2Test" resultType="User">
	select * from user where id in
	<foreach collection="array" index="index" item="item" open="("
		separator="," close=")">
		#{item}
	</foreach>
</select>

接口:

public List<User> dynamicForeach2Test(Integer[] ids);

测试:

@Test
public void dynamicForeach2Test(){
    Integer[] ids={1,2,3};
	List<User> listUser = mapper.dynamicForeach2Test(ids);
    for(User u : listUser){
        System.out.println(u);
    }
}

12.2.7 ResultMap标签

在这里插入图片描述

第十三、关联映射(多表查询)

数据库中多表之间存在着三种关系,如图所示。

在这里插入图片描述

多对多: 程序员<——>项目 用户———>角色
一对多: 班级——>学生 学校——>班级 帅哥—–>多个女朋友 土豪—多辆豪车
一对一: 学生—–>学位证 人——>DNA 公民—-身份证 房子–产权证

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。

现实生活中实体和实体之间的关系: 一对多 多对多 一对一

关系:是双向的!!

13.1 关联映射作用

在现实的项目中进行数据库建模时,我们要遵循数据库设计范式的要求,会对现实中的业务模型进行拆分,封装在不同的数据表中,表与表之间存在着一对多或是多对多的对应关系。进而,我们对数据库的增删改查操作的主体,也就从单表变成了多表。那么Mybatis中是如何实现这种多表关系的映射呢?

查询结果集ResultMap

resultMap元素是 MyBatis中最重要最强大的元素。它就是让你远离90%的需要从结果 集中取出数据的JDBC代码的那个东西,而且在一些情形下允许你做一些 JDBC 不支持的事 情。

有朋友会问,之前的示例中我们没有用到结果集,不是也可以正确地将数据表中的数据映射到Java对象的属性中吗?是的。这正是resultMap元素设计的初衷,就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。

resultMap元素中,允许有以下直接子元素:

id - 作用与result相同,同时可以标识出用这个字段值可以区分其他对象实例。可以理解为数据表中的主键,可以定位数据表中唯一一笔记录
result - 将数据表中的字段注入到Java对象属性中

association - 关联,简单的讲,就是“有一个”关系,如“用户”有一个“帐号” has a
collection - 集合,顾名思议,就是“有很多”关系,如“客户”有很多“订单” has many

13.2 一对一关联(了解)

数据库表:

CREATE TABLE `tb_card` (
  `id` int NOT NULL AUTO_INCREMENT,
  `no` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `expired` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

INSERT INTO `tb_card`(`id`, `no`, `address`, `expired`) VALUES (1, '345435435435435x', '山东省曹县XX号', '2022-05-21');
INSERT INTO `tb_card`(`id`, `no`, `address`, `expired`) VALUES (2, '5645654645654653', '山东省曹县102号', '2022-04-17');



CREATE TABLE `tb_student` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `age` int DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8_bin DEFAULT NULL,
  `birthday` date DEFAULT NULL,
  `card_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `qu_card_id` (`card_id`),
  CONSTRAINT `fk_card_id` FOREIGN KEY (`card_id`) REFERENCES `tb_card` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
INSERT INTO `tb_student`(`id`, `name`, `age`, `sex`, `birthday`, `card_id`) VALUES (1, '武大郎', 40, '男', '2022-01-19', 2);
INSERT INTO `tb_student`(`id`, `name`, `age`, `sex`, `birthday`, `card_id`) VALUES (2, '西门庆', 28, '男', '2022-01-19', 1);
需求:
根据身份证号码查询身份证信息和对应的学生信息

表关系:

在这里插入图片描述

接口:

    /**
     * 根据身份证号码查询身份证信息和对应的学生信息
     * @param no
     * @return
     */
    TbCard getCardByNo(String no);

定义SQL映射文件TbCardMapper.xml

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

<mapper namespace="com.haidi8.mapper.TbCardMapper">
    <resultMap id="cardMap" type="TbCard">
        <id property="id" column="CID"></id>
        <result property="address" column="address"></result>
        <result property="no" column="NO"></result>
        <result property="expired" column="EXPIRED"></result>
        <association property="student" javaType="TbStudent">
            <id  property="id" column="ID" ></id>
            <result property="name" column="NAME"></result>
            <result property="age" column="AGE"></result>
            <result property="birthday" column="BIRTHDAY"></result>
            <result property="sex" column="SEX"></result>
            <result property="cardId" column="card_Id"></result>
        </association>
    </resultMap>

    <select id="getCardByNo" resultMap="cardMap" parameterType="java.lang.String">
         SELECT
            tb_card.id cid,
            tb_card. NO,
            tb_card.address,
            tb_card.expired,
        tb_student.*
        FROM
            tb_student
        INNER JOIN tb_card ON tb_student.card_id = tb_card.id
        WHERE
            tb_card. no = #{no}
    </select>
</mapper>

测试:

@Test
public void testgetCardByNo() {
    TbCard card = cm.getCardByNo("5645654645654653");
    System.out.println(card);
}
需求:
根据学生姓名查询学生的基本信息同时查询学生的身份证信息

接口:

/**
 * 根据学生姓名查询学生的基本信息同时查询学生的身份证信息
 * @param name
 * @return
 */
List<TbStudent> findStudentsByName(String name);

编写SQL映射文件TbStudentMapper.xml

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

<mapper namespace="com.haidi8.mapper.TbStudentMapper">
    <resultMap id="studentMap" type="TbStudent">
        <id  property="id" column="ID" ></id>
        <result property="name" column="NAME"></result>
        <result property="age" column="AGE"></result>
        <result property="birthday" column="BIRTHDAY"></result>
        <result property="sex" column="SEX"></result>
        <result property="cardId" column="CARD_ID"></result>
        <!--学生对象有一个身份证对象  has a-->
        <association property="card" javaType="TbCard">
            <id property="id" column="CID"></id>
            <result property="address" column="ADDRESS"></result>
            <result property="no" column="NO"></result>
            <result property="expired" column="EXPIRED"></result>
        </association>
    </resultMap>

    <select id="findStudentsByName" resultMap="studentMap" parameterType="java.lang.String">
           SELECT
                tb_student.*, tb_card.id cid,
                tb_card. NO,
                tb_card.address,
                tb_card.expired
            FROM
                tb_student
            INNER JOIN tb_card ON tb_student.card_id = tb_card.id
            WHERE
                tb_student. NAME = #{name}
    </select>
</mapper>

测试:

@Test
public void testfindStudentsByName() {
    List<TbStudent> students = sm.findStudentsByName("武大郎");
    for (TbStudent student : students) {
        System.out.println(student);
    }
}

13.2.1 MyBatis一对一关联查询总结

MyBatis中使用association(有一个)标签来解决一对一的关联查询,association标签可用的属性如下:

property:对象属性的名称
javaType:对象属性的类型
column:所对应的外键字段名称
select:使用另一个查询封装的结果

13.3 一对多关联(重点)

数据库表:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_girl
-- ----------------------------
DROP TABLE IF EXISTS `tb_girl`;
CREATE TABLE `tb_girl`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `height` double NULL DEFAULT NULL,
  `cupsize` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `boy_id` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `FK_boy_Id`(`boy_id`) USING BTREE,
  CONSTRAINT `FK_boy_Id` FOREIGN KEY (`boy_id`) REFERENCES `tb_boy` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_girl
-- ----------------------------
INSERT INTO `tb_girl` VALUES (1, '凤姐', 150, 'C', 1);
INSERT INTO `tb_girl` VALUES (2, '小泽玛利亚', 165, 'A', 1);
INSERT INTO `tb_girl` VALUES (3, '乔碧罗', 160, 'D', 1);

SET FOREIGN_KEY_CHECKS = 1;

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_boy
-- ----------------------------
DROP TABLE IF EXISTS `tb_boy`;
CREATE TABLE `tb_boy`  (
  `id` int(0) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,
  `age` int(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of tb_boy
-- ----------------------------
INSERT INTO `tb_boy` VALUES (1, '刘兵', '男', 20);

SET FOREIGN_KEY_CHECKS = 1;

表关系:
在这里插入图片描述

需求:
根据boy的id查询他自己的信息和他所有的女朋友信息

接口:

/**
 * 根据boy的id查询他自己的信息和他所有的女朋友信息
 * @param id
 * @return
 */
TbBoy getBoyById(long id);

编写SQL映射文件TbBoyMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.haidi8.mapper.TbBoyMapper">
    <resultMap id="boyMap" type="TbBoy">
        <id property="id" column="ID"></id>
        <result property="username" column="username"></result>
        <result property="sex" column="sex"></result>
        <result property="age" column="age"></result>
        <!--一个男人下有多个女朋友-->
        <collection property="girls" ofType="TbGirl">
            <id property="id" column="GID"></id>
            <result property="username" column="GNAME"></result>
            <result property="height" column="HEIGHT"></result>
            <result property="cupsize" column="CUPSIZE"></result>
            <result property="boyId" column="BOY_ID"></result>
        </collection>
    </resultMap>

    <select id="getBoyById" resultMap="boyMap" parameterType="long">
         select tb_boy.*,
                    tb_girl.id gid,
                    tb_girl.height,
                    tb_girl.username gname,
                    tb_girl.cupsize,
                    tb_girl.boy_id
                   from tb_boy inner join tb_girl on tb_boy.id = tb_girl.boy_id
                   where tb_boy.id=#{id}
    </select>
</mapper>

测试:

@Test
public void testgetBoyById(){
    TbBoy boyById =  bm.getBoyById(1);
    System.out.println(boyById);
}
需求
根据id查询女孩及其男朋友的信息

接口:

/**
 * 根据id查询女孩及其男朋友的信息
 * @param id
 * @return
 */
TbGirl getGirlById(long id);

编写SQL映射文件TbGirlMapper.xml

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

<mapper namespace="com.haidi8.mapper.TbGirlMapper">
    <resultMap id="girlMap" type="TbGirl">
        <id property="id" column="GID"></id>
        <result property="username" column="GNAME"></result>
        <result property="height" column="HEIGHT"></result>
        <result property="cupsize" column="CUPSIZE"></result>
        <result property="boyId" column="BOY_ID"></result>
        <association property="boy" javaType="TbBoy">
            <id property="id" column="ID"></id>
            <result property="username" column="username"></result>
            <result property="sex" column="sex"></result>
            <result property="age" column="age"></result>
        </association>
    </resultMap>

    <select id="getGirlById" resultMap="girlMap">
            select tb_boy.*,
           tb_girl.id gid,
           tb_girl.height,
           tb_girl.username gname,
           tb_girl.cupsize,
            tb_girl.boy_id
           from tb_boy inner join tb_girl on tb_boy.id = tb_girl.boy_id
           where tb_girl.id=#{id}
    </select>
</mapper>

测试:

@Test
public void testgetGirlById(){
    TbGirl girl = gm.getGirlById(1);
    System.out.println(girl);
}

13.3.1 MyBatis一对多关联查询总结

MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。

13.4 多对多关联(重点)

数据库建表:

DROP TABLE IF EXISTS `tb_admin`;
CREATE TABLE `tb_admin` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `gender` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

-- ----------------------------
-- Records of tb_admin
-- ----------------------------
INSERT INTO `tb_admin` VALUES ('1', '李某', '女', '18');
INSERT INTO `tb_admin` VALUES ('2', '张某', '女', '18');
INSERT INTO `tb_admin` VALUES ('3', '刘某', '男', '20');

-- ----------------------------
-- Table structure for tb_admin_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_admin_role`;
CREATE TABLE `tb_admin_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `admin_id` int DEFAULT NULL,
  `role_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_admin_id` (`admin_id`),
  KEY `fk_role_id` (`role_id`),
  CONSTRAINT `fk_admin_id` FOREIGN KEY (`admin_id`) REFERENCES `tb_admin` (`id`),
  CONSTRAINT `fk_role_id` FOREIGN KEY (`role_id`) REFERENCES `tb_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

-- ----------------------------
-- Records of tb_admin_role
-- ----------------------------
INSERT INTO `tb_admin_role` VALUES ('1', '1', '1');
INSERT INTO `tb_admin_role` VALUES ('2', '1', '2');
INSERT INTO `tb_admin_role` VALUES ('3', '1', '3');
INSERT INTO `tb_admin_role` VALUES ('4', '2', '2');
INSERT INTO `tb_admin_role` VALUES ('5', '2', '3');
INSERT INTO `tb_admin_role` VALUES ('6', '3', '2');
INSERT INTO `tb_admin_role` VALUES ('7', '3', '1');

-- ----------------------------
-- Table structure for tb_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `description` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;

-- ----------------------------
-- Records of tb_role
-- ----------------------------
INSERT INTO `tb_role` VALUES ('1', '售票员', '买票的');
INSERT INTO `tb_role` VALUES ('2', '秘书', '老板的秘书');
INSERT INTO `tb_role` VALUES ('3', '家教老师', '家教');

表关系:、
在这里插入图片描述

需求:
根据用户名查询用户信息的同时查询用户的角色信息

接口:

/**
 * 根据用户名查询用户信息的同时查询用户的角色信息
 * @param name
 * @return
 */
TbAdmin getAdminByName(String name);

编写SQL映射文件TbAdminMapper.xml

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

    <resultMap id="adminMap" type="TbAdmin">
        <id property="id" column="id"></id>
        <result property="name" column="name"></result>
        <result property="gender" column="gender"></result>
        <result property="age" column="age"></result>
        <collection property="roles" ofType="TbRole">
            <id property="id" column="rid"></id>
            <result property="name" column="rname"></result>
            <result property="description" column="description"></result>
        </collection>
    </resultMap>

    <select id="getAdminByName" parameterType="java.lang.String" resultMap="adminMap">
          SELECT
            tb_admin.*,
            tb_role.id 'rid',
            tb_role. NAME 'rname',
            tb_role.description
        FROM
            tb_admin
        INNER JOIN tb_admin_role ON tb_admin.id = tb_admin_role.admin_id
        INNER JOIN tb_role ON tb_admin_role.role_id = tb_role.id
        WHERE
            tb_admin. NAME = #{name}
    </select>
</mapper>

测试:

@Test
public void testgetAdminByName(){
    TbAdmin admin = am.getAdminByName("李某");
    System.out.println(admin);
}
需求:
根据角色的名字查询角色信息的同时查询用户的信息

接口:

/**
 * 根据角色的名字查询角色信息的同时查询用户的信息
 * @param name
 * @return
 */
TbRole getRoleByName(String name);

编写SQL映射文件TbRoleMapper.xml

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

    <resultMap id="roleMap" type="TbRole">
        <id property="id" column="rid"></id>
        <result property="name" column="rname"></result>
        <result property="description" column="description"></result>
        <!--一个角色多个用户-->
        <collection property="admins" ofType="TbAdmin">
            <id property="id" column="id"></id>
            <result property="name" column="name"></result>
            <result property="gender" column="gender"></result>
            <result property="age" column="age"></result>
        </collection>
    </resultMap>
    <select id="getRoleByName" resultMap="roleMap" parameterType="java.lang.String">
         SELECT
          tb_role.id 'rid',
            tb_role. NAME 'rname',
            tb_role.description,
          tb_admin.*
        FROM
            tb_admin
        INNER JOIN tb_admin_role ON tb_admin.id = tb_admin_role.admin_id
        INNER JOIN tb_role ON tb_admin_role.role_id = tb_role.id
        WHERE
            tb_role. NAME = #{name}
    </select>
</mapper>

测试:

@Test
public void testgetRoleByName(){
    TbRole role = rm.getRoleByName("秘书");
    System.out.println(role);
}

13.5 小结

在这里插入图片描述

第十四、Mybatis的缓存

14.1 MyBatis缓存(面试题)

Cache 缓存
请添加图片描述

缓存一致性问题!
缓存中有,先查询缓存。缓存中没有,那么查询数据库。这样的话不用每次都查询数据库。减轻数据库的压力。提高查询效率!!!
第一次查询的时候,由于缓存中没有,那么去查询数据库返回给客户端。同时还会把这个次查询的数据放入缓存。
第二次查询同样的数据时候,发现缓存中曾经有查询过的数据,那么直接从缓存中读取。不必再次查询数据库,减轻数据库服务器压力,缓存中有就缓存,缓存中没有就查数据库!·
如果数据库中数据发生了修改,那么缓存就会清空,保持数据的一致性!防止脏数据!

请添加图片描述

14.2 MyBatis缓存分析

mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。

请添加图片描述

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

	二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

	Mybatis的缓存,包括一级缓存和二级缓存
 	一级缓存指的就是sqlsession,在sqlsession中有一个数据区域,是map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。一级缓存中的value,就是查询出的结果对象。
   一级缓存是session级别的,同一个session! 1级缓存是系统自带,不需要手动开启!

 	二级缓存指的就是同一个namespace下的mapper,二级缓存中,也有一个map结构,这个区域就是二级缓存区域。二级缓存中的key是由sql语句、条件、statement等信息组成一个唯一值。二级缓存中的value,就是查询出的结果对象。

二级缓存,可以跨session!二级缓存是要配置,然后手动开启!

 	一级缓存是默认使用的。
	二级缓存需要手动开启。

Map<String,Object>  key 缓存标志  Value 缓存的数据

14.3 一级缓存

请添加图片描述

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。	
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
Mybatis默认支持一级缓存。

测试:

 /**
     * 测试一级缓存,一级缓存默认开启的!
     * 一级缓存是Sqlsession级别的缓存,每个Sqlsession对象都维护了map缓存区!如果跨了sqlsession,一级缓存失效啦!
     * 如果数据库发生了增删改,那么缓存自动失效!
     * 一级缓存默认开启,可以手动关闭!
     */
    @Test
    public void test1() throws Exception {
        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        TbStudentMapper sm = sqlSession.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!

        Thread.sleep(2000);
        TbStudent s2 = sm.getById(2); //第二次查询ID为2的对象,缓存中已经有了,直接查询查询缓存,没有查询数据库!没有SQL!
        System.out.println(s1==s2); //true
        sqlSession.close();
    }

    @Test
    public void test2() throws Exception {
        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        TbStudentMapper sm = sqlSession.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!
        //手动的清空缓存!
        sqlSession.clearCache();
        Thread.sleep(2000);
        TbStudent s2 = sm.getById(2); //第二次查询ID为2的对象,缓存中已经有了,直接查询查询缓存,没有查询数据库!没有SQL!
        System.out.println(s1==s2); //false
        sqlSession.close();
    }

    @Test
    public void test3() throws Exception {
        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        TbStudentMapper sm = sqlSession.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!
        //如果执行了增删改,缓存自动清空
        TbStudent s=new TbStudent();
        s.setId(1);
        s.setName("小A");
        sm.updateStudent(s);

        Thread.sleep(2000);

        TbStudent s2 = sm.getById(2); //第二次查询ID为2的对象,缓存中已经有了,但是期间发生了sql的更新,所有缓存消失,没有
        // 缓存,要查询数据库
        System.out.println(s1==s2); //false
        sqlSession.close();
    }

    @Test
    public void test4() throws Exception{

        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        TbStudentMapper sm1 = sqlSession1.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm1.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!

        TbStudentMapper sm3 = sqlSession1.getMapper(TbStudentMapper.class);
        TbStudent s3 = sm3.getById(1); //第二查询ID为1的对象,缓存中没有,查询数据库,发送查询SQL语句!
        sqlSession1.close();
        System.out.println(s1==s3); //fasle
    }
    @Test
    public void test6() throws Exception{

        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        TbStudentMapper sm1 = sqlSession1.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm1.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!
        sqlSession1.close();
        //Thread.sleep(1000);

        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        TbStudentMapper sm2 = sqlSession2.getMapper(TbStudentMapper.class);
        TbStudent s2 = sm2.getById(2);//第二次查询ID为2的对象,缓存中有,但是是属于sqlSesson1这个对象的,第二次查询的
        // session对象为sqlSession2 ,不同sqlSessoin 之间不能互相取缓存。
        sqlSession2.close();

        System.out.println(s1==s2); //fasle
    }

 @Test
    public void test7() throws IOException {
        /**
         *人工关闭一级缓存
         *关闭MyBatis一级缓存总开关
         *localCacheScope是本地缓存(一级缓存)的作用域,只有两种取值:SESSION和STATEMENT,取STATEMENT意味着关闭一级缓存
         * <setting name="localCacheScope" value="SESSION"/>
         */
        String resources="mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        TbStudentMapper sm1 = sqlSession1.getMapper(TbStudentMapper.class);
        TbStudent s1 = sm1.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!

        TbStudentMapper sm3 = sqlSession1.getMapper(TbStudentMapper.class);
        TbStudent s3 = sm3.getById(2); //第二查询ID为2的对象,缓存中没有(因为我们关闭了一级缓存),查询数据库,发送查询SQL语句!
        sqlSession1.close();
        System.out.println(s1==s3); //fasle
    }

14.4 二级缓存

下图是多个sqlSession请求UserMapper的二级缓存图解。

请添加图片描述

二级缓存是mapper级别的。

第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。

第二次调用相同namespace下的mapper映射文件(xml)中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。

如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

开启二级缓存

Mybatis默认是没有开启二级缓存

1.在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):

在settings标签中添加以下内容:
在这里插入图片描述

2.在StudentMapper映射文件中,加入以下内容,开启二级缓存:

请添加图片描述

请添加图片描述


**
<*\cache eviction =“LRU” flushInterval=“60000” readOnly=“false” size=“1000”>

实现序列化
请添加图片描述

由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。

缓存默认是存入内存中,但是如果需要把缓存对象存入硬盘那么久需要序列化(实体类要实现)

禁用二级缓存

该statement中设置useCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。
在这里插入图片描述

测试:

@Test
public void test8() throws IOException {
    /**
     开启二级缓存,默认不开启的,为FALSE
     <setting name="cacheEnabled" value="false"/>
     设置StudentMapper缓存策略
     eviction: 缓存的淘汰回收策略,默认是LRU
         LRU - 最近最少使用的:移除最长时间不被使用的对象
         FIFO - 先进先出策略:按对象进入缓存的顺序来移除它们
         SOFT - 软引用:移除基于垃圾回收器状态和软引用规则的对象
         WEAK - 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
     flushInterval:缓存的刷新间隔,默认是不刷新的 默认不刷新 毫秒单位!
     eadOnly:缓存的只读设置,默认是false 可读可写
         true:只读       mybatis认为只会对缓存中的数据进行读取操作,不会有修改操作为了加快数据的读取,直接将缓存中对象的引用交给用户
         false:不只读    mybatis认为不仅会有读取数据,还会有修改操作。会通过序列化和反序列化的技术克隆一份新的数据交给用户
     size:缓存中的对象个数
     type:自定义缓存或者整合第三方缓存时使用
          class MyCache implements Cache{}
     blocking="" 缓存中如果没有这个缓存,那么进行等待阻塞 默认是不阻塞
     <cache eviction="LRU" flushInterval="2000" readOnly="false" size="1"></cache>
     */
    String resources="mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resources);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
    TbStudentMapper sm1 = sqlSession1.getMapper(TbStudentMapper.class);
    TbStudent s1 = sm1.getById(2); //第一查询ID为2的对象,缓存中没有,查询数据库,发送查询SQL语句!

    sqlSession1.close();//要第一个session提交后,才能进行缓存,要不然不能进行缓存如果第一个session没有提交,则没有进行二级缓存。 
                        //所以,想实现二级缓存,需要前面的session已经提交过,并且相同的提交sql。
    SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
    TbStudentMapper sm3 = sqlSession2.getMapper(TbStudentMapper.class);
    TbStudent s3 = sm3.getById(2); //第二查询ID为2的对象,因为我们开启了二级缓存,所以从缓存中取值,不发送SQL
    sqlSession1.close();
    System.out.println(s1==s3); //true
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94161.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!