MyBatis入门笔记

Mybatis简介

1.1 MyBatis历史

  • MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github
  • iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

1.2 MyBatis特性

  1. MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
  2. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  4. MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架

1.3 MyBatis下载

  • MyBatis下载地址
  • MyBatis入门笔记_第1张图片

MyBatis官方中文文档

1.4 和其它持久化层技术对比

  • JDBC

    • SQL语句 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA

    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难
    • 反射操作太多,导致数据库性能下降
  • MyBatis

    • 轻量级,性能出色 ,半自动的ORM框架

    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

    • 开发效率稍逊于HIbernate,但是完全能够接受

2. 搭建MyBatis

2.1 搭建开发环境

  • IDE:idea 2022.3.3
  • 构建工具:maven 3.8.8
  • MySQL版本:MySQL 5.7.30
  • MyBatis版本:MyBatis 3.5.7
  • JDK版本:1.8.0_271

在idea新建MyBatisDemo项目,再新建module名为MyBatis_Demo1,项目目录如图:

MyBatis入门笔记_第2张图片

2.2 配置数据库

MySQL数据库脚本如下:

drop database if exists mybatis_demo;
create database mybatis_demo;
use mybatis_demo;
create table t_user(
	`id` int primary key auto_increment,
    `username` varchar(20),
    `password` varchar(20),
    `gender` char,
    `age` int,
    `email` varchar(25)
)ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; 
  
create table t_department(
	`did` int primary key,
    `dname` varchar(20) 
)ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;  
    
create table t_emp(
	`id` int primary key auto_increment,
	`emp_name` varchar(20),
    `password` varchar(20),
    `gender` char,
    `age` int,
    `email` varchar(25),
    `did` int,
	constraint fk_employee_department foreign key(`did`) references t_department(`did`)
)ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

insert t_user values(null,'张三','123456','男',22,'[email protected]');
insert into t_department values(1,"开发部");
insert into t_department values(2,"测试部");
insert into t_department values(3,"运维部");
insert into t_emp values (null, "张三", "aaa", "男", 22, "[email protected]", 1);
insert into t_emp values (null, "李四", "bbb", "女", 22, "[email protected]", 2);
insert into t_emp values (null, "王五", "ccc", "男", 22, "[email protected]", 3);
insert into t_emp values (null, "赵六", "ddd", "女", 22, "[email protected]", 1);
insert into t_emp values (null, "田七", "eee", "男", 22, "[email protected]", 2);

2.3 创建maven工程

在MyBatis—Demo1下的pom.xml文件添加依赖

  • 打包方式:jar
  • 引入依赖

<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.0modelVersion>
    <parent>
        <groupId>com.atguigugroupId>
        <artifactId>MyBatisDemoartifactId>
        <version>1.0-SNAPSHOTversion>

    parent>

    <artifactId>MyBatis_Demo1artifactId>

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

    
    <packaging>jarpackaging>

    
    <dependencies>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.7version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.2.5version>
        dependency>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.12version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.17version>
        dependency>
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.4version>
            <scope>providedscope>
        dependency>
        
        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>
    dependencies>
project>

2.4 加入log4j日志功能

  1. 在pom.xml加入依赖

    
    <dependency>
        <groupId>log4jgroupId>
        <artifactId>log4jartifactId>
        <version>1.2.17version>
    dependency>
    
  2. 加入log4j的配置文件

    • log4j的配置文件名为log4j.xml,存放的位置是src/main/resources目录下
    • 日志的级别:FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试) 从左到右打印的内容越来越详细
    
    DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
        <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
            <param name="Encoding" value="UTF-8" />
            <layout class="org.apache.log4j.PatternLayout">
    			<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
            layout>
        appender>
        <logger name="java.sql">
            <level value="debug" />
        logger>
        <logger name="org.apache.ibatis">
            <level value="info" />
        logger>
        <root>
            <level value="debug" />
            <appender-ref ref="STDOUT" />
        root>
    log4j:configuration>
    

2.5 创建MyBatis的核心配置文件

在src/main/resources目录下新建 datasorce.properties 配置文件

# jdbc数据源
jdbc.username = root
jdbc.password = 360421
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis_demo?useUnicode = true&characterEncoding = UTF-8&serverTimezone = UTC

# druid数据源
# 驱动名称
druid.driverClassName = com.mysql.cj.jdbc.Driver
# 连接数据库的URL
druid.url = jdbc:mysql://localhost:3306/mybatis_demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
# mysql的用户与密码
druid.username = root
druid.password = 360421
# 初始化时的物理连接的个数
druid.initialSize = 5
# 最大连接池数量
druid.maxActive = 10
# 获取连接时最大等待时间
druid.maxWait = 3000

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。
核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目录下


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    
    <properties resource="datasource.properties"/>

    
    <environments default="development">
        <environment id="development">
            
            <transactionManager type="JDBC"/>
            
            
            
            
            
            
            
            <dataSource type="com.atguigu.util.DruidDataSourceFactory">
                
                <property name="driverClassName" value="${druid.driverClassName}"/>
                
                <property name="url" value="${druid.url}"/>
                
                <property name="username" value="${druid.username}"/>
                
                <property name="password" value="${druid.password}"/>
                
                <property name="initialSize" value="${druid.initialSize}"/>
                
                <property name="maxActive" value="${druid.maxActive}"/>
                
                <property name="maxWait" value="${druid.maxWait}"/>
            dataSource>

        environment>
    environments>
    
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    mappers>
configuration>

2.6 创建实体类及其mapper接口

在pojo包下创建User实体类:

package com.atguigu.pojo;

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

/**
 * @author : Sakura
 * @className : User
 * @description : User实体类
 * @createTime : 2023-08-08  16:12:18
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {

    private Integer id;

    private String username;

    private String password;

    private Integer age;

    private String email;

    private String gender;

}

在util包下创建DruidDataSourceFactory类,用于配置Druid数据源:

package com.atguigu.util;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;

public class DruidDataSourceFactory extends PooledDataSourceFactory {
    public DruidDataSourceFactory(){
        this.dataSource = new DruidDataSource();
    }
}

在mapper包下创建UserMapper类

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类

package com.atguigu.mapper;

import com.atguigu.pojo.User;

import java.util.List;

/**
 * @author : Sakura
 * @className : UserMapper
 * @description : TODO
 * @createTime : 2023-08-08  16:18:35
 */
public interface UserMapper {

    /**
     * 添加用户信息
     * @return
     */
    int insertUser(User user);

    /**
     * 根据id查找用户
     * @param id
     * @return
     */
    User getUserById(Integer id);

    /**
     * 获取User列表
     * @return
     */
    List<User> getUserList();

    /**
     * 修改用户信息
     * @param user
     * @return
     */
     int updateUser(User user);

    /**
     * 根据id删除用户
     * @param id
     * @return
     */
    int deleteUserById(Integer id);
}

2.7 创建MyBatis的映射文件

  • 相关概念:ORM(Object Relationship Mapping)对象关系映射。
    • 对象:Java的实体类对象
    • 关系:关系型数据库
    • 映射:二者之间的对应关系
Java概念 数据库概念
属性 字段/列
对象 记录/行
  • 映射文件的命名规则
    • 表所对应的实体类的类名+Mapper.xml
    • 例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
    • 因此一个映射文件对应一个实体类,对应一张表的操作
    • MyBatis映射文件用于编写SQL,访问以及操作表中的数据
    • MyBatis映射文件存放的位置是src/main/resources/mappers目录下
  • MyBatis中可以面向接口操作数据,要保证两个一致
    • mapper接口的全类名和映射文件的命名空间(namespace)保持一致
    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mapper.UserMapper">
    
    <insert id="insertUser">
        insert into t_user
        values (#{id}, #{username}, #{password}, #{gender}, #{age}, #{email})
    insert>
    
    
    
    <select id="getUserById" resultType="com.atguigu.pojo.User">
        select * from t_user
        where id = #{id}
    select>

    <resultMap id="userResultMap" type="com.atguigu.pojo.User">
        <result column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
        <result column="gender" property="gender" />
        <result column="age" property="age" />
        <result column="email" property="email" />
    resultMap>

    
    <select id="getUserList" resultMap="userResultMap">
        select * from t_user
    select>

    
    <update id="updateUser">
        update t_user
        set username = #{username}, password = #{password}, gender = #{gender}, age = #{age}, email = #{email}
        where id = #{id}
    update>

    
    <delete id="deleteUserById" >
        delete from t_user
        where id = #{id}
    delete>
mapper>

2.8 通过junit测试功能

  • SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”
  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象
import com.atguigu.mapper.UserMapper;
import com.atguigu.pojo.User;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @author : Sakura
 * @className : UserMapperTest
 * @description : TODO
 * @createTime : 2023-08-08  16:23:30
 */
public class UserMapperTest {
    /**
     * 此时需要手动提交事务,如果要自动提交事务,则在获取sqlSession对象时,使用`SqlSession sqlSession = sqlSessionFactory.openSession(true);`,传入一个Boolean类型的参数,值为true,这样就可以自动提交
     * @throws IOException
     */
    @Test
    public void testInsertUser() throws IOException {
        User user = new User(null,"李四","1234",16,"[email protected]","女");
        // 读取MyBatis的核心配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 获取SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        // 获取sqlSession,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
        // 通过openSession(true)方法创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        //
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配映射文件中的SQL标签,并执行标签中的SQL语句
        int i = userMapper.insertUser(user);
        // 提交事务
        // sqlSession.commit();
        System.out.println("i = " + i);
    }

    /**
     * 获取UserMapper实例
     * @return
     */
    public UserMapper getUserMapper() throws IOException {
        // 创建SqlSessionFactory实例
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 创建SqlSession实例,并设置自动提交
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 通过代理模式创建UserMapper接口的代理实现类对象
        return sqlSession.getMapper(UserMapper.class);
    }

    /**
     * 根据id查找用户
     * @throws IOException
     */
    @Test
    public void testGetUserById() throws IOException {
        UserMapper userMapper = getUserMapper();
        User user = userMapper.getUserById(1);
        System.out.println(user);
    }

    /**
     * 修改用户信息
     * @throws IOException
     */
    @Test
    public void testUpdateUser() throws IOException {
        UserMapper userMapper = getUserMapper();
        User user = userMapper.getUserById(1);
        System.out.println(user);
        user.setUsername("Sakura");
        System.out.println(user);
    }

    /**
     * 获取User列表
     * @throws IOException
     */
    @Test
    public void testGetUserList() throws IOException {
        UserMapper userMapper = getUserMapper();
        List<User> userList = userMapper.getUserList();
        System.out.println(userList);
    }

    /**
     * 根据id删除用户
     * @throws IOException
     */
    @Test
    public void testDeleteUserById() throws IOException {
        UserMapper userMapper = getUserMapper();
        int i = userMapper.deleteUserById(4);
        System.out.println(i);
    }

}

  • 此时需要手动提交事务,如果要自动提交事务,则在获取sqlSession对象时,使用SqlSession sqlSession = sqlSessionFactory.openSession(true);,传入一个Boolean类型的参数,值为true,这样就可以自动提交

3. 核心配置文件详解

点击查看官方中文文档

核心配置文件中的标签必须按照固定的顺序(有的标签可以不写,但顺序一定不能乱):
propertiessettingstypeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers


DOCTYPE configuration
        PUBLIC "-//MyBatis.org//DTD Config 3.0//EN"
        "http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration>
    
    <properties resource="jdbc.properties"/>
    <settings>
        
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        
        <setting name="lazyLoadingEnabled" value="true"/>
    settings>
    <typeAliases>
        
        
        
        
        <package name="com.atguigu.mybatis.bean"/>
    typeAliases>
    
    <environments default="mysql_test">
        
        <environment id="mysql_test">
            
            <transactionManager type="JDBC"/>
            
             
            <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>
    
    <mappers>
        
        
        <package name="com.atguigu.mybatis.mapper"/>
    mappers>
configuration>

使用typeAlisaes标签设置类的别名:

	<typeAliases>
        
        
        
        
        <package name="com.atguigu.mybatis.bean"/>
    typeAliases>

使用alias属性设置别名时,别名不区分大小写。若不设置此属性,该类型拥有默认的别名,即类名。

以下为常见数据类型的别名:

基本数据类型: _基本数据类型

引用数据类型: 引用数据类型全小写

!](https://img-blog.csdnimg.cn/48565f78410c4e73b91763411df7f378.png)

MyBatis入门笔记_第3张图片

使用mappers标签实现mapper接口和mapper映射文件的映射,用法如下:

    
    <mappers>
        
        
        <package name="com.atguigu.mybatis.mapper"/>
    mappers>

以包为单位,将包下所有的映射文件引入核心配置文件时,需要注意:

  • 此方式必须保证mapper接口和mapper映射文件必须在相同的包下
  • mapper接口要和mapper映射文件的名字一致

MyBatis入门笔记_第4张图片

4. 配置mybatis模板和封装SqlSessionUtil工具

4.1 配置mybatis模板

File——>Settings——>Editor——>File and Code Templates 新建mybatis-config和mybatis-mapper模板文件,其格式如下

mybatis-config.xml


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    
    <properties resource="jdbc.properties"/>
       
       <settings>
        
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        
        <setting name="lazyLoadingEnabled" value="true"/>
         
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
 
    
    <typeAliases>
        
        
        
        <package name="com.xxx.entity"/>
    typeAliases>
 
    
    <environments default="company">
        
        <environment id="company">
            
            <transactionManager type="JDBC"/>
            
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            dataSource>
        environment>
    environments>
 
    
    <mappers>
        
        
        <package name="mappers"/>
    mappers>
 
configuration>

以包为单位,将包下所有的映射文件引入核心配置文件时需要注意:

  1. 此方式必须保证mapper接口和mapper映射文件必须在相同的包下
  2. mapper接口要和mapper映射文件的名字一致

mybatis-mapper.xml


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">

mapper>

MyBatis入门笔记_第5张图片

4.2 封装SqlSessionUtil工具

为了之后测试方便,可以借助自定义的SqlSessionUtil工具类简化测试:

package com.atguigu.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;

public class SqlSessionUtil {
    private static SqlSessionFactory factory;
    private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();
    static {
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            factory = builder.build(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static SqlSession getSqlSession(boolean isAutoCommit) {
        SqlSession sqlSession = local.get();
        if (sqlSession == null){
            sqlSession = factory.openSession(isAutoCommit);
            local.set(sqlSession);
        }
        return sqlSession;
    }

    public static SqlSession getSqlSession(){
        return getSqlSession(false);
    }

    public static <T extends Object>T getMapper(Class<T> c){
        SqlSession sqlSession = getSqlSession(true);
        return sqlSession.getMapper(c);
    }
}

4. MyBatis获取参数值的两种方式

  • MyBatis获取参数值的两种方式:${}#{}
  • ${}的本质就是字符串拼接,#{}的本质就是占位符赋值
  • ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
  • 相较于JDBC的SQL拼接而言,减少了代码冗余;
  • 简单理解,不涉及字符串用#{},涉及字符串拼接用${}

4.1 单个字面量类型的参数

  • 若mapper接口中的方法参数为单个的字面量类型,此时可以使用${}和#{}以任意的名称(最好见名识意)获取参数的值,注意${}需要手动加单引号
    
	<select id="getUserByName" resultType="com.atguigu.pojo.User">
        select * from t_user where username = #{username}
	select>
    
    <select id="getUserByName" resultType="com.atguigu.pojo.User">
        select * from t_user
        where username = '${username}'
    select>

4.2 多个字面量类型的参数

  • 若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中

    1. 以arg0,arg1…为键,以参数为值;
    2. 以param1,param2…为键,以参数为值;
  • 因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号。

  • 使用arg或者param都行,要注意的是,arg是从arg0开始的,param是从param1开始的

    
    <select id="getUserListByPage" resultType="com.atguigu.pojo.User">
        select * from t_user
        limit #{arg0}, #{arg1}
    select>
    
    <select id="getUserListByPage" resultType="com.atguigu.pojo.User">
        select * from t_user
        limit #{param1}, #{param2}
    select>

4.3 map集合类型的参数

  • 若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
    
    <select id="getUserListByPage" resultType="com.atguigu.pojo.User">
        select * from t_user
        limit #{start}, #{pageSize}
    select>
    /**
     * 获取User列表分页
     *
     * @throws IOException
     */
    @Test
    public void testGetUserListByPage() throws IOException {
        UserMapper userMapper = SqlSessionUtil.getMapper(UserMapper.class);
        HashMap<String,Integer> map = new HashMap<>();
        map.put("start",0);
        map.put("pageSize",2);
        List<User> userList = userMapper.getUserListByPage(map);
        System.out.println(userList);
    }

4.4 实体类类型的参数

  • 若mapper接口中的方法参数为实体类对象时此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号
    
    <insert id="insertUser">
        insert into t_user
        values (#{id}, #{username}, #{password}, #{gender}, #{age}, #{email})
    insert>
@Test
public void insertUser() {
	SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    UserMapper userMapper = SqlSessionUtil.getMapper(UserMapper.class);
    User user = new User(null, "李四", "1234", 16, "[email protected]", "女");

	userMapper.insertUser(user);
}

4.5 使用@Param标识参数

  • 可以通过@Param注解标识mapper接口中的方法参数,此时,会将这些参数放在map集合中

    1. 以@Param注解的value属性值为键,以参数为值;
    2. 以param1,param2…为键,以参数为值;
  • 只需要通过${}和#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

    
    <select id="getUserListByPage" resultType="com.atguigu.pojo.User">
        select * from t_user
        limit #{start}, #{pageSize}
    select>
    /**
     * 获取User列表分页
     *
     * @throws IOException
     */
    @Test
    public void testGetUserListByPage() throws IOException {
        UserMapper userMapper = SqlSessionUtil.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserListByPage(0, 2);
        System.out.println(userList);
    }

4.6 @Param获取参数值源码分析

通过debug断点调试,逐步渗透分析到ParamNameResolver.java

MyBatis入门笔记_第6张图片

MyBatis入门笔记_第7张图片

显然,使用@Param标识参数以获取参数值,也是在底层通过自定义封装为map形式的参数。

  • 建议分成两种情况进行处理

    1. 实体类类型的参数
    2. 使用@Param标识参数

5. MyBatis的各种查询功能

  1. 如果查询出的数据只有一条,可以通过
    1. 实体类对象接收
    2. List集合接收
    3. Map集合接收,结果{password=123456, sex=男, id=1, age=23, username=admin}
  2. 如果查询出的数据有多条,一定不能用实体类对象接收,会抛异常TooManyResultsException,可以通过
    1. 实体类类型的LIst集合接收
    2. Map类型的LIst集合接收
    3. 在mapper接口的方法上添加@MapKey注解

5.1 查询单个实体类对象

/**
 * 根据用户id查询用户信息
 * @param id
 * @return
 */
User getUserById(@Param("id") int id);

<select id="getUserById" resultType="User">
	select * from t_user where id = #{id}
select>

注意:

  • 查询的数据只有一条,实体类和集合接收都可以;
  • 查询的数据有多条,只能使用集合接收,否则报错

MyBatis入门笔记_第8张图片

5.2 查询实体类对象集合

/**
 * 查询所有用户信息
 * @return
 */
List<User> getUserList();

<select id="getUserList" resultType="User">
	select * from t_user
select>

5.3 查询单个数据

/**  
 * 查询用户的总记录数  
 * @return  
 * 在MyBatis中,对于Java中常用的类型都设置了类型别名  
 * 例如:java.lang.Integer-->int|integer  
 * 例如:int-->_int|_integer  
 * 例如:Map-->map,List-->list  
 */  
int getCount();
	
    
    
    <select id="getCount" resultType="integer">
        select count(*) from t_user;
    select>

5.4 查询一条数据为map集合

    /**
     * 根据id查询用户信息,封装为map集合
     */
    Map<String,Object> getUserByIdToMap(@Param("id") Integer id);

    <select id="getUserByIdToMap" resultType="map">
        select * from t_user
        where id = #{id}
    select>

{password=123456, gender=男, id=1, age=22, [email protected], username=张三}

MyBatis会将查询到的数据按照key-value转为map集合,其中key为属性名,value=字段值

5.5 查询多条数据为map集合

5.5.1 方法一

/**  
 * 查询所有用户信息为map集合  
 * @return  
 * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此时可以将这些map放在一个list集合中获取  
 */  
    List<Map<String,Object>> getUserListToMap();
  
<select id="getAllUserToMap" resultType="map">  
	select * from t_user  
select>

样例结果:

[{password=123456, gender=, id=1, age=22, email=zhangsang@168.com, username=张三}, {password=1234, gender=, id=2, age=16, email=12468965@qq.com, username=李四1}, {password=1234, gender=, id=3, age=16, email=12468965@qq.com, username=李四2}, {password=1234, gender=, id=5, age=16, email=12468965@qq.com, username=李四4}, {password=1234, gender=, id=6, age=16, email=12468965@qq.com, username=李四5}]

5.5.2 方法二

    /**
     * 查询所有的用户信息,封装为map集合
     * @MapKey("id") 表示以id为key,id对应的user信息为value
     * 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,
     * 并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的map集合
     */
    @MapKey("id")
    Map<String,Object> getUserListToMap2();
	
    <select id="getUserListToMap2" resultType="java.util.Map">
        select * from t_user
    select>

样例结果:

{
    1={password=123456, gender=, id=1, age=22, email=zhangsang@168.com, username=张三},
	2={password=1234, gender=, id=2, age=16, email=12468965@qq.com, username=李四1}, 
	3={password=1234, gender=, id=3, age=16, email=12468965@qq.com, username=李四2}, 
	5={password=1234, gender=, id=5, age=16, email=12468965@qq.com, username=李四4}, 
	6={password=1234, gender=, id=6, age=16, email=12468965@qq.com, username=李四5}
}

6. 特殊SQL的执行

6.1 模糊查询

/**
 * 根据用户名进行模糊查询
 */
List<User> getUserByLike(@Param("username") String username);

<select id="getUserByLike" resultType="User">
	  
	  
	select * from t_user where username like "%"#{mohu}"%"
select>
  • 其中select * from t_user where username like "%"#{mohu}"%"是最常用的

6.2 批量删除

方式一:

  • 只能使用${},如果使用#{},则解析后的sql语句为delete from t_user where id in ('1,2,3'),这样是将1,2,3看做是一个整体,只有id为1,2,3的数据会被删除。正确的语句应该是delete from t_user where id in (1,2,3),或者delete from t_user where id in ('1','2','3')
    /**
     *  根据id批量删除
     * @param ids   形如 1,2
     * @return
     */
    int deleteMore(@Param("ids") String ids);
<delete id="deleteMore">
	delete from t_user where id in (${ids})
delete>
    @Test
    public void testDeleteMore(){
        SQLMapper mapper = SqlSessionUtil.getMapper(SQLMapper.class);
        int i = mapper.deleteMore("10,11");
        System.out.println(i);
    }

方式二:

采用List传递待删除的数据的id,借助foreach实现批量删除的SQL语句

    /**
     *  根据存储待删除id的list批量删除
     * @param ids
     * @return
     */
    int deleteMore2(@Param("ids") List<Integer> ids);
    <delete id="deleteMore2">
        delete from t_user
        where  id in
        <foreach collection="ids" item="i" separator="," open="(" close=")">
            #{i}
        foreach>
    delete>
    @Test
    public void testDeleteMore2(){
        SQLMapper mapper = SqlSessionUtil.getMapper(SQLMapper.class);
        int i = mapper.deleteMore2(Arrays.asList(9,12,13));
        System.out.println(i);
    }

6.3 动态设置表名

  • 只能使用${},因为表名不能加单引号
/**
 * 查询指定表中的数据
 * @param tableName 
 * @return java.util.List
 * @date 2022/2/27 14:41
 */
List<User> getUserByTable(@Param("tableName") String tableName);

<select id="getUserByTable" resultType="User">
	select * from ${tableName}
select>

6.4 添加功能获取自增的主键

  • 使用场景

    • t_clazz(clazz_id,clazz_name)
    • t_student(student_id,student_name,clazz_id)
    1. 添加班级信息
    2. 获取新添加的班级的id
    3. 为班级分配学生,即将某学的班级id修改为新添加的班级的id
  • 在mapper.xml中设置两个属性

  • useGeneratedKeys:设置使用自增的主键

  • keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中

    /**
     * 添加用户信息
     *
     * @return
     */
    int insertUser(SelectUser selectUser);
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
        insert into t_user
        values (#{id}, #{username}, #{password}, #{gender}, #{age}, #{email})
    insert>
    @Test
    public void testInsertUser(){
        SQLMapper mapper = SqlSessionUtil.getMapper(SQLMapper.class);
        SelectUser selectUser = new SelectUser(null, "李四", "1234", 16, "[email protected]", "女");
        mapper.insertUser(selectUser);
        System.out.println(selectUser);
    }

输出:

SelectUser(id=14, username=李四, password=1234, age=16, [email protected], gender=女)

可以看到,执行添加SQL后,实体类的id被赋值为添加的数据的主键

7. 自定义映射resultMap

现在面临这样一个问题,实体类的empName属性对应着关系表中的emp_name字段,但直接返回时empName属性不会被emp_name字段正确赋值,出现了 empName=null 的情况。针对这个问题:

方式一:对emp_name字段起别名

    <select id="getAllEmp" resultType="com.atguigu.pojo.Emp">
        select eid, emp_name as empName, password, gender, age, email, did
        from t_emp
    select>

方式二:在mybatis配置文件中开启 驼峰命名自动映射mapUnderscoreToCamelCase

在mybatis-config.xml配置文件中添加如下代码:

    
    <settings>
        
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    settings>

这种方法适用情形:字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰)

接下来介绍第三种方法,自定义映射resultMap来解决上述实体类的属性与关系表的字段名不匹配问题。

7.1 resultMap处理字段和属性的映射关系

  • resultMap:设置自定义映射

    • 属性:
      • id:表示自定义映射的唯一标识,不能重复
    • type:查询的数据要映射的实体类的类型

    resultMap子标签:

    • id:设置主键的映射关系
    • result:设置普通字段的映射关系
      - 子标签属性:
      • property:设置映射关系中实体类中的属性名
      • column:设置映射关系中表中的字段名
  • 若字段名和实体类中的属性名不一致,则可以通过resultMap设置自定义映射,即使字段名和属性名一致的属性也要映射,也就是全部属性都要列出来

    
    <resultMap id="empResultMap" type="Emp">
        
        <id property="eid" column="eid"/>
        
        <result property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="empName" column="emp_name"/>
        <result property="gender" column="gender" />
        <result property="password" column="password"/>
    resultMap>

    
    <select id="getAllEmp" resultMap="empResultMap">
        select eid, emp_name , password, gender, age, email, did
        from t_emp
    select>

7.2 多对一映射处理

查询员工信息以及员工所对应的部门信息

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Emp {

    private Integer eid;

    private String empName;

    private String password;

    private String gender;

    private Integer age;

    private String email;

    private Dept dept;
}

7.2.1 级联方式处理映射关系

    
    <resultMap id="empAndDeptResultMap" type="Emp">
        
        <id property="eid" column="eid"/>
        
        <result property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="empName" column="emp_name"/>
        <result property="gender" column="gender"/>
        <result property="password" column="password"/>
        <result property="dept.did" column="did" />
        <result property="dept.deptName" column="dname"/>
    resultMap>

    
    <select id="getAllEmpAndDept" resultMap="empAndDeptResultMap">
        select eid, emp_name, password, gender, age, email, e.did as did, dname
        from t_emp as e
        inner join t_department as d  on d.did = e.did
    select>

内连接、等值连接、左连接和右连接的区别

7.2.2 使用association处理多对一映射关系

  • association:处理多对一的映射关系
    • property:需要处理多对的映射关系的属性名
    • javaType:该属性的类型
    
    <resultMap id="empAndDeptResultMap2" type="Emp">
        
        <id property="eid" column="eid"/>
        
        <result property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="empName" column="emp_name"/>
        <result property="gender" column="gender"/>
        <result property="password" column="password"/>
        
        <association property="dept" javaType="Dept">
            <id property="did" column="did"/>
            <result property="deptName" column="dname"/>
        association>
    resultMap>

    
    <select id="getAllEmpAndDept2" resultMap="empAndDeptResultMap2">
        select eid, emp_name, password, gender, age, email, e.did as did, dname
        from t_emp as e
        inner join t_department as d  on d.did = e.did
    select>

7.2.3 使用association分步查询

  1. 查询员工信息
  • select:设置分布查询的sql的唯一标识(namespace.SQLId或mapper接口的全类名.方法名),例如: select=“com.atguigu.mapper.DeptMapper.getDeptById”
  • column:设置分步查询的条件(分步查询的参数)
//EmpMapper
/**
 * 通过分步查询,员工及所对应的部门信息
 * 分步查询第一步:查询员工信息
 * @param  
 * @return com.atguigu.pojo.Emp
 * @date 2022/2/27 20:17
 */
List<Emp> getAllEmpAndDept3();
    
    <resultMap id="empAndDeptResultMap3" type="Emp">
        
        <id property="eid" column="eid"/>
        
        <result property="age" column="age"/>
        <result property="email" column="email"/>
        <result property="empName" column="emp_name"/>
        <result property="gender" column="gender"/>
        <result property="password" column="password"/>
        <association property="dept" select="com.atguigu.mapper.DeptMapper.getDeptById" column="did"/>
    resultMap>
    
    
    <select id="getAllEmpAndDept3" resultMap="empAndDeptResultMap3">
        select eid, emp_name, password, gender, age, email, did
        from t_emp
    select>
  1. 查询部门信息
//DeptMapper里的方法
/**
 * 通过分步查询,员工及所对应的部门信息
 * 分步查询第二步:通过did查询员工对应的部门信息
 * @param
 * @return com.atguigu.pojo.Emp
 * @date 2022/2/27 20:23
 */
 Dept getDeptById(@Param("did") Integer did);
	
    <resultMap id="deptResultMap" type="Dept">
        <id property="did" column="did"/>
        <result property="deptName" column="dname"/>
    resultMap>

    <select id="getDeptById" resultMap="deptResultMap">
        select did, dname
        from t_department
        where did = #{did}
    select>

7.3 一对多映射处理

去掉 Emp实体类中的dept属性,在Dept实体类中增加属性

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Dept {
    private Integer did;

    private String deptName;
    
    private List<Emp> empList;
}

7.3.1 使用collection处理一对多映射关系

  • collection:用来处理一对多的映射关系
  • ofType:表示该属性对饮的集合中存储的数据的类型
    <resultMap id="deptAndEmpResultMap" type="Dept">
        <id property="did" column="did"/>
        <result property="deptName" column="dname"/>
        
        <collection property="empList" ofType="Emp">
            
            <id property="eid" column="eid"/>
            
            <result property="age" column="age"/>
            <result property="email" column="email"/>
            <result property="empName" column="emp_name"/>
            <result property="gender" column="gender"/>
            <result property="password" column="password"/>
        collection>
    resultMap>

    <select id="getDeptList" resultMap="deptAndEmpResultMap">
        select e.did, dname, eid, age, email, emp_name, gender, password
        from t_department d
        left outer join t_emp e on d.did = e.did
    select>

7.3.2 分步查询

  1. 查询部门信息
/**
 * 通过分步查询,查询部门及对应的所有员工信息
 * 分步查询第一步:查询部门信息
 * @param did 
 * @return com.atguigu.pojo.Dept
 */
List<Dept> getDeptList2();
    <resultMap id="deptAndEmpResultMap2" type="Dept">
        <id property="did" column="did"/>
        <result property="deptName" column="dname"/>
        
        <association property="empList" select="com.atguigu.mapper.EmpMapper.getEmpListByDid" column="did"/>
    resultMap>

    <select id="getDeptList2" resultMap="deptAndEmpResultMap2">
        select did, dname
        from t_department
    select>
  1. 根据部门id查询部门中的所有员工
    /**
     * 通过分步查询,查询部门及对应的所有员工信息
     * 分步查询第二步:根据部门id查询部门中的所有员工
     * @param did
     */
    List<Emp> getEmpListByDid(@Param("did")Integer did);
    
    <select id="getEmpListByDid"  resultMap="empResultMap">
        select eid, emp_name, password, gender, age, email, did
        from t_emp
        where did = #{did}
    select>

7.4 浅谈延迟加载

  • 分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
    • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载
    • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
  • 此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过associationcollection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加载)|eager(立即加载)"
<settings>
	
	<setting name="lazyLoadingEnabled" value="true"/>
settings>
    @Test
    public void test2() {

        DeptMapper mapper = SqlSessionUtil.getMapper(DeptMapper.class);
        List<Dept> deptList = mapper.getDeptList2();
        System.out.println(deptList);
    }
  • 关闭延迟加载,两条SQL语句都运行了

    MyBatis入门笔记_第9张图片

  • 开启延迟加载,只运行获取deptList的SQL语句

    @Test
    public void test2() {

        DeptMapper mapper = SqlSessionUtil.getMapper(DeptMapper.class);
        List<Dept> deptList = mapper.getDeptList2();
        for (Dept dept : deptList) {
            System.out.println(dept);
            System.out.println(dept.getEmpList());
        }
    }

开启后,需要用到查询dept的时候才会调用相应的SQL语句

MyBatis入门笔记_第10张图片

  • fetchType:当开启了全局的延迟加载之后,可以通过该属性手动控制延迟加载的效果,fetchType=“lazy(延迟加载)|eager(立即加载)”
    <select id="getDeptList" resultMap="deptAndEmpResultMap">
        select d.did, dname, eid, age, email, emp_name, gender, password
        from t_department d
        left outer join t_emp e on d.did = e.did
    select>

    <resultMap id="deptAndEmpResultMap2" type="Dept">
        <id property="did" column="did"/>
        <result property="deptName" column="dname"/>
        
        
        <association property="empList" select="com.atguigu.mapper.EmpMapper.getEmpListByDid" column="did" fetchType="lazy"/>
    resultMap>

    <select id="getDeptList2" resultMap="deptAndEmpResultMap2">
        select did, dname
        from t_department
    select>

8. 动态SQL

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题

8.1 if 标签

  • if标签可通过test属性(即传递过来的数据)的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行
  • 在where后面添加一个恒成立条件1=1
    • 这个恒成立条件并不会影响查询的结果
    • 这个1=1可以用来拼接and语句,例如:当empName为null时
    • 如果不加上恒成立条件,则SQL语句为select * from t_emp where and age = ? and email = ?,此时where会与and连用,SQL语句会报错
    • 如果加上一个恒成立条件,则SQL语句为select * from t_emp where 1= 1 and age = ? and email = ?,此时不报错
    
    
    <select id="getEmpByIf" resultMap="com.atguigu.mapper.EmpMapper.empResultMap">
        select * from t_emp where 1=1
        <if test="empName != null and  empName != ''">
            emp_name = #{empName}
        if>
        <if test="age != null and  age > 0">
            and age = #{age}
        if>
        <if test="email != null and  email != ''">
            and email = #{email}
        if>
    select>

8.2 where 标签

  • where和if一般结合使用:
  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and/or去掉
    
    <select id="getEmpByWhere" resultMap="com.atguigu.mapper.EmpMapper.empResultMap">
        select * from t_emp
        <where>
            <if test="empName != null and  empName != ''">
                emp_name = #{empName}
            if>
            <if test="age != null and  age > 0">
                and age = #{age}
            if>
            <if test="email != null and  email != ''">
                and email = #{email}
            if>
        where>
    select>
  • 注意:where标签不能去掉条件后多余的and/or


<if test="empName != null and empName !=''">
emp_name = #{empName} and
if>
<if test="age != null and age !=''">
	age = #{age}
if>

8.3 trim 标签

  • trim用于去掉或添加标签中的内容
  • 常用属性:
    • prefix:在trim标签中的内容的前面添加某些内容
    • suffix:在trim标签中的内容的后面添加某些内容
    • prefixOverrides:在trim标签中的内容的前面去掉某些内容
    • suffixOverrides:在trim标签中的内容的后面去掉某些内容
  • 若trim中的标签都不满足条件,则trim标签没有任何效果,也就是只剩下select * from t_emp
    
    <select id="getEmpByTrim" resultMap="com.atguigu.mapper.EmpMapper.empResultMap">
        select * from t_emp
        <trim prefix="where" suffix=" order by eid desc ">
            <if test="empName != null and  empName != ''">
                emp_name = #{empName}
            if>
            <if test="age != null and  age > 0">
                and age = #{age}
            if>
            <if test="email != null and  email != ''">
                and email = #{email}
            if>
        trim>

    select>
//测试类
    @Test
    public void getEmpByCondition3() {
        DynamicSQLMapper mapper = SqlSessionUtil.getMapper(DynamicSQLMapper.class);
        List<Emp> empList = mapper.getEmpByCondition2(new Emp(null, "张三", "", "", 22, ""));
        System.out.println(empList);
    }

在这里插入图片描述

8.4 choose、when、otherwise标签

  • choose、when、otherwise相当于if...else if..else
  • when至少要有一个,otherwise至多只有一个

    <select id="getEmpByChoose" resultMap="com.atguigu.mapper.EmpMapper.empResultMap">
        select * from t_emp
        <where>
            <choose>
                <when test="empName != null and  empName != ''">
                    emp_name = #{empName}
                when>
                <when test="age != null and  age > 0">
                    and age = #{age}
                when>
                <when test="email != null and  email != ''">
                    and email = #{email}
                when>
                <otherwise>
                    did = 1
                otherwise>
            choose>
        where>
    select>
    @Test
    public void getEmpByChoose() {
        DynamicSQLMapper mapper = SqlSessionUtil.getMapper(DynamicSQLMapper.class);
        List<Emp> empList = mapper.getEmpByChoose(new Emp(null, "张三", "", "", 22, ""));
        System.out.println(empList);
    }
  • 相当于if a else if b else if c else d,只会执行其中一个

8.5 foreach 标签

  • 属性:

    • collection:设置要循环的数组或集合
    • item:表示集合或数组中的每一个数据
    • separator:设置循环体之间的分隔符,分隔符前后默认有一个空格,如,
    • open:设置foreach标签中的内容的开始符
    • close:设置foreach标签中的内容的结束符
  • 使用场景一:批量删除

    /**
     * 批量删除:foreach标签
     */
    int deleteMore(@Param("ids")List<Integer> ids);
    
    <delete id="deleteMore">
        delete from t_emp
        where eid in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        foreach>
    delete>
    @Test
    public void deleteMore() {
        DynamicSQLMapper mapper = SqlSessionUtil.getMapper(DynamicSQLMapper.class);
        List<Integer> ids = Arrays.asList(12, 13, 14);
        int i = mapper.deleteMore(ids);
        System.out.println("i = " + i);
    }
  • 使用场景二:批量添加
    /**
     * 批量插入:foreach标签
     */
    int insertMore(@Param("empList")List<Emp> empList);
    
    <insert id="insertMore">
        insert into t_emp( emp_name, password, gender, age, email, did)
        values
        <foreach collection="empList" item="emp" separator="," close=";">
            (#{emp.empName}, #{emp.password}, #{emp.gender}, #{emp.age}, #{emp.email}, 3)
        foreach>
    insert>
    <!-- 使用foreach标签实现批量删除 -->
    <delete id="deleteMore">
        delete from t_emp
        where eid in
        <foreach collection="ids" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>

8.6 使用sql和include标签插入SQL片段

  • sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入
  • 声明sql片段:标签
    
    <sql id="empCloumns" >
        eid, emp_name, password, gender, age, email, did
    sql>
  • 引用sql片段:标签
    
    <select id="getEmpList" resultMap="com.atguigu.mapper.EmpMapper.empResultMap">
        select <include refid="empCloumns"/>
        from t_emp
    select>

9. MyBatis的缓存

9.1 MyBatis的一级缓存

  • 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问 ;
  • Mybatis一级缓存默认开启
  • 缓存只针对查询语句生效

样例:

    @Test
    public void testFirstLevelCache(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
        List<Emp> allEmp = mapper.getEmpList();
        System.out.println(allEmp);

        DynamicSQLMapper mapper2 = sqlSession.getMapper(DynamicSQLMapper.class);
        List<Emp> allEmp2 = mapper2.getEmpList();
        System.out.println(allEmp2);
    }

在这里插入图片描述

可以看出,虽然调用了两次列表查询,但实际只执行了一次SQL查询。

使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSession但是查询条件不同
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存,调用SqlSession实例的closeCache方法

9.2 MyBatis的二级缓存

  • 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
  • 二级缓存开启的条件
    • 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
    • 在映射文件中设置标签
    • 二级缓存必须在SqlSession关闭或提交之后有效
    • 查询的数据所转换的实体类类型必须实现序列化的接口
    
    
    <settings>
        
        <setting name="cacheEnabled" value="true"/>
    settings>
    
	
    <cache />
    /**
     * 测试二级缓存
     */
    @Test
    public void testSecondLevelCache() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder  sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        DynamicSQLMapper mapper1 = sqlSession1.getMapper(DynamicSQLMapper.class);
        List<Emp> empList1 = mapper1.getEmpList();
        System.out.println(empList1);
        // 二级缓存必须在SqlSession关闭或提交之后有效
        sqlSession1.close();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        DynamicSQLMapper mapper2 = sqlSession2.getMapper(DynamicSQLMapper.class);
        List<Emp> empList2 = mapper2.getEmpList();
        System.out.println(empList2);
    }
  • 使二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

9.3 二级缓存的相关配置

  • 在mapper配置文件中添加的cache标签可以设置一些属性
  • eviction属性:缓存回收策略
    • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象,是默认的缓存回收策略
    • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  • flushInterval属性:刷新间隔,单位毫秒
    • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句(增删改)时刷新
  • size属性:引用数目,正整数
    • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly属性:只读,true/false,默认是false
    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全。

9.4 MyBatis缓存查询的顺序

**MyBatis缓存查询的顺序:**二级缓存 --> 一级缓存 --> 数据库

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. SqlSession关闭之后,一级缓存中的数据会写入二级缓存

9.5 整合第三方缓存EHCache替换Mybatis二级缓存(了解)

9.5.1 添加依赖


<dependency>
	<groupId>org.mybatis.cachesgroupId>
	<artifactId>mybatis-ehcacheartifactId>
	<version>1.2.1version>
dependency>

<dependency>
	<groupId>ch.qos.logbackgroupId>
	<artifactId>logback-classicartifactId>
	<version>1.2.3version>
dependency>

各个jar包的功能

jar包名称 作用
mybatis-ehcache Mybatis和EHCache的整合包
ehcache EHCache核心包
slf4j-api SLF4J日志门面包
logback-classic 支持SLF4J门面接口的一个具体实现

9.5.2 创建EHCache的配置文件ehcache.xml

  • 名字必须叫ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    
    <diskStore path="D:\atguigu\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    defaultCache>
ehcache>

EHCache的配置文件的属性说明:

属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的element的最大数目
maxElementsOnDisk 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal 设定缓存的elements是否永远不过期。 如果为true,则缓存的数据始终有效, 如果为false那么还要根据timeToIdleSeconds、timeToLiveSeconds判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
timeToIdleSeconds 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时, 这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区
diskPersistent 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s, 相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。 默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出

9.5.3 设置二级缓存的类型

  • 在Mapper映射文件xxxMapper.xml文件中设置二级缓存类型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

9.5.4 加入logback日志

  • 存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志。创建logback的配置文件logback.xml,名字固定,不可改变

<configuration debug="true">
    
    <appender name="STDOUT"
              class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            
            
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%npattern>
        encoder>
    appender>
    
    
    <root level="DEBUG">
        
        <appender-ref ref="STDOUT" />
    root>
    
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
configuration>

10. MyBatis的逆向工程

  • 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的
  • 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
    • Java实体类
    • Mapper接口
    • Mapper映射文件

10.1 创建逆向工程

10.1.1 添加依赖和插件


    <packaging>jarpackaging>

    <dependencies>
        
        <dependency>
            <groupId>org.mybatisgroupId>
            <artifactId>mybatisartifactId>
            <version>3.5.9version>
        dependency>
        
        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <version>4.13.2version>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.27version>
        dependency>
        
        <dependency>
            <groupId>log4jgroupId>
            <artifactId>log4jartifactId>
            <version>1.2.17version>
        dependency>
    dependencies>

    
    <build>
        
        <plugins>
            
            <plugin>
                <groupId>org.mybatis.generatorgroupId>
                <artifactId>mybatis-generator-maven-pluginartifactId>
                <version>1.3.2version>
                <configuration>
                    
                    <overwrite>trueoverwrite>
                configuration>
                
                <dependencies>
                    
                    <dependency>
                        <groupId>org.mybatis.generatorgroupId>
                        <artifactId>mybatis-generator-coreartifactId>
                        <version>1.3.2version>
                    dependency>

                    
                    <dependency>
                        <groupId>com.alibabagroupId>
                        <artifactId>druidartifactId>
                        <version>1.1.12version>
                    dependency>

                    
                    <dependency>
                        <groupId>mysqlgroupId>
                        <artifactId>mysql-connector-javaartifactId>
                        <version>8.0.27version>
                    dependency>
                dependencies>
            plugin>

        plugins>
    build>

10.1.2 创建MyBatis的核心配置文件


DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"/>
    <typeAliases>
        <package name=""/>
    typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <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>
    <mappers>
        <package name=""/>
    mappers>
configuration>

10.1.3 创建逆向工程的配置文件

  • 文件名必须是:generatorConfig.xml

DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    

    <properties resource="jdbc.properties"/>

    
    <context id="DB2Tables" targetRuntime="MyBatis3">

        
        <property name="javaFileEncoding" value="UTF-8"/>

        
        <commentGenerator>
            <property name="suppressAllComments" value="true" />
        commentGenerator>

        
        <jdbcConnection driverClass="${jdbc.driver}"
                        connectionURL="${jdbc.url}"
                        userId="${jdbc.username}"
                        password="${jdbc.password}">
            
            
            
            <property name="nullCatalogMeansCurrent" value="true"/>
        jdbcConnection>

        
        <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java">
            
            <property name="enableSubPackages" value="true" />
            
            <property name="trimStrings" value="true" />
        javaModelGenerator>

        
        <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        sqlMapGenerator>

        
        <javaClientGenerator type="XMLMAPPER"
                             targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        javaClientGenerator>

        
        
        
        <table tableName="t_emp"  domainObjectName="Emp"/>
        <table tableName="t_department"   domainObjectName="Dept"/>
    context>
generatorConfiguration>

10.1.4 执行MBG插件的generate目标

MyBatis入门笔记_第11张图片

  • 如果出现报错:Exception getting JDBC Driver,可能是pom.xml中,数据库驱动配置错误

    • dependency中的驱动MyBatis入门笔记_第12张图片

    • mybatis-generator-maven-plugin插件中的驱动MyBatis入门笔记_第13张图片

  • 执行结果MyBatis入门笔记_第14张图片

10.2 QBC

10.2.1 查询

  • selectByExample:按条件查询,需要传入一个example对象或者null;如果传入一个null,则表示没有条件,也就是查询所有数据
  • example.createCriteria().xxx:创建条件对象,通过andXXX方法为SQL添加查询添加,每个条件之间是and关系
  • example.or().xxx:将之前添加的条件通过or拼接其他条件
    MyBatis入门笔记_第15张图片
    @Test
    public void test() throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        // 查询所有数据
        EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
        List<Emp> empList = empMapper.selectByExample(null);
        empList.forEach(System.out::println);
        System.out.println("********************");
        // 根据条件查询
        EmpExample empExample = new EmpExample();
        // 姓名为张三,年龄大于20
        empExample.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThan(20);
        //或者did不为空
        empExample.or().andDidIsNotNull();
        List<Emp> emps = empMapper.selectByExample(empExample);
        emps.forEach(System.out::println);
    }

DEBUG 08-10 21:32:18,431 ==> Preparing: select eid, emp_name, password, gender, age, email, did from t_emp WHERE ( emp_name = ? and age > ? ) or( did is not null ) (BaseJdbcLogger.java:137)
DEBUG 08-10 21:32:18,432 > Parameters: 张三(String), 20(Integer) (BaseJdbcLogger.java:137)
DEBUG 08-10 21:32:18,435 <
Total: 11 (BaseJdbcLogger.java:137)

MyBatis入门笔记_第16张图片

10.2.2 增改

  • updateByPrimaryKey:通过主键进行数据修改,如果某一个值为null,也会将对应的字段改为null

  • updateByPrimaryKeySelective():通过主键进行选择性数据修改,如果某个值为null,则不修改这个字段

    • 例:mapper.updateByPrimaryKeySelective(new Emp(2,"admin2",22,null,"[email protected]",3));
    @Test
        public void test2() throws IOException {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            SqlSession sqlSession = sqlSessionFactory.openSession(true);
            EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
            // updateByPrimaryKey通过主键进行数据修改,如果某一个值为null,也会将对应的字段改为null
            // empMapper.updateByPrimaryKey(new Emp(11,"Sakura","123456",null,11,null,1));
    
            // 通过主键进行选择性数据修改,如果某个值为null,则不修改这个字段
            empMapper.updateByPrimaryKeySelective(new Emp(11,"Sakura","123456",null,11,null,1));
    
    
        }
    

11. 分页插件

11.1 分页插件配置步骤

11.1.1 添加依赖


<dependency>
	<groupId>com.github.pagehelpergroupId>
	<artifactId>pagehelperartifactId>
	<version>5.2.0version>
dependency>

11.1.2 配置分页插件

  • 在MyBatis的核心配置文件(mybatis-config.xml)中配置插件
  • MyBatis入门笔记_第17张图片
<plugins>
	
	<plugin interceptor="com.github.pagehelper.PageInterceptor">plugin>
plugins>

11.2 分页插件的使用

11.2.1 开启分页功能

  • 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
    • pageNum:当前页的页码
    • pageSize:每页显示的条数
@Test
public void testPageHelper() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
	//访问第一页,每页四条数据
	PageHelper.startPage(1,4);
	List<Emp> emps = mapper.selectByExample(null);
	emps.forEach(System.out::println);
}

MyBatis入门笔记_第18张图片

11.2.2 分页相关数据

方法一:直接输出

@Test
public void testPageHelper() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
	//访问第一页,每页四条数据
	Page<Object> page = PageHelper.startPage(1, 4);
    // 查询全部数据
	List<Emp> emps = mapper.selectByExample(null);
	//在查询到List集合后,打印分页数据
	System.out.println(page);
}
  • 分页相关数据:

    Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=8, pages=2, reasonable=false, pageSizeZero=false}[Emp{eid=1, empName='admin', age=22, sex='男', email='[email protected]', did=3}, Emp{eid=2, empName='admin2', age=22, sex='男', email='[email protected]', did=3}, Emp{eid=3, empName='王五', age=12, sex='女', email='[email protected]', did=3}, Emp{eid=4, empName='赵六', age=32, sex='男', email='[email protected]', did=1}]
    

方法二使用PageInfo

  • 在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, intnavigatePages)获取分页相关数据
    • list:分页之后的数据
    • navigatePages:导航分页的页码数
  • PageHelper.startPage需要放在查询语句的上面且紧邻
@Test
public void testPageHelper() throws IOException {
	InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
	SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
	SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
	SqlSession sqlSession = sqlSessionFactory.openSession(true);
	EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
	PageHelper.startPage(1, 4);
	List<Emp> emps = mapper.selectByExample(null);
	PageInfo<Emp> page = new PageInfo<>(emps,5);
	System.out.println(page);
}
  • 分页相关数据:

    PageInfo{
    pageNum=1, pageSize=4, size=4, startRow=1, endRow=4, total=8, pages=2, 
    list=Page{count=true, pageNum=1, pageSize=4, startRow=0, endRow=4, total=8, pages=2, reasonable=false, pageSizeZero=false}[Emp{eid=1, empName='admin', age=22, sex='男', email='[email protected]', did=3}, Emp{eid=2, empName='admin2', age=22, sex='男', email='[email protected]', did=3}, Emp{eid=3, empName='王五', age=12, sex='女', email='[email protected]', did=3}, Emp{eid=4, empName='赵六', age=32, sex='男', email='[email protected]', did=1}], 
    prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=5, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
    
  • 其中list中的数据等同于方法一中直接输出的page数据

常用数据:

  • pageNum:当前页的页码
  • pageSize:每页显示的条数
  • size:当前页显示的真实条数
  • total:总记录数
  • pages:总页数
  • prePage:上一页的页码
  • nextPage:下一页的页码
  • isFirstPage/isLastPage:是否为第一页/最后一页
  • hasPreviousPage/hasNextPage:是否存在上一页/下一页
  • navigatePages:导航分页的页码数
  • navigatepageNums:导航分页的页码,[1,2,3,4,5]

你可能感兴趣的:(mybatis,笔记)