Mybatis个人笔记

Mybatis笔记总结

1 、简介

​ MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

特点

  1. 持久层框架
  2. 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
  3. 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO

2、基本使用步骤

2.1、搭建环境
2.1.1、引入mybatis相关jar包
 <dependencies>
     <!-- mybatis -->
     <dependency>
         <groupId>org.mybatis</groupId>
         <artifactId>mybatis</artifactId>
         <version>3.5.6</version>
     </dependency>

     <!-- mysql-connector-java -->
     <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>8.0.22</version>
     </dependency>

     <!-- junit -->
     <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>4.13</version>
         <scope>test</scope>
     </dependency>
     
     <!-- lombok -->
     <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.16</version>
     </dependency>
</dependencies>
2.1.2、确保有对应的数据库表
# 创建mybatis数据库
CREATE DATABASE IF NOT EXISTS `mybatis` ;

# 使用mybatis数据库
USE `mybatis`;

DROP TABLE IF EXISTS `user`;

# 创建user表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增的用户id',
  `username` varchar(20) NOT NULL COMMENT '用户名',
  `birthday` date DEFAULT NULL COMMENT '注册时间',
  `gender` varchar(7) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;


# 表中插入若干条数据
insert  into `user`(`id`,`username`,`birthday`,`gender`,`address`) values (1,'zero','2020-02-25','男','广州市白云区'),(2,'tom','2020-02-23','男','广州市番禺区'),(3,'zero','2020-02-25','男','广州市海珠区'),(4,'jerry','2020-01-25','男','广州市增城区'),(5,'gogo','2020-03-25','男','广州市白云区'),(6,'ben','2020-02-20','男','广州市番禺区'),(7,'peter','2020-02-23','男','广州市天河区'),(12,'haha','2020-03-01','男','广州市从化区');
2.1.3、编写实体类
package com.zero.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private int id; //对应表中的id字段
    private String username;    //对应表中的username字段
    private Date birthday;      //对应表中的birthday字段
    private String gender;      //对应表中的gender字段
    private String address;     //对应表中的address字段
}

注意:一般要求类的属性名与表的字段名一致,映射时才能一一对应

2.1.4、编写实体类对应的dao层接口
package com.zero.dao;
import com.zero.pojo.User;
import java.util.List;

/**
 * User实体类的dao接口(统一命名为...Mapper)
 */
public interface UserMapper {

    /**
     * 获得表中所有的user信息
     * @return user列表
     */
    List<User> getAllUser();
}
2.2、编写mabatis的核心配置文件
<?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>
    <!-- 环境容器标签,用于存放多个环境,default的值指定使用哪一个环境 -->
    <environments default="development">
        <!-- 环境标签,用于配置一个环境,id为环境标识 -->
        <environment id="development">
            <!-- 配置数据库的事务管理器的类型 -->
            <transactionManager type="JDBC"/>

            <!-- 配置数据源,POOLED表示采用数据库连接池的方式 -->
            <dataSource type="POOLED">
                <!-- 配置数据库驱动 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- 配置数据库连接的url -->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC"/>
                <!-- 配置数据库的连接名 -->
                <property name="username" value="root"/>
                <!-- 配置数据库的连接名的密码 -->
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
    <mappers>
        <!-- 配置UserMapper.xml文件到核心配置文件中 -->
        <mapper resource="com/zero/dao/UserMapper.xml"/>
    </mappers>
</configuration>
2.3、编写dao层接口的mapper映射文件
<?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用于绑定dao层实体类接口的全限定名 -->
<mapper namespace="com.zero.dao.UserMapper">
    <!-- sql标签,id用于绑定接口的方法名,resultType用于绑定返回类型(需要写全限定类名),标签体内部写sql语句 -->
    <select id="getAllUser" resultType="com.zero.pojo.User">
        select * from USER
    </select>
</mapper>

注:当使用maven工程,且mapper映射文件写在包内时,此处有个bug。这是由于配置文件应写在resources文件夹内,在Java内的配置文件导不出来。

解决方案:(两种)

  1. 将mapper配置文件移到resources下。
  2. 在pom.xml配置文件中配置文件过滤,以支持包内文件的导出(配置内容如下)
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>
2.4、在核心配置文件中注册mapper映射文件

参照2.2核心配置文件的标签内的写法

<!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
<mappers>
    <!-- 配置UserMapper.xml文件到核心配置文件中(注意用/而不是.) -->
    <mapper resource="com/zero/dao/UserMapper.xml"/>
</mappers>
2.5、封装mybatis的工具类,以便每次都可以直接获得SqlSession对象
package com.zero.util;

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;

/**
 * Mybatis的工具类,用于获取SqlSession对象,避免多余的重复代码
 */
public class MybatisUtil {

    //存放sqlSessionFactory对象
    private static SqlSessionFactory sqlSessionFactory = null;

    static {
        //核心配置文件的路径
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            //获得配置文件流对象
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //通过SqlSessionFactoryBuilder对象获得SqlSessionFactory对象
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    /**
     * 返回一个SqlSession对象
     * @return sqlSession
     */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}
2.6、测试
package com.zero.dao;

import com.zero.pojo.User;
import com.zero.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class UserMapperTest {

    @Test
    public void test(){
        //通过工具类获得sqlSession对象
        SqlSession sqlSession = MybatisUtil.getSqlSession();

        //通过sqlSession对象获得dao层接口的实现类对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //调用dao层接口的方法获得sql执行的结果
        List<User> userList = userMapper.getAllUser();

        //遍历输出结果
        for (User user : userList) {
            System.out.println(user);
        }
        
        //关闭sqlSession对象
        sqlSession.close();
    }
}

3、CRUD操作

​ 基于2、中的框架环境实现数据库表的增删改查操作。mybatis实现这些操作仅需三步:

  1. 在dao层接口中编写接口方法
  2. 在接口的mapper配置文件中编写对应的sql标签
  3. 通过获取接口实现类对象使用相应方法即可

注意事项:mybatis对于增删改是需要提交事务的,否则数据库不会更新

注意事项:#{}内的参数取值情况,若参数为基本数据类型,则为方法的参数名(可以使用@Param注解在方法参数前绑定被识别的参数名);若是pojo或者map的参数,则参数为其属性名或者键名

3.1、select
  • 接口方法

    /**
    * 通过用户id查询用户
    * @param id 用户id
    * @return 返回查询结果
    */
    User getUserById(int id);
    
  • mapper文件的sql标签

    <!-- 绑定getUserById()方法,parameterType表示方法参数类型,#{}表示取出参数(类似于占位符) -->
    <select id="getUserById" parameterType="int" resultType="com.zero.pojo.User">
        SELECT * FROM user WHERE id = #{id}
    </select>
    
  • 使用

    @Test
    public void testGetUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
        //查询id=1的用户
        User user = userMapper.getUserById(1);
    
        System.out.println(user);
    
        sqlSession.close();
    }
    
3.2、insert
  • 接口方法

    /**
    * 增加一个用户
    * @param user 用户信息
    * @return 返回增加结果
    */
    int addUser(User user);
    
  • mapper文件的sql标签

    <!-- 绑定addUser()方法,#{}对于基本数据类型则需要使用与方法参数相同的名称,对于pojo则名称与属性名一致 -->
    <insert id="addUser" parameterType="com.zero.pojo.User">
        INSERT INTO user (username, birthday, gender, address)
        VALUES (#{username}, #{birthday}, #{gender}, #{address})
    </insert>
    
  • 使用

    @Test
    public void testAddUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
        //创建用户对象
        User user = new User();
        user.setUsername("张三");
        user.setBirthday(new Date());
        user.setGender("男");
        user.setAddress("广州市从化区");
    
        //添加用户
        int res = userMapper.addUser(user);
    
        //判断是否执行成功
        if(res>=0){
            System.out.println("添加成功");
        }
    
        //提交事务
        sqlSession.commit();
    
        sqlSession.close();
    }
    
3.3、update
  • 接口方法

    /**
    * 更新用户信息
    * @param user 用户信息
    * @return 返回更新结果
    */
    int updateUser(User user);
    
  • mapper文件的sql标签

    <!-- 绑定updateUser()方法 -->
    <update id="updateUser" parameterType="com.zero.pojo.User">
        UPDATE user SET username = #{username}, birthday = #{birthday},
        gender = #{gender}, address = #{address} WHERE id = #{id}
    </update>
    
  • 使用

    @Test
    public void testUpdateUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
        //获得一个用户
        User user = userMapper.getUserById(1);
    
        //修改信息
        user.setUsername("kkkk");
    
        //数据库执行更新
        int res = userMapper.updateUser(user);
    
        //判断是否执行成功
        if (res > 0){
            System.out.println("更新成功");
        }
    
        //提交事务
        sqlSession.commit();
    
        sqlSession.close();
    }
    
3.4、delete
  • 接口方法

    /**
    * 通过用户id删除用户
    * @param id 用户id
    * @return 返回删除结果
    */
    int deleteUserById(int id);
    
  • mapper文件的sql标签

    <!-- 绑定deleteUserById()方法 -->
    <delete id="deleteUserById" parameterType="int">
        DELETE from user WHERE id = #{id}
    </delete>
    
  • 使用

    @Test
    public void testDeleteUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
    
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
        //删除用户
        int res = userMapper.deleteUserById(18);
    
        //判断是否执行成功
        if(res>0){
            System.out.println("删除成功");
        }
    
        //提交事务
        sqlSession.commit();
    
        sqlSession.close();
    }
    

4、mybatis的工作流程

在这里插入图片描述

  1. 应用程序创建SqlSessionFactoryBuilder对象
  2. SqlSessionFactoryBuilder对象读取核心配置文件(mybatis-config.xml)
  3. SqlSessionFactoryBuilder对象创建一个SqlSessionFactory对象
  4. SqlSessionFactory对象创建一个SqlSession对象
  5. SqlSession对象读取xxxMapper.xml中的配置文件
  6. SqlSession通过读取到的配置文件执行相应sql语句,并结果返回给调用程序

生命周期问题

  • SqlSessionFactoryBuilder是使用建造者模式建造一个SqlSessionFactory对象,SqlSessionFactory对象通过工厂模式创建SqlSession对象,最终SqlSession对象执行相应的sql调用就结束其任务。本质上,SqlSessionFactory对象相当于一个数据库连接池,SqlSession对象相当于一个数据库连接。
  • 基于上面的分析,将三个对象的生命周期分别做以下处理:
    1. 由于SqlSessionFactoryBuilder对象只是使用一次,可以将其放在局部变量中去创建SqlSessionFactory对象
    2. 由于SqlSessionFactory对象是起到数据库连接池的作用,故将其设置为单例,并且其生命周期和整个程序共存亡,以便随时获取SqlSession对象
    3. 由于SqlSession对象类似于数据库连接对象(且官方中说明其是线程不安全的),因此该对象不应该被共享,应当放置于局部变量中,需要时创建,不需要时需要及时关闭,以免占用太多连接导致程序崩坏。

5、核心配置文件

5.1、properties(属性)

​ 该配置项可用于配置一些属性,或者读取外部配置文件中的属性,以供配置文件使用。其配置方式如下:(此处以改造数据库连接属性为例)

  1. 书写配置文件

    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
    username=root
    password=root
    
  2. 引入外部配置文件

    <?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>
    
        <!-- 外部引入的方式,引入resources下的db.properties配置文件 -->
        <properties resource="db.properties"></properties>
    
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <!-- 配置数据库驱动,使用了外部文件中配置的driver属性 -->
                    <property name="driver" value="${driver}"/>
                    <!-- 配置数据库连接的url,使用了外部文件中配置的url属性 -->
                    <property name="url" value="${url}"/>
                    <!-- 配置数据库的连接名,使用了外部文件中配置的username属性 -->
                    <property name="username" value="${username}"/>
                    <!-- 配置数据库的连接名的密码,使用了外部文件中配置的password属性 -->
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        
        <mappers>
            <mapper resource="com/zero/dao/UserMapper.xml"/>
        </mappers>
    </configuration>
    
  3. 使用属性

    ${属性名}
    

注:内部也可以配置属性,但其同名属性优先级低于外部文件,配置方式如下:

<properties resource="db.properties">
    <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
</properties>
5.2、settings(设置)

​ 这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。以下介绍几个常用的配置项,具体参照官网解释:https://mybatis.org/mybatis-3/zh/configuration.html#settings

属性名描述取值默认值
cacheEnabled是否开启缓存true | falsetrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射true | falsefalse
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | …未设置

使用mybatis的默认日志

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
5.3、typeAliases(类型别名)

​ 类型别名可用于为 Java 类型设置一个缩写名字,以便在写配置时不用去写完全限定名。使用方式如下:

  1. 逐一配置别名:
<!-- 给com.zero.pojo.User类起一个别名为user -->
    <typeAliases>
        <typeAlias type="com.zero.pojo.User" alias="user"/>
    </typeAliases>
  1. 扫描整个包配置别名
<!-- 给com.zero.pojo包下的所有类起别名,其别名为其类名首字母小写 -->
<typeAliases>
    <package name="com.zero.pojo" />
</typeAliases>
5.4、environments

​ MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory实例只能选择一种环境。 其常用的方式如下,具体的参照官网:https://mybatis.org/mybatis-3/zh/configuration.html#environments

<?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>
    <!-- 环境容器标签,用于存放多个环境,default的值指定使用哪一个环境 -->
    <environments default="development">
        <!-- 环境标签,用于配置一个环境,id为环境标识 -->
        <environment id="development">
            <!-- 配置数据库的事务管理器的类型 -->
            <transactionManager type="JDBC"/>

            <!-- 配置数据源,POOLED表示采用数据库连接池的方式 -->
            <dataSource type="POOLED">
                <!-- 配置数据库驱动 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!-- 配置数据库连接的url -->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC"/>
                <!-- 配置数据库的连接名 -->
                <property name="username" value="root"/>
                <!-- 配置数据库的连接名的密码 -->
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- mapper容器,用于配置mapper文件的路径(根据编写的mapper文件来写),以便加载 -->
    <mappers>
        <!-- 配置UserMapper.xml文件到核心配置文件中 -->
        <mapper resource="com/zero/dao/UserMapper.xml"/>
    </mappers>
</configuration>
5.5、mappers

​ 该配置用于告诉核心配置文件编写sql语句的mapper配置文件去哪里找。其配置的方式主要有以下几种方式:

  1. 通过使用相对于类路径的资源引用

    <!-- 通过资源路径引用 -->
    <mapper resource="com/zero/dao/UserMapper.xml"/>
    
  2. 使用接口的完全限定类名寻找

    <!-- 通过接口的完全限定类名引用 -->
    <mapper class="com.zero.dao.UserMapper" />
    
  3. 通过扫描包寻找

    <!-- 将扫描包进行寻找-->
    <package name="com.zero.dao" />
    

注意事项:第一种方式若将mapper映射文件放置在Java源码路径下,需要配置maven的静态过滤;第二、第三种方式需要在同一个包内写好接口类和mapper映射文件,且要求两个文件的文件名相同

5.6、其他配置

​ mybatis还提供了以下的配置项,详细查看官网:

typeHandlers(类型处理器)

objectFactory(对象工厂)

plugins(插件)

databaseIdProvider(数据库厂商标识)

6、ResultMap的使用

6.1、字段映射

​ 在进行结果集映射时,mybatis会在幕后为我们创建一个resultMap,将pojo类的属性和数据库表的字段名一一对应,不过要求属性名和字段名必须相同。但有时候在定义表字段名和pojo类时,会出现不同的情况,这时就需要进行手动的映射了。比如以下表和pojo类的情况:

数据库表

# 创建user表
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增的用户id',
  `username` varchar(20) NOT NULL COMMENT '用户名',
  `birthday` date DEFAULT NULL COMMENT '注册时间',
  `gender` varchar(7) DEFAULT NULL COMMENT '性别',
  `address` varchar(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;

pojo类

package com.zero.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private int id; //对应表中的id字段
    private String user_name;    //对应表中的username字段
    private Date birthday;      //对应表中的birthday字段
    private String gender;      //对应表中的gender字段
    private String address;     //对应表中的address字段
}

​ 对比表和pojo类,pojo类中的userName属性和表中的user_name字段并不是一一对应,这在默认的映射中会找不到该映射属性,使得最后的查询结果如下:

User(id=1, user_name=null, birthday=Mon Feb 24 00:00:00 CST 2020, gender=男, address=广州市白云区)
User(id=2, user_name=null, birthday=Sun Feb 23 00:00:00 CST 2020, gender=男, address=广州市番禺区)

使用resultMap进行手动映射(仅需映射不同的属性和字段)

<?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.zero.dao.UserMapper">
    <!-- resultMap属性通过id绑定下面定义的resultMap -->
    <select id="getAllUser" resultMap="user">
        select * from USER
    </select>

    <!-- 定义一个resultMap -->
    <resultMap id="user" type="com.zero.pojo.User">
        <result property="user_name" column="username"/>
    </resultMap>
</mapper>

输出结果

User(id=1, user_name=kkkk, birthday=Mon Feb 24 00:00:00 CST 2020, gender=男, address=广州市白云区)
User(id=2, user_name=tom, birthday=Sun Feb 23 00:00:00 CST 2020, gender=男, address=广州市番禺区)
6.2、多对一

​ 对于单一的属性,通过简单的属性和字段名映射即可解决从数据库表到pojo类的映射,但对于复杂的属性与复杂的sql语句却很难实现一一映射。比如数据库中放置的仅仅是简单的数据类型,但pojo类中放置的是另一个pojo类,这就很难进行映射,需要手动使用result Map进行映射。

  • 构造环境

    数据表

    # 教师表
    CREATE TABLE `teacher` (
      `id` int(10) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
    
    # 学生表
    CREATE TABLE `student` (
      `id` int(10) NOT NULL AUTO_INCREMENT,
      `name` varchar(20) DEFAULT NULL,
      `tid` int(10) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `tid` (`tid`),
      CONSTRAINT `student_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
    

    pojo类

    import lombok.Data;
    
    //学生类
    @Data
    public class Student {
        private int id;
        private String name;
        private Teacher teacher;	//Teacher类对象
    }
    
    import lombok.Data;
    import java.util.List;
    
    //教师类
    @Data
    public class Teacher {
        private int id;
        private String name;
        private List<Student> students;		//Student类的集合
    }
    

    分析

    ​ 一个教师对应多个学生,多个学生对应一个老师。从学生的角度看,与教师的关系是多对一;从教师的角度看,与学生的关系是一对多。

  • 使用方式

    编写dao层接口

    import com.zero.pojo.Student;
    import java.util.List;
    
    public interface StudentMapper {
    
        List<Student> getStudentList();
    }
    

    编写mapper配置文件(使用resultMap中的association标签进行关联)

    <?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用于绑定dao层实体类接口的全限定名 -->
    <mapper namespace="com.zero.dao.StudentMapper">
        <!-- 编写对应的sql, 返回类型通过resultMap进行映射 -->
        <select id="getStudentList" resultMap="student">
            SELECT s.id sid, s.name sname, t.id tid, t.name tname
            FROM student s, teacher t
            WHERE  s.tid = t.id;
        </select>
    
        <!-- id表示该resultMap的标识,type表示该映射返回的实际类型 -->
        <resultMap id="student" type="com.zero.pojo.Student">
            <!-- 普通的数据类型字段映射 -->
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            
            <!-- 关联关系的映射,关联关系即对应一个pojo类属性的映射,
                 property为字段名,javaType为该属性的pojo类型 -->
            <association property="teacher" javaType="com.zero.pojo.Teacher">
                <!-- 这里的映射和上面的差不多,只是这里指的property是该pojo类的属性映射 -->
                <result property="id" column="tid"/>
                <result property="name" column="tname"/>
            </association>
        </resultMap>
    
    </mapper>
    

    测试结果

    @Test
    public void studentTest(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
    
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    
        List<Student> studentList = studentMapper.getStudentList();
    
        for (Student student : studentList) {
            System.out.println(student);
        }
        /*
            * Student(id=1, name=tom, teacher=Teacher(id=1, name=zero, students=null))
            * Student(id=2, name=gogo, teacher=Teacher(id=1, name=zero, students=null))
            * Student(id=3, name=jerry, teacher=Teacher(id=1, name=zero, students=null))
            * Student(id=5, name=张三, teacher=Teacher(id=2, name=haha, students=null))
            * Student(id=6, name=李四, teacher=Teacher(id=2, name=haha, students=null))
            * */
    
        sqlSession.close();
    
    }
    
6.3、一对多

​ 和上面一对多的情况类似,只不过是描述的情形反转。此处基于上面的环境,从教师的角度进行一对多的映射使用。

dao层接口

import com.zero.pojo.Teacher;
import org.apache.ibatis.annotations.Param;

//教师dao层接口
public interface TeacherMapper {

    Teacher getTeacherById(@Param("tid") int id);
}

mapper配置(使用resultMap中的collection标签进行集合绑定)

<?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用于绑定dao层实体类接口的全限定名 -->
<mapper namespace="com.zero.dao.TeacherMapper">

    <select id="getTeacherById" resultMap="teacher" parameterType="_int">
        SELECT t.id tid, t.name tname, s.id sid,s.name sname
        FROM student s, teacher t
        WHERE s.tid = t.id AND t.id = #{tid}
    </select>


    <resultMap id="teacher" type="com.zero.pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>

        <!-- 一对多使用collection集合标签,javaType表示返回类型,ofType表示返回类型内的泛型-->
        <collection property="students" javaType="ArrayList" ofType="com.zero.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
        </collection>
    </resultMap>

</mapper>

测试结果

@Test
public void teacherTest(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);

    Teacher teacher = teacherMapper.getTeacherById(1);

    System.out.println(teacher);
    /*
        * Teacher(
        *   id=1,
        *   name=zero,
        *   students=[
        *       Student(id=1, name=tom, teacher=null),
        *       Student(id=2, name=gogo, teacher=null),
        *       Student(id=3, name=jerry, teacher=null)
        *   ])
        * */

    sqlSession.close();

}

7、动态sql

​ 动态sql,其存在的意义是为了解决在指定条件下对sql进行按需组织与拼接,其本质就是按照条件进行sql语句的拼接,只是将该工作移到了编写sql的配置文件中,而不是在java代码中进行拼接。

搭建环境

  • 数据库表

    # 创建blog表,包含4个字段
    CREATE TABLE blog(
        id VARCHAR(50) PRIMARY KEY,
        title VARCHAR(20),
        author VARCHAR(20),
        create_time DATE 
    )ENGINE INNODB DEFAULT CHARSET=utf8
    
    # 插入5条数据
    INSERT INTO blog VALUES
    ('123456', 'java', 'zero', '2020-12-12'),
    ('123457', 'mysql', 'zero', '2020-12-13'),
    ('123458', 'java web', 'zero', '2020-12-14'),
    ('123459', 'mybatis', 'zero', '2020-12-15'),
    ('123450', 'spring', 'zero', '2020-12-16');
    
  • 编写对应的pojo类

    import lombok.Data;
    import java.util.Date;
    
    @Data
    public class Blog {
        private String id;
        private String title;
        private String author;
        private Date date;
    }
    
  • 编写对应的dao层接口

    import com.zero.pojo.Blog;
    import java.util.List;
    import java.util.Map;
    
    public interface BlogMapper {
    	//待写接口方法
    }
    
  • 编写对应的配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.zero.dao.BlogMapper">
    	<!-- 待写sql配置 -->
    </mapper>
    

常用标签

  • where、if标签

    ​ where标签会在包含的内容存在时,在前面的sql后添加where关键字, 并过滤第一个子元素前的and/or。if标签会按照传入参数情况,根据test中的结果是否成立来拼接sql。

    • 在dao层接口添加一个方法

      /**
      * 使用if标签的动态查询接口
      * @param map if判断的参数,为空则不参与查询的选择
      * @return 返回查询结果
      */
      List<Blog> getBlogByIf(Map map);
      
    • 在配置文件中使用标签写sql语句

      <!-- where标签会在包含的内容存在时,在前面的sql后添加where关键字, 并过滤第一个子元素前的and/or -->
      <!-- if标签会判断test里的内容是否成立,成立则添加对应的sql-->
      <select id="getBlogByIf" resultType="blog" parameterType="map">
          SELECT * FROM mybatis.blog
          <where>
              <if test="title != null">
                  title = #{title}
              </if>
              <if test="author != null">
                  and author = #{author}
              </if>
          </where>
      </select>
      
    • 测试结果

      @Test
      public void getBlogIfTest(){
          SqlSession sqlSession = MybatisUtil.getSqlSession();
      
      
          BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
      
          HashMap<String, String> map = new HashMap<String, String>();
      
      
          /*
                  只传一个title参数时,打印的sql相关日志如下:
                  ==>  Preparing: SELECT * FROM mybatis.blog WHERE title = ?
                  ==> Parameters: java(String)
                  <==    Columns: id, title, author, create_time
                  <==        Row: 123456, java, zero, 2020-12-12
                  <==      Total: 1
               */
          map.put("title", "java");
      
      
          /*
                  只传一个author参数时,打印的sql相关日志如下:
                  ==>  Preparing: SELECT * FROM mybatis.blog WHERE author = ?
                  ==> Parameters: zero(String)
                  <==    Columns: id, title, author, create_time
                  <==        Row: 123450, spring, zero, 2020-12-16
                  <==        Row: 123456, java, zero, 2020-12-12
                  <==        Row: 123457, mysql, zero, 2020-12-13
                  <==        Row: 123458, java web, zero, 2020-12-14
                  <==        Row: 123459, mybatis, zero, 2020-12-15
                  <==      Total: 5
               */
          map.put("author", "zero");
      
      
          /*
                  传两个参数时,答应的sql相关日志如下:
                  ==>  Preparing: SELECT * FROM mybatis.blog WHERE title = ? and author = ?
                  ==> Parameters: java(String), zero(String)
                  <==    Columns: id, title, author, create_time
                  <==        Row: 123456, java, zero, 2020-12-12
                  <==      Total: 1
               */
      
          mapper.getBlogByIf(map);
      
          sqlSession.close();
      
      }
      
  • choose、when、otherwise标签

    ​ 这三个标签是一体的,相当于Java中的switch的功能,choose是外层标签,when是判断标签(类似于if),otherwise是没有成立的when标签时的默认选项。choose中的情况只能有一个成立,从上到下选择第一个成立的项。

    • 在dao层接口添加一个方法

          /**
           * 使用choose标签的动态查询接口
           * @param map choose判断的参数
           * @return 返回查询结果
           */
          Blog getBlogByChoose(Map map);    
      
    • 在配置文件中使用标签写sql语句

       <!-- 使用choose, when, otherwise标签 -->
          <select id="getBlogByChoose" parameterType="map" resultType="blog">
              SELECT * from mybatis.blog
              <where>
                  <choose>
                      <when test="title != null">
                          title = #{title}
                      </when>
                      <when test="author != null">
                          author = #{author}
                      </when>
                      <otherwise>
                          id = #{id}
                      </otherwise>
                  </choose>
              </where>
          </select>
      
    • 测试结果

      @Test
      public void getBlogChooseTest(){
          SqlSession sqlSession = MybatisUtil.getSqlSession();
      
      
          BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
      
          HashMap<String, String> map = new HashMap<String, String>();
      
      
      
          map.put("title", "java");
          map.put("author", "zero");
          map.put("id", "123450");
      
          /*
                  当三个参数都设置时,打印的sql日志如下:仅title参数有用
                  ==>  Preparing: SELECT * from mybatis.blog WHERE title = ?
                  ==> Parameters: java(String)
                  <==    Columns: id, title, author, create_time
                  <==        Row: 123456, java, zero, 2020-12-12
                  <==      Total: 1
                  Blog(id=123456, title=java, author=zero, date=null)
               */
      
      
          Blog blog = mapper.getBlogByChoose(map);
          System.out.println(blog);
      
          sqlSession.close();
      }
      
      
      
  • foreach标签

    ​ foreach标签用于从集合中遍历出每一个集合元素来拼接sql语句,常用于组合sql中的in关键字后集合,其使用如下:

    • 在dao层接口添加一个方法

      /**
      * 使用foreach标签的动态查询接口
      * @param map foreach遍历的参数map
      * @return 返回查询结果
      */
      List<Blog> getBlogByForeach(Map map);
      
    • 在配置文件中使用标签写sql语句

      <!--
          使用foreach标签,collection指参数中集合,item为每次遍历到的元素名称,open表示在遍历的结果之前添加的符号,close表示在遍历的结果之后添加的符号,separator表示遍历的每个元素之间的分隔符
      -->
      <select id="getBlogByForeach" parameterType="map" resultType="blog">
          select * FROM mybatis.blog
          <where>
              id IN
              <foreach collection="ids" item="id" open="(" close=")" separator=",">
                  #{id}
              </foreach>
          </where>
      </select>
      
    • 测试结果

      @Test
      public void getBlogForeachTest(){
          SqlSession sqlSession = MybatisUtil.getSqlSession();
      
      
          BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
      
          HashMap<String, List<String>> map = new HashMap<String, List<String>>();
      
          //添加集合信息
          ArrayList<String> ids = new ArrayList<String>();
          ids.add("123456");
          ids.add("123457");
          ids.add("123458");
      
          //将集合放置到map中,map的健和配置文件中foreach中的集合参数一致
          map.put("ids", ids);
      
          /*
                  打印的sql日志:
                  ==>  Preparing: select * FROM mybatis.blog WHERE id IN ( ? , ? , ? )
                  ==> Parameters: 123456(String), 123457(String), 123458(String)
                  <==    Columns: id, title, author, create_time
                  <==        Row: 123456, java, zero, 2020-12-12
                  <==        Row: 123457, mysql, zero, 2020-12-13
                  <==        Row: 123458, java web, zero, 2020-12-14
                  <==      Total: 3
                  Blog(id=123456, title=java, author=zero, date=null)
                  Blog(id=123457, title=mysql, author=zero, date=null)
                  Blog(id=123458, title=java web, author=zero, date=null)
          */
          List<Blog> blogs = mapper.getBlogByForeach(map);
      
          for (Blog blog : blogs) {
              System.out.println(blog);
          }
          
          sqlSession.close();
      }
      
      

8、缓存

​ mybatis的缓存,指将之前查询到的数据存放在内存中,下一次查询同样的数据时,就不再需要去数据库中查找,而是直接在内存中查找即可。其缓存等级分为一级和二级。

8.1、一级缓存

​ 基于sqlSession连接的缓存,当sqlSession对象还没关闭时,缓存存在;当sqlSession对象关闭时消失。一级缓存是默认存在的,不需要进行设置。

测试验证,基于上面的Blog类和blog表进行测试

  • 增加dao层接口方法

        /**
         * 根据id查找博客
         * @param id 查找的id
         * @return 返回查询结果
         */
        Blog getBlogById(@Param("id") String id);
    
  • 配置sql查询语句

    <!-- 根据id查找博客 -->
    <select id="getBlogById" resultType="blog" parameterType="string">
        select * from mybatis.blog WHERE id = #{id}
    </select>
    

测试1:测试同一个sqlSession对象的情况

@Test
public void getBlogByIdTest(){
    SqlSession sqlSession1 = MybatisUtil.getSqlSession();

    //获得SqlSession对象
    BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);

    //使用该对象进行一次查询
    Blog blog1 = mapper1.getBlogById("123456");

    System.out.println("============================");

    //使用该对象进行第二次查询
    Blog blog2 = mapper1.getBlogById("123456");

    //判断查询结果
    System.out.println("blog1 == blog2 :" + (blog1 == blog2));


    sqlSession1.close();
}

日志结果:

Opening JDBC Connection
Created connection 1205406622.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
==>  Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<==    Columns: id, title, author, create_time
<==        Row: 123456, java, zero, 2020-12-12
<==      Total: 1
============================
blog1 == blog2 :true
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Returned connection 1205406622 to pool.

分析:

只进行了一次sql查询,且两次的的查询结果对象相等,说明Sqlsession对象是有缓存的。

测试2:测试不同的sqlSession对象的情况

@Test
public void getBlogByIdTest(){
    //获得SqlSession对象
    SqlSession sqlSession1 = MybatisUtil.getSqlSession();
    SqlSession sqlSession2 = MybatisUtil.getSqlSession();

    //使用不同的sqlSession对象获得实现类对象
    BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);
    BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);

    //使用mapper1对象进行一次查询
    Blog blog1 = mapper1.getBlogById("123456");

    System.out.println("============================");

    //使用mapper2对象进行第二次查询
    Blog blog2 = mapper2.getBlogById("123456");

    //判断查询结果
    System.out.println("blog1 == blog2 :" + (blog1 == blog2));


    sqlSession1.close();
    sqlSession2.close();
}

日志结果:

Opening JDBC Connection
Created connection 1205406622.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
==>  Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<==    Columns: id, title, author, create_time
<==        Row: 123456, java, zero, 2020-12-12
<==      Total: 1
============================
Opening JDBC Connection
Created connection 904861801.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
==>  Preparing: select * from mybatis.blog WHERE id = ?
==> Parameters: 123456(String)
<==    Columns: id, title, author, create_time
<==        Row: 123456, java, zero, 2020-12-12
<==      Total: 1
blog1 == blog2 :false
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47d90b9e]
Returned connection 1205406622 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@35ef1869]
Returned connection 904861801 to pool.

分析:

进行了两次sql查询,且两次的结果对象不同,说明一级查询仅存在于SqlSession对象中。

影响缓存的刷新的因素

  1. 手动刷新

    sqlSession对象.clearCache();
    
  2. 执行update、insert、delete操作

  3. 关闭SqlSession对象

8.2、二级缓存

​ 一级缓存仅存在于同一个SqlSession中,对于不同的SqlSession是不能共享的,这对于非线程安全的SqlSession来说意义不是很大(因为每次用完都应该关闭,生命周期应该在局部变量中)。这样子就诞生了二级缓存。二级缓存是基于整个mapper配置的namespace作用域的。

工作原理

在这里插入图片描述

一个Mapper会有自己的多个SqlSession对象,SqlSeesion对象查询数据时,会自动将数据存到自己的一级缓存,以便下次查找时避免去数据库查找,当多个SqlSession对象并不共享彼此的一级缓存。当开启二级缓存时,当SqlSession关闭时,会将一级缓存的数据放到二级缓存中,这样所有的SqlSession就能共享基于Mapper的数据。

使用方式

  1. 核心配置文件启动缓存设置

    <settings>
        <!-- 开启二级缓存 -->
        <setting name="cacheEnabled " value="true"/>
    </settings>
    
  2. 在mapper配置文件中开启缓存

    <!-- eviction表示缓存策略,flushInterval表示刷新周期,size表示缓存数量,readOnly表示只读 -->
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    

注意:SqlSession未关闭时,不会将数据放置到二级缓存

数据的读取顺序

  1. 先从二级缓存看是否有数据,有则取出,没有进行下一步
  2. 再从一级缓存看是否有数据,有则取出,没有进行下一步
  3. 执行sql,从数据库中获得数据并返回,顺便存到一级缓存

新手上路,恐有错漏,望客官看看即可,还得参照各路大神博文,以免被我误入歧途。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值