抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

边做笔记边学习是真的慢啊,10个小时的视频都看了5天🤔,虽然这能让自己理解的效果稍微好一些,对以后学习回顾也是一个好处,但是现在时间已经不允许我再慢慢搞了😭,越学越觉得自己会的东西少且low,面经看一个不会一个,lc也没刷,学历也差,再看看今年的形式真是焦虑至极啊😖希望快点好起来

一、Mybatis简介

下载地址

将mybatis下载下来后,文件夹中有官方文档和jar包,现在我们都是使用Maven直接导入依赖,所以现在没必要去下载,除非不使用Maven就可以下载下来将其jar包导入到项目进行使用

1.1 MyBatis历史

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。Mybatis一词来源于“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 和其它持久化层技术对比

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难
    • 反射操作太多,导致数据库性能下降
  • Mybatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于HIbernate,但是完全能够接受

二、搭建Mybatis

2.1 开发环境

IDEA:2021.2

Mybatis:3.5.9

Maven:3.8.1

MySQL:5.7

2.2 创建maven工程

打开IDEA,新建项目

完成Maven项目的创建

2.3 准备工作

2.3.1 导入Maven依赖

<!--更改打包方式为jar,其实可以不用改,默认就为jar-->
<packaging>jar</packaging>
<!--导入依赖-->
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2.3.2 创建数据库和数据表

首先我们需要创建好一个数据库和数据表来作为本次学习Mybatis使用的数据存储介质

创建好数据库后,为了操作操作方便,我们还可以将数据库作为数据源添加到IDEA中,过程如下:(而且如果不添加数据源的话,等下方配置mapper接口的映射方法时会报错说什么找不到数据源之类的,虽然可能没什么影响,但是有强迫症的话就很恼火)

2.3.3 创建对应实体类

public class User {
    //实体类的属性名要尽量和数据库中表的字段名一致
    private Integer id;
    private String username;
    private String password;
    private Integer age;
    private String gender;
    private String email;
    //省略get、set、toString方法
}

2.3.4 创建配置文件和mapper接口

  1. java文件夹中创建好mapper包,然后在其中创建一个名为UserMappermapper接口,其作用就是代替之前javaweb开发中操作数据库的dao层

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

  2. resource文件夹下创建一个mybatis-config.xml的配置文件

    习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略。核心配置文件主要用于配置连接数据库的环境以及MyBatis的全局配置信息

    核心配置文件存放的位置是src/main/resources目录下

  3. resource文件夹下创建一个专门存放mapper接口的映射文件的包mappers,在这个包中创建好UserMapper接口的映射文件UserMapper.xml文件

    相关概念:ORM(Object Relationship Mapping)对象关系映射

    • 对象:Java的实体类对象
    • 关系:关系型数据库
    • 映射:二者之间的对应关系
    Java概念 数据库概念
    属性 字段/列
    对象 记录/行

    1、映射文件的命名规则:
    表所对应的实体类的类名+Mapper.xml
    例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml
    因此一个映射文件对应一个实体类,对应一张表的操作
    MyBatis映射文件用于编写SQL,访问以及操作表中的数据
    MyBatis映射文件存放的位置是src/main/resources/mappers目录下

    2、MyBatis中可以面向接口操作数据,要保证两个一致
    a>mapper接口的全类名和映射文件的命名空间(namespace)保持一致
    b>mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

  4. 创建好后的结构如图所示:

2.4 配置Mybatis

2.4.1 配置mybatis配置文件

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

<configuration>
    <!--设置连接数据库的环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--其中这四个是需要改成自己数据库的配置-->
                <!--如果在pom文件中导入的mysql驱动依赖是8.x版本的,driver应该这样配置-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入映射文件-->
    <mappers>
        <!--将创建的映射文件引入-->
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

2.4.2 编写Mapper接口内容

//由于只是测试,所以mapper映射文件中会写死条件,这里的方法就不设置传参了
public interface UserMapper {
    //插入新用户
    int insertUser();
    //更新用户
    int updateUser();
    //删除用户
    int deleteUser();
    //通过id查询用户
    User queryUserById();
    //查询所有用户
    List<User> queryAllUser();
}

2.4.3 配置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="com.mybatis.mapper.UserMapper">
    <!--是UserMapper中的int insertUser()方法的映射-->
    <insert id="insertUser">
        insert into t_user values(null,'admin','123456',23,'女','12345@qq.com')
    </insert>
    <!--int updateUser();-->
    <update id="updateUser">
        update t_user set username = 'zhangsan' where id = 2
    </update>
    <!--int deleteUser();-->
    <delete id="deleteUser">
        delete from t_user where id = 2
    </delete>
    <!--User queryUserById();-->
    <select id="queryUserById" resultType="com.mybatis.entity.User">
        select * from t_user where id = 1
    </select>
    <!--List<User> queryAllUser()-->
    <select id="queryAllUser" resultType="com.mybatis.entity.User">
        select * from t_user
    </select>
</mapper>

查询功能的标签必须设置resultType或resultMap属性

resultType:设置默认的映射关系(即实体类对象属性和数据库表字段映射一致的情况)
resultMap:设置自定义的映射关系(后面会讲到如何使用)

2.5 加入日志功能

可加可不加

<!--导入依赖-->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

添加log4j的配置文件,命名为log4j.xml,存放在src/main/resources目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!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>

日志的级别

FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)
从左到右打印的内容越来越详细,且打印信息会大于等于设置的日志级别

2.6 测试功能

2.6.1 测试添加功能

@Test
public void insertUser() throws IOException {
    //读取MyBatis的核心配置文件
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //创建SqlSessionFactoryBuilder对象
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    //通过核心配置文件所对应的字节输入流创建工厂类SqlSessionFactory,生产SqlSession对象
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    //SqlSession sqlSession = sqlSessionFactory.openSession();    创建SqlSession对象,此时通过SqlSession对象所操作的sql都必须手动提交或回滚事务
    SqlSession sqlSession = sqlSessionFactory.openSession(true);  //创建SqlSession对象,此时通过SqlSession对象所操作的sql都会自动提交
    //通过代理模式创建UserMapper接口的代理实现类对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    //调用UserMapper接口中的方法,就可以根据UserMapper的全类名匹配元素文件,通过调用的方法名匹配映射文件中的SQL标签,并执行标签中的SQL语句
    int result = userMapper.insertUser();
    //sqlSession.commit();    如果是使用sqlSessionFactory.openSession()创建的sqlSession对象就需要使用这行代码手动提交或回滚事务
    System.out.println("result:" + result);//返回受影响行数
}
  • SqlSession:代表Java程序和数据库之间的会话(HttpSession是Java程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”
  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象

此时,数据库中就多出一条数据:

注:如果我们不手动提交或设置自动提交事务,测试输出的结果仍虽然会是1,但是数据库中不会有数据,并且它会占用id

2.6.2 测试修改和删除功能

@Test
public void updateUser() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    int i = userMapper.updateUser();
    System.out.println("result:" + i);//result:1
}

@Test
public void deleteUser() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    int i = userMapper.deleteUser();
    System.out.println("result:" + i);//result:1
}

更新用户结果:id为2的用户的用户名已经被更新为”zhangsan”

删除用户结果:id为2的用户已经被删除

2.6.3 测试查询功能

@Test
public void queryUserById() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.queryUserById();
    System.out.println(user);//User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}
}
@Test
public void queryAllUser() throws IOException {
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.queryAllUser();
    System.out.println(userList);
    //[User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}, User{id=3, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}]
}

2.6.4 封装工具类

由于每次操作我们都需要使用相同的步骤来创建sqlSession,这样就晓得操作过程非常繁琐,所以我们可以将这些操作使用一个工具类来进行封装

public class SqlSessionUtils {
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = null;
        try {
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
            sqlSession = sqlSessionFactory.openSession(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSession;
    }
}

三、Mybatis基础功能

3.1 核心配置文件详解

核心配置文件中的标签必须按照固定的顺序:

properties -> settings -> typeAliases -> typeHandlers -> objectFactory -> objectWrapperFactory -> reflectorF actory -> plugins - > environments -> databaseIdProvider -> mappers

这里只配置propertiestypeAliasesenvironmentsmappers,其余的等使用到的时候再添加

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

<configuration>
    <!--引入properties文件,此时就可以在配置数据库时使用${属性名}的方式访问属性值-->
    <!--jdbc.properties存放在resources文件夹中-->
    <properties resource="jdbc.properties"/>
    
    <typeAliases>
        <!--
            typeAlias:设置某个具体的类型的别名
            属性:
            type:需要设置别名的类型的全类名
            alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小写。若设置此属性,此时该类型的别名只能使用alias所设置的值
            alisa属性也可以在实体类中使用@Alisa注解代替
        -->
        <!--<typeAlias type="com.mybatis.entity.User"></typeAlias>-->
        <!--<typeAlias type="com.mybatis.entity.User" alias="abc"></typeAlias>-->
        <!--以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写-->
        <package name="com.mybatis.entity"/>
    </typeAliases>
    
    <!--
        environments:设置多个连接数据库的环境
        属性:
        default:设置默认使用的环境的id
    -->
    <environments default="mysql_test">
        <!--
            environment:设置具体的连接数据库的环境信息属性:
            id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境
        -->
        <environment id="mysql_test">
            <!--
                transactionManager:设置事务管理方式
                属性:
                type:设置事务管理方式,type="JDBC|MANAGED"
                type="JDBC":设置当前环境的事务管理都必须手动处理
                type="MANAGED":设置事务被管理,例如spring中的AOP,使用声明式事务
            -->
            <transactionManager type="JDBC"/>
            <!--
                dataSource:设置数据源
                属性:
                type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
                type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
                type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建
                type="JNDI":调用上下文中的数据源
            -->
            <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>
        <!--引入单个mapper映射文件-->
        <!--<mapper resource="UserMapper.xml"/>-->
        <!--
            以包为单位,将包下所有的映射文件引入核心配置文件
            注意:此方式必须保证mapper接口和mapper映射文件必须在相同的包下
            比如,mapper接口所在包为com.mybatis.mapper,那么mapper映射文件也必须在resources下的com.mybatis.mapper包中
        -->
        <package name="com.mybatis.mapper"/>
    </mappers>
</configuration>

Tips小技巧:创建Mybatis核心配置文件和mapper映射文件模板

①创建Mybatis核心配置文件模板(文件名可选,如果不设置,新建模板文件时就会提示输入文件名)

测试:选择一个文件夹,右键新建,选择新建一个mybatis-config文件,在新文件中就可以发现Mybatis那些必要的配置代码就自动生成了,减少了每次使用mybatis都需要去复制编写的麻烦

②创建mapper映射文件的方式同上,这里就不再多说了

3.2 Mybatis的增删改查

1、增加

<!--是UserMapper中的int insertUser()方法的映射-->
<insert id="insertUser">
    insert into t_user values(null,'admin','123456',23,'女','12345@qq.com')
</insert>

2、修改

<!--int updateUser();-->
<update id="updateUser">
    update t_user set username = 'zhangsan' where id = 2
</update>

3、删除

<!--int deleteUser();-->
<delete id="deleteUser">
    delete from t_user where id = 2
</delete>

4、查询单个实体类对象

<!--User queryUserById();-->
<select id="queryUserById" resultType="com.mybatis.entity.User">
    select * from t_user where id = 1
</select>

5、查询集合

<!--List<User> queryAllUser()-->
<select id="queryAllUser" resultType="com.mybatis.entity.User">
    select * from t_user
</select>

注意:
1、查询的标签select必须设置属性resultType或resultMap,用于设置实体类和数据库表的映射关系

resultType:自动映射,用于属性名和表中字段名一致的情况

resultMap:自定义映射,用于一对多多对一字段名和属性名不一致的情况

2、当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常TooManyResultsException;但是若查询的数据只有一条,可以使用实体类或集合作为返回值

3、其实如果简单一点的sql语句可以不使用mapper映射文件来编写,可以使用注解@Select@Update@Delete@Insert来代替,将这些注解标识在方法上面,然后在注解中编写sql语句,例如

@Select("select * from t_user where username = #{username}")
User getUserByUsername(String username);

3.3 Mybatis获取参数值

MyBatis获取参数值的两种方式:${}#{}

  • ${}的本质就是字符串拼接#{}的本质就是占位符赋值
  • ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号

3.3.1 单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}#{}以任意的名称获取参数的值,注意${}需要手动加单引号

<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">
    select * from t_user where username = '${username}'
</select>

<!--User getUserByUsername(String username);-->
<select id="getUserByUsername" resultType="User">
    select * from t_user where username = #{username}
</select>

3.3.2 多个字面量类型的参数

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

  • arg0,arg1…为键,以参数为值
  • param1,param2…为键,以参数为值
  • 注:多个字面量类型的参数不能像单个字面量类型参数一样直接通过参数名作为键,如果不使用上面这两种键来取值,就得使用@Param注解标识参数,否则会获取不了参数值
  • 然后只需要通过${}#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
<!--User checkLogin(String username,String password)-->
<select id="checkLogin" resultType="User">
    select * from t_user where username = #{arg0} and password = #{arg1}
</select>

<!--User checkLogin(String username,String password)-->
<select id="checkLogin" resultType="User">
    select * from t_user where username = #{param1} and password = #{param2}
</select>

3.3.3 map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中,只需要通过${}#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号

@Test
public void checkLoginByMap() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("username","admin");
    map.put("password","123456");
    User user = mapper.checkLoginByMap(map);
    System.out.println(user);
}
<!--User checkLoginByMap(Map map)-->
<select id="checkLoginByMap" resultType="User">
    select * from t_user where username = #{username} and password = #{password}
</select>

3.3.4 实体类类型的参数

若mapper接口中的方法参数为实体类对象时,此时可以使用${}#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加单引号

@Test
public void insertUser() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId(null);
    user.setUsername("lisi");
    user.setPassword("123456");
    user.setAge(20);
    user.setGender("男");
    user.setEmail("123456@qq.com");
    int i = userMapper.insertUser(user);
    System.out.println(i);
}
<!--int insertUser(User user)-->
<insert id="insertUser">
    insert into t_user values (#{id}, #{username}, #{password}, #{age}, #{gender}, #{email})
</insert>

3.3.5 使用@Param标识的参数

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

  • @Param注解的value属性值为键,以参数为值
  • 以param1,param2…为键,以参数为值
  • 然后只需要通过${}#{}访问map集合的键就可以获取相对应的值,注意${}需要手动加单引号
<!--User checkLoginByParam(@Param("username") String username, @Param("password") String password)-->
<select id="checkLoginByParam" resultType="User">
    <!--这里#{}中的值就是我们设置@Param注解的value的值-->
    select * from t_user where username = #{username} and password = #{password}
</select>

3.4 Mybatis的各种查询方式

3.4.1 查询单个实体类对象

/**
* 根据用户id查询用户信息
*/
User getUserById(@Param("id") Integer id);
<!--User getUserById(@Param("id") Integer id)-->
<select id="getUserById" resultType="User">
    select * from t_user where id = #{id}
</select>
<!--User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}-->

3.4.2 查询一个list集合

/**
* 查询所有用户信息
*/
List<User> getAllUser();
<!--List<User> getAllUser()-->
<select id="getAllUser" resultType="User">
    select * from t_user
</select>
<!--
    [User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}, 
    User{id=3, username='zhangsan', password='123456', age=23, gender='女', email='12345@qq.com'}, 
    User{id=4, username='lisi', password='123456', age=20, gender='男', email='123456@qq.com'}]
-->

3.4.3 查询单个数据

/**
* 查询用户的总记录数
* 在MyBatis中,对于Java中常用的类型都设置了类型别名
* 例如:java.lang.Integer-->int|integer
* 例如:int-->_int|_integer
* 例如:Map-->map,List-->list
*/
Integer getUserCount();
<!--Integer getUserCount()-->
<select id="getUserCount" resultType="Integer">
    select count(id) from t_user
</select>
<!--3-->
<!--count(1)和count(*)查询出来的结果是一样的,而使用count(字段名)查询的话,若某一条数据的当前字段名值为null,那么这条数据不会被算到count中-->

3.4.4 查询一条数据为map集合

/**
* 根据用户id查询用户信息为map集合
*/
Map<String,Object> getUserToMap(@Param("id") Integer id);
<!--Map<String,Object> getUserToMap(@Param("id") Integer id)-->
<select id="getUserToMap" resultType="map">
    select * from t_user where id = #{id}
</select>
<!--{password=123456, gender=女, id=1, age=23, email=12345@qq.com, username=admin}-->

3.4.5 查询多条数据为map集合

① 方式一

/**
* 查询所有用户信息为map集合
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,此时可以将这些map放在一个list集合中获取
*/
List<Map<String,Object>> getAllUserToListMap();
<!--List<Map<String,Object>> getAllUserToListMap()-->
<select id="getAllUserToListMap" resultType="map">
    select * from t_user
</select>
<!--
    [{password=123456, gender=女, id=1, age=23, email=12345@qq.com, username=admin},
     {password=123456, gender=女, id=3, age=23, email=12345@qq.com, username=zhangsan}, 
    {password=123456, gender=男, id=4, age=20, email=123456@qq.com, username=lisi}]
-->

② 方式二

/**
* 查询所有用户信息为map集合
* 将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置为map集合的键,值是每条数据所对应的map集合
*/
@MapKey("id")
Map<String,Object> getAllUserToMap();
<!--Map<String,Object> getAllUserToMap()-->
<select id="getAllUserToMap" resultType="map">
    select * from t_user
</select>
<!--
{
    1={password=123456, gender=女, id=1, age=23, email=12345@qq.com, username=admin},
     3={password=123456, gender=女, id=3, age=23, email=12345@qq.com, username=zhangsan}, 
    4={password=123456, gender=男, id=4, age=20, email=123456@qq.com, username=lisi}}
-->

3.4.6 Mybatis中的映射别名

Java常用类型在Mybatis中的映射别名

3.5 特殊SQL

3.5.1 模糊查询

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

模糊查询中字符串的拼接有三种方式:

<!--List<User> getUserByLike(@Param("username") String username)-->
<!--第一种:使用双引号加#{}的方式-->
<select id="getUserByLike" resultType="User">
    <!--这种方式IDE会报错,但是仍能查询-->
    select * from t_user where username like "%"#{username}"%"
</select>
<!--第二种:使用单引号加${}的方式-->
<select id="getUserByLike" resultType="User">
    select * from t_user where username like '%${username}%'
</select>
<!--第三种:使用concat函数-->
<select id="getUserByLike" resultType="User">
    select * from t_user where username like concat('%',#{username},'%');
</select>
@Test
public void getUserByLike() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class);
    List<User> userList = sqlMapper.getUserByLike("a");
    System.out.println(userList);
    //[User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}, User{id=3, username='zhangsan', password='123456', age=23, gender='女', email='12345@qq.com'}]
}

3.5.2 批量删除

//批量删除用户
//注意:这儿ids用几个id值拼接而成的字符串,如"1,2,3"
Integer deleteMore(@Param("ids") String ids);
<!--Integer deleteMore(@Param("ids") String ids)-->
<!--在mybatis中批量删除只能使用${},因为#{}在生成sql语句时后自动拼接单引号,这对于mysql的in语法是错误的,in中只能是数值,而不能是字符串-->
<delete id="deleteMore">
    delete from t_user where id in (${ids});
</delete>
@Test
public void deleteMore() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class);
    Integer deleteMore = sqlMapper.deleteMore("1,2,3");
    System.out.println(deleteMore);
}

3.5.3 动态设置表名

//查询不同表中的用户
List<User> selectUserByTableName(@Param("tableName") String tableName);
<!--List<User> selectUserByTableName(@Param("tableName") String tableName)-->
<!--同样,由于表名不是字符串,不需要单引号来进行拼接,所以动态设置表名也只能使用${}的方式-->
<select id="selectUserByTableName" resultType="User">
    select * from ${tableName}
</select>
@Test
public void selectUserByTableName() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class);
    List<User> userList = sqlMapper.selectUserByTableName("t_user");
    System.out.println(userList);
    //[User{id=1, username='admin', password='123456', age=23, gender='女', email='12345@qq.com'}, User{id=3, username='zhangsan', password='123456', age=23, gender='女', email='12345@qq.com'}, User{id=4, username='lisi', password='123456', age=20, gender='男', email='123456@qq.com'}]
}

3.5.4 添加功能中获取自增的主键

应用场景:当我们新添加一个用户后,需要获取到这个用户的id,来进行其他操作,此时我们就可以通过这个功能来实现

//添加一个新用户
Integer insertUser(User user);
<!--Integer insertUser(User user)-->
<!--useGeneratedKeys属性表示当前这个sql使用到了自动递增的主键;keyProperty表示将自增主键配置到传入的实体类对象参数的哪个属性-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values (null, #{username}, #{password}, #{age}, #{gender}, #{email})
</insert>
@Test
public void insertUser() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class);
    User user = new User();
    user.setId(null);
    user.setUsername("wangwu");
    user.setPassword("123456");
    user.setAge(32);
    user.setGender("男");
    user.setEmail("123456@qq.com");
    Integer insertUser = sqlMapper.insertUser(user);
    System.out.println(insertUser);//1
    System.out.println(user);//User{id=7, username='wangwu', password='123456', age=32, gender='男', email='123456@qq.com'},可以发现新添加的用户的id被赋值了
}

3.6 自定义映射ResultMap

3.6.1 解决字段名和属性名的映射关系

首先,我们新建两张数据表

员工表:

部门表:

然后,随便添加几条数据

然后,新建对应实体类

public class Employee {
    private Integer eid;
    private String employeeName;
    private Integer employeeAge;
    private String employeeGender;
    private String employeeEmail;
    private Integer did;
    //省略构造方法、set、get、toString方法
}
public class Department {
    private Integer did;
    private String departmentName;
    //省略构造方法、set、get、toString方法
}

然后,编写mapper接口和映射文件进行测试

//获取所有员工信息
List<Employee> getAllEmployee();
<!--List<Employee> getAllEmployee()-->
<select id="getAllEmployee" resultType="Employee">
    select * from t_employee
</select>
@Test
public void getAllEmployee() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> allEmployee = employeeMapper.getAllEmployee();
    allEmployee.forEach(employee -> System.out.println(employee));
    //Employee{eid=1, employeeName='null', employeeAge='null', employeeGender='null', employeeEmail='null', did=1}
    //Employee{eid=2, employeeName='null', employeeAge='null', employeeGender='null', employeeEmail='null', did=1}
    //Employee{eid=3, employeeName='null', employeeAge='null', employeeGender='null', employeeEmail='null', did=2}
    //Employee{eid=4, employeeName='null', employeeAge='null', employeeGender='null', employeeEmail='null', did=2}
}

可以发现,由于我们的实体类的部分属性名和数据库字段名不匹配,输出的实体类对象对应的属性就没有被赋值

①解决方法一

在查询时,给数据库字段取别名来让字段名和属性名映射一致

<!--List<Employee> getAllEmployee()-->
<select id="getAllEmployee" resultType="Employee">
    select eid,employee_name employeeName,employee_age employeeAge,employee_gender employeeGender,employee_email employeeEmail,did from t_employee
</select>
@Test
public void getAllEmployee() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> allEmployee = employeeMapper.getAllEmployee();
    allEmployee.forEach(employee -> System.out.println(employee));
    //Employee{eid=1, employeeName='zhangsan', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', did=1}
    //Employee{eid=2, employeeName='lisi', employeeAge='24', employeeGender='男', employeeEmail='12345@qq.com', did=1}
    //Employee{eid=3, employeeName='wangwu', employeeAge='25', employeeGender='女', employeeEmail='12345@qq.com', did=2}
    //Employee{eid=4, employeeName='刘六', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', did=2}
}

通过测试发现,实体类对象对应的属性都被赋值了

但是这种方法当字段名太多的情况下就显得非常麻烦,所以不建议使用

②解决方法二

在mybatis全局配置文件中配置一个**mapUnderscoreToCamelCase**即可

<!--注意setting配置的位置在properties和typeAliases之间-->
<settings>
    <!--此时,employee_name就会被解析成employeeName-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
③解决方法三

通过自定义resultMap来解决

采用resultMap自定义映射进行配置时,无论字段名和属性名是否一致,建议将所有字段名和属性名的映射都进行配置,当然,如果我们确实用不到的属性也可以不配置

<!--通过如下自定义resultMap的设置就可以解决字段名和属性名映射问题了-->
<resultMap id="employeeResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
    <result property="did" column="did"/>
</resultMap>
<!--List<Employee> getAllEmployee()-->
<select id="getAllEmployee" resultMap="employeeResultMap">
    select * from t_employee
</select>

resultMap:设置自定义映射,其属性:

  • id:表示自定义映射的唯一标识
  • type:查询的数据要映射的实体类的类型

子标签:

  • id:设置主键的映射关系
  • result:设置普通字段的映射关系
  • association:设置多对一的映射关系
  • collection:设置一对多的映射关系
  • 属性:
  • property:设置映射关系中实体类中的属性名
  • column:设置映射关系中表中的字段名
  • javaType:association中的属性,用于为配置对象类型的属性指定具体对象类型

3.6.2 多对一映射处理

实际情况中,我们查询出的员工信息中会包含其部门的一些相关信息而非只有部门id;同样,当我们查询一个部门的信息时,我们应该可以看到一个部门中有哪些员工;此时就涉及到了实体类之间的关联关系,那么数据库表也会存在相同的关联关系,而多个不同员工可以属于同一个部门就是多对一关系

首先,我们需要改变实体类的属性设置

public class Employee {
    private Integer eid;
    private String employeeName;
    private Integer employeeAge;
    private String employeeGender;
    private String employeeEmail;
    private Department department;
}
public class Department {
    private Integer did;
    private String departmentName;
    private List<Employee> employeeList;
}
//均省略构造方法、set、get、toString方法
①级联属性赋值方式处理

将查询出来的信息的表字段名通过对象.属性名的方式进行配置映射

//获取所有员工及其对应部门信息
List<Employee> getEmployeeAndDepartment();
<!--Employee getEmployeeAndDepartment()-->
<resultMap id="EmployeeAndDepartmentResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
    <!--将查询出来的信息的表字段名通过"对象.属性名"的方式进行配置映射-->
    <result property="department.did" column="did"/>
    <result property="department.departmentName" column="department_name"/>
</resultMap>
<select id="getEmployeeAndDepartment" resultMap="EmployeeAndDepartmentResultMap">
    select * from t_employee left join t_department on t_employee.did = t_department.did
</select>
@Test
public void getEmployeeAndDepartment() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> employeeAndDepartment = employeeMapper.getEmployeeAndDepartment();
    employeeAndDepartment.forEach(employee -> System.out.println(employee));
    //Employee{eid=1, employeeName='zhangsan', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', department=Department{did=1, departmentName='开发部', employeeList=null}}
    //Employee{eid=2, employeeName='lisi', employeeAge='24', employeeGender='男', employeeEmail='12345@qq.com', department=Department{did=1, departmentName='开发部', employeeList=null}}
    //Employee{eid=3, employeeName='wangwu', employeeAge='25', employeeGender='女', employeeEmail='12345@qq.com', department=Department{did=2, departmentName='运营部', employeeList=null}}
    //Employee{eid=4, employeeName='刘六', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', department=Department{did=2, departmentName='运营部', employeeList=null}}
}
②使用association处理

通过association标签先为对象类型的属性进行映射配置,然后再将这个对象类型属性赋值给我们需要查询的对象

//获取所有员工及其对应部门信息
List<Employee> getEmployeeAndDepartment();
<!--Employee getEmployeeAndDepartment()-->
<resultMap id="EmployeeAndDepartmentResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
    <!--通过"association"标签先为对象类型的属性进行映射配置,然后再将这个对象类型属性赋值给我们需要查询的对象-->
    <!--
        association:处理多对一的映射关系
        property:需要处理多对一映射关系的属性名
        javaType:该属性的类型
    -->
    <association property="department" javaType="Department">
        <id property="did" column="did"/>
        <result property="departmentName" column="department_name"/>
    </association>
</resultMap>
<select id="getEmployeeAndDepartment" resultMap="EmployeeAndDepartmentResultMap">
    select * from t_employee left join t_department on t_employee.did = t_department.did
</select>

经过测试发现,查询结果一样

③分步查询处理

先将一个我们需要查询的信息查询出来,然后再通过查询出来得到的信息去查询另一个信息

在本例中,我们先把员工的信息查询出来(其中包含了其所在部门的id即did),然后再通过查询出来的部门id去查询对应部门,再将查询出的部门赋值给员工信息中的部门对象属性

//EmployeeMapper接口
//查询员工及其部门信息
List<Employee> getEmployeeAndDepartment();

//DepartmentMapper接口
//根据部门d查询对应部门
Department getDepartmentById(@Param("did") Integer did);
<!--EmployeeMapper中-->
<!--Employee getEmployeeAndDepartment()-->
<resultMap id="EmployeeAndDepartmentResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
    <!--
        select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId),即我们要使用哪个mapper映射文件中的哪个方法进行查询
        column:将sql以及查询结果中的某个字段设置为分步查询的条件,这儿就代表我们将员工信息查询结果的did作为下一步查询部门的条件
    -->
    <association property="department" select="com.mybatis.mapper.DepartmentMapper.getDepartmentById" column="did"></association>
</resultMap>
<select id="getEmployeeAndDepartment" resultMap="EmployeeAndDepartmentResultMap">
    select * from t_employee
</select>
<!--DepartmentMapper中-->
<!--Department getDepartmentById(@Param("did") Integer did)-->
<resultMap id="departmentResultMap" type="Department">
    <id property="did" column="did"/>
    <result property="departmentName" column="department_name"/>
    <!--这儿我们暂时不需要Department对象中的员工集合employeeList,所以不用配置映射-->
</resultMap>
<select id="getDepartmentById" resultMap="departmentResultMap">
    select * from t_department where did = #{did}
</select>

经过测试发现,查询结果一样

这种方式虽然看起来比较麻烦,但是在其他的mapper中编写查询语句是可以复用的,比如我们这儿新增的根据部门ID查询对应部门信息的方法不仅可以和查询员工信息的功能合起来使用还可以作为单独一个功能使用

④延迟加载功能

在mybatis核心配置文件中设置全局配置信息:

<settings>
    <!--配置延迟加载功能-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象(即我们在分布查询中的第二步、第三步等)都会延迟加载

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性或是说都会执行分布查询所有步骤(当mybatis版本<=3.4.1时该项默认为true)

配置好延迟加载时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql

  • 比如我们只需要获取员工信息,那么就只会执行查询员工信息的sql,而不会继续执行查询部门信息的sql;同样,如果我们需要部门信息,那么它才会把员工信息查询出来后再根据部门id去查询对应部门信息
  • 如果有些需求不需要延迟加载,可通过associationcollection中的fetchType属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(延迟加载)|eager(立即加载)"

测试:

@Test
public void getEmployeeAndDepartment() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
    List<Employee> employeeAndDepartment = employeeMapper.getEmployeeAndDepartment();
    //此时我们只需要获取员工的名字
    employeeAndDepartment.forEach(employee -> System.out.println(employee.getEmployeeName()));
}

可以发现,开启延迟加载时,当我们只需获取员工名字,那么它只会执行分布查询中的第一步,即查询出员工信息

经过测试,当我们在mybatis-config.xml全局配置文件中开启延迟加载时在全局配置文件中关闭延迟加载但在mapper映射文件的某个方法中加上fetchType=lazy时都会出现以上情况(第二种情况视频老师是说的是必须要在全局配置文件开启延迟加载后设置fetchType才有用,但是是错的,我亲自测试仍然可以,至少在我使用的mybatis3.5.9版本是这样的)

而当关闭延迟加载后,它又会将分布查询所有步骤进行执行

经过测试,当我们在mybatis-config.xml全局配置文件中关闭延迟加载时在全局配置文件中开启延迟加载但在mapper映射文件的某个方法中加上fetchType=eager时也都会出现以上情况

总结:

  • 我们可以在全局配置文件开启延迟加载的情况下利用fetchType=eager使某一个方法不延迟加载,也可以在全局配置文件关闭延迟加载的情况下利用fetchType=lazy使某一个方法延迟加载
  • lazyLoadingEnabled是针对所有方法的延迟加载功能,fetchType是针对某一个方法的延迟加载功能

3.6.3 一对多映射处理

一个部门中有多个员工就是一对多关系

①使用collection处理
//通过部门id查询对应部门及其包含的员工
Department getDepartmentById(@Param("did") Integer did);
<!--Department getDepartmentById(@Param("did") Integer did)-->
<resultMap id="departmentResultMap" type="Department">
    <id property="did" column="did"/>
    <result property="departmentName" column="department_name"/>
    <!--
        ofType:设置collection标签所处理的集合属性中存储数据的类型
    -->
    <collection property="employeeList" ofType="Employee">
        <id property="eid" column="eid"/>
        <result property="employeeName" column="employee_name"/>
        <result property="employeeAge" column="employee_age"/>
        <result property="employeeGender" column="employee_gender"/>
        <result property="employeeEmail" column="employee_email"/>
    </collection>
</resultMap>
<select id="getDepartmentById" resultMap="departmentResultMap">
    select * from t_department left join t_employee on t_department.did = t_employee.did where t_department.did = #{did}
</select>
@Test
public void getDepartmentById() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
    Department department = departmentMapper.getDepartmentById(1);
    System.out.println(department);
    //Department{did=1, departmentName='开发部', employeeList=[Employee{eid=1, employeeName='zhangsan', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', department=null}, Employee{eid=2, employeeName='lisi', employeeAge='24', employeeGender='男', employeeEmail='12345@qq.com', department=null}]}
}
②分步查询
//DepartmentMapper接口中
//通过部门id查询对应部门及其包含的员工
Department getDepartmentById(@Param("did") Integer did);

//EmployeeMapper接口中
//根据部门id查询员工信息
List<Employee> getDepartmentAndEmployeeByDid(@Param("did") Integer did);
<!--Department getDepartmentById(@Param("did") Integer did)-->
<resultMap id="departmentResultMap" type="Department">
    <id property="did" column="did"/>
    <result property="departmentName" column="department_name"/>
    <!--
        select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId),即通过哪个方法查询
        column:将sql以及查询结果中的某个字段设置为分步查询的条件,即查询条件
        与多对一映射处理的分布查询一样的效果
    -->
    <collection property="employeeList" select="com.mybatis.mapper.EmployeeMapper.getDepartmentAndEmployeeByDid" column="did"></collection>
</resultMap>
<select id="getDepartmentById" resultMap="departmentResultMap">
    select * from t_department where did = #{did}
</select>
<!--List<Employee> getDepartmentAndEmployeeByDid(@Param("did") Integer did)-->
<resultMap id="departmentAndEmployeeByDidResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getDepartmentAndEmployeeByDid" resultMap="departmentAndEmployeeByDidResultMap">
    select * from t_employee where did = #{did}
</select>

经过测试发现,查询结果一样

同样,这儿也可以配置延迟加载的功能,具体参考多对一映射处理中的延迟加载功能部分

3.7 动态SQL

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

3.7.1 if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会被拼接到sql语句中;反之标签中的内容不会被拼接

//根据条件批量查询员工
List<Employee> getEmployeeListByIF(Employee employee);
<!--List<Employee> getEmployeeListByIF(Employee employee)-->
<resultMap id="employeeListResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getEmployeeListByIF" resultMap="employeeListResultMap">
    select * from t_employee where 1=1
    <if test="employeeName != null and employeeName != ''">
        and employee_name = #{employeeName}
    </if>
    <if test="employeeAge != null and employeeAge != ''">
        and employee_age = #{employeeAge}
    </if>
    <if test="employeeGender != null and employeeGender != ''">
        and employee_gender = #{employeeGender}
    </if>
    <if test="employeeEmail != null and employeeEmail != ''">
        and employee_email = #{employeeEmail}
    </if>
</select>
@Test
public void getEmployeeListByIF() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
    Employee employee = new Employee();
    employee.setEmployeeAge(23);
    employee.setEmployeeGender("男");
    employee.setEmployeeEmail("12345@qq.com");
    List<Employee> employeeList = mapper.getEmployeeListByIF(employee);
    System.out.println(employeeList);
    //[Employee{eid=1, employeeName='zhangsan', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', department=null}, Employee{eid=4, employeeName='刘六', employeeAge='23', employeeGender='男', employeeEmail='12345@qq.com', department=null}]
}

通过sql语句可以发现,值为null的属性名未被拼接到sql语句中

3.7.2 where

where和if一般结合使用:

  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉
  • 注意:where标签不能将sql语句中条件后面多余的and去掉

仍然使用上方的例子根据条件批量查询员工,但是<select>标签中where我们使用<where>标签代替

//根据条件批量查询员工
List<Employee> getEmployeeListByWhere(Employee employee);
<!--List<Employee> getEmployeeListByWhere(Employee employee)-->
<resultMap id="employeeListResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getEmployeeListByWhere" resultMap="employeeListResultMap">
    select * from t_employee
    <where>
        <if test="employeeName != null and employeeName != ''">
            and employee_name = #{employeeName}
        </if>
        <if test="employeeAge != null and employeeAge != ''">
            and employee_age = #{employeeAge}
        </if>
        <if test="employeeGender != null and employeeGender != ''">
            and employee_gender = #{employeeGender}
        </if>
        <if test="employeeEmail != null and employeeEmail != ''">
            and employee_email = #{employeeEmail}
        </if>
    </where>
</select>

通过测试发现,mybatis为sql语句动态添加了where子句,并且还为我们将条件中多余的and去掉了;当然,如果我们<where>标签中的条件都不成立,那么sql语句中也不会有where子句

3.7.3 trim

trim用于去掉或添加标签中的内容

常用属性:

  • prefix:在trim标签中的内容的前面添加某些内容
  • prefixOverrides:在trim标签中的内容的前面去掉某些内容
  • suffix:在trim标签中的内容的后面添加某些内容
  • suffixOverrides:在trim标签中的内容的后面去掉某些内容

仍用上方的例子根据条件批量查询员工

<!--List<Employee> getEmployeeListByTrim(Employee employee)-->
<resultMap id="employeeListResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getEmployeeListByTrim" resultMap="employeeListResultMap">
    select * from t_employee
    <trim prefix="where" suffixOverrides="and|or">
        <if test="employeeName != null and employeeName != ''">
            employee_name = #{employeeName} and
        </if>
        <if test="employeeAge != null and employeeAge != ''">
            employee_age = #{employeeAge} or
        </if>
        <if test="employeeGender != null and employeeGender != ''">
            employee_gender = #{employeeGender} and
        </if>
        <if test="employeeEmail != null and employeeEmail != ''">
            employee_email = #{employeeEmail}
        </if>
    </trim>
</select>

通过测试发现,当if中有满足的条件,则<trim>标签会自动拼接上前缀where,并且会自动去除设置的后缀重写的andor;而当if中没有满足的条件时,<trim>则不会添加前缀where

3.7.4 choose、when、otherwise

choose、when、otherwise相当于if…else if..else

when至少要有一个,otherwise最多只能有一个

当其中一个条件满足时其他条件就不再进行判断了,所以这类动态SQL拼接后最多只会有一个条件

这儿仍然使用上方的例子根据条件批量查询员工

<!--List<Employee> getEmployeeListByChoose(Employee employee)-->
<resultMap id="employeeListResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getEmployeeListByChoose" resultMap="employeeListResultMap">
    select * from t_employee
    <where>
        <choose>
            <when test="employeeName != null and employeeName != ''">
                employee_name = #{employeeName}
            </when>
            <when test="employeeAge != null and employeeAge != ''">
                employee_age = #{employeeAge}
            </when>
            <when test="employeeGender != null and employeeGender != ''">
                employee_gender = #{employeeGender}
            </when>
            <when test="employeeEmail != null and employeeEmail != ''">
                employee_email = #{employeeEmail}
            </when>
            <otherwise>
                did = 1
            </otherwise>
        </choose>
    </where>
</select>

3.7.5 foreach

foreach标签的属性:

  • collection:设置要循环的数组或集合
  • item:表示集合或数组中的每一个数据
  • separator:设置循环体之间的分隔符
  • open:设置foreach标签中的内容的开始符
  • close:设置foreach标签中的内容的结束符
//批量添加新员工
Integer insertEmployeeList(@Param("employeeList") List<Employee> employeeList);
//批量删除员工
Integer deleteEmployeeByIds(@Param("eids") int[] eids);
<!--Integer insertEmployeeList(@Param("employeeList") List<Employee> employeeList)-->
<insert id="insertEmployeeList">
    insert into t_employee values 
    <foreach collection="employeeList" item="employee" separator=",">
        (null,#{employee.employeeName},#{employee.employeeAge},#{employee.employeeGender},#{employee.employeeEmail},null)
    </foreach>
</insert>
<!--Integer deleteEmployeeByIds(@Param("eids") int[] eids)-->
<!--第一种方式,使用in-->
<delete id="deleteEmployeeByIds">
    delete from t_employee where eid in 
    <foreach collection="eids" item="eid" separator="," open="(" close=")">
        #{eid}
    </foreach>
</delete>
<!--第二种方式,使用or-->
<delete id="deleteEmployeeByIds">
    delete from t_employee where 
    <foreach collection="eids" item="eid" separator="or">
        eid = #{eid}
    </foreach>
</delete>
@Test
public void insertEmployeeList() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
    List<Employee> employeeList = new ArrayList<>();
    employeeList.add(new Employee(null,"李四",30,"女","1234@qq.com",null));
    employeeList.add(new Employee(null,"王五",27,"男","1234@qq.com",null));
    Integer list = mapper.insertEmployeeList(employeeList);
    System.out.println(list);
}
@Test
public void deleteEmployeeByIds() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
    int[] eids = new int[]{5,6,7,8};//即删除eid为5、6、7、8的员工
    Integer delete = mapper.deleteEmployeeByIds(eids);
    System.out.println(delete);
}

批量添加新员工结果:

批量删除新员工结果:

由于这里我测试是添加了两条数据后eid为分别5和6,然后再测试的第一种删除方式,此时就把5和6删除了;然后我又添加了两条数据到表中,eid分别为7和8,再测试了第二种删除的方法,此时又把7和8删除了。所以这两种方式我这儿删除的结果影响行数都是2,正常情况下应该是5、6、7、8都会被删除,同时删除影响行数返回4

3.7.6 sql

sql片段,可以记录一段公共sql片段,在使用的地方通过include标签进行引入

通过<sql>标签记录一段公共sql片段,然后在需要的地方使用<include>进行引入

<!--定义sql片段-->
<sql id="employeeColumns">
    eid,employee_name,employee_age,employee_gender,employee_email
</sql>
<!--List<Employee> getEmployeeListByChoose(Employee employee)-->
<resultMap id="employeeListResultMap" type="Employee">
    <id property="eid" column="eid"/>
    <result property="employeeName" column="employee_name"/>
    <result property="employeeAge" column="employee_age"/>
    <result property="employeeGender" column="employee_gender"/>
    <result property="employeeEmail" column="employee_email"/>
</resultMap>
<select id="getEmployeeListByChoose" resultMap="employeeListResultMap">
    <!--引入sql片段,refid的值为被引入的sql片段的id-->
    select <include refid="employeeColumns"/> from t_employee
    <where>
        <choose>
            <when test="employeeName != null and employeeName != ''">
                employee_name = #{employeeName}
            </when>
            <when test="employeeAge != null and employeeAge != ''">
                employee_age = #{employeeAge}
            </when>
            <when test="employeeGender != null and employeeGender != ''">
                employee_gender = #{employeeGender}
            </when>
            <when test="employeeEmail != null and employeeEmail != ''">
                employee_email = #{employeeEmail}
            </when>
            <otherwise>
                did = 1
            </otherwise>
        </choose>
    </where>
</select>

四、Mybatis缓存

4.1 Mybatis一级缓存

4.1.1 一级缓存说明

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问

案例测试:

<!--Employee getEmployeeByID(@Param("eid") Integer eid)-->
<select id="getEmployeeByID" resultType="Employee">
    select * from t_employee where eid = #{eid}
</select>
@Test
public void getEmployeeByID() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    Employee employee = mapper.getEmployeeByID(1);
    System.out.println(employee);
    Employee employee2 = mapper.getEmployeeByID(1);
    System.out.println(employee2);
    //此时我们的employee和employee2都是查询eid为1的员工信息
}

通过测试结果发现,当我们两次都查询eid为1的员工时,sql语句只执行了一遍(因为我没配置对象属性和字段的映射,所以查询结果中那些属性都为null)

可能有小伙伴会说,这两个对象都是通过同一个mapper调用方法获取的,不能说明Mybatis一级缓存是SqlSession级别的,那么我们再创建一个新的mapper进行测试

@Test
public void getEmployeeByID() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    CacheMapper mapper2 = sqlSession.getMapper(CacheMapper.class);
    Employee employee = mapper.getEmployeeByID(1);
    System.out.println(employee);
    Employee employee2 = mapper2.getEmployeeByID(1);
    System.out.println(employee2);
}

经过测试,sql语句仍然是只执行了一遍,说明同一个SqlSession创建的不同mapper查询的数据同样是从缓存中进行获取的

那么我们创建一个新的SqlSession,再通过这个对象创建一个mapper进行测试

@Test
public void getEmployeeByID() {
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    SqlSession sqlSession2 = SqlSessionUtils.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
    Employee employee = mapper.getEmployeeByID(1);
    System.out.println(employee);
    Employee employee2 = mapper2.getEmployeeByID(1);
    System.out.println(employee2);
}

经过测试发现,这次sql语句就执行了两次,原因是我们调用这两次查询方法的mapper是来自不同SqlSession对象创建的,所以就不存在缓存一说了

总结:Mybatis一级缓存是SqlSession级别的

4.1.2 一级缓存失效的四种情况

  • 不同的SqlSession对应不同的一级缓存(即我们上面做的案例)
  • 同一个SqlSession但是查询条件不同
  • 同一个SqlSession两次查询期间执行了任何一次增删改操作
  • 同一个SqlSession两次查询期间手动清空了缓存(执行sqlsession.clearCache()方法)

4.2 Mybatis二级缓存

4.2.1 二级缓存说明

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取

由于SqlSession是由SqlSessionFactory创建出来的,所以二级缓存的范围也就比一级缓存大,即同一个SqlSessionFactory创建出来的不同SqlSession中的查询都可以共用二级缓存中的数据;而一级缓存中的数据只能是同一个SqlSession中的查询才能共用

二级缓存开启的条件:

  • 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在SqlSession关闭或提交之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

案例测试:

<!--首先按照开启条件在映射文件中添加<cache/>标签-->
<cache/>
//查询数据对应的实体类实现序列化的接口
public class Employee implements Serializable {}
@Test
public void testTwoCache(){
    try {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
        Employee employee1 = mapper1.getEmployeeByID(1);
        Employee employee2 = mapper2.getEmployeeByID(1);
        System.out.println(employee1);
        System.out.println(employee2);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

以上测试中,我们没有将SqlSession关闭,通过测试结果发现,sql语句执行了两次,表明两次查询都是从数据库查询出来的

这时,可能有人会说,我们不是设置了自动提交吗,满足开启二级缓存的条件为什么还是没有使用二级缓存?我看弹幕有人说是因为SqlSession的自动提交是在整个程序运行完之后,所以就没有完全满足开启二级缓存的条件,那么第一次查询到结果后并没有将结果放到二级缓存中,第二次查询就也会从数据库中去查询

于是,我将SqlSession自动提交关闭了,进行了手动提交(不关闭自动提交,手动关闭SqlSession也可以)

@Test
public void testTwoCache(){
    try {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
        Employee employee1 = mapper1.getEmployeeByID(1);
        sqlSession1.commit();
        Employee employee2 = mapper2.getEmployeeByID(1);
        sqlSession2.commit();
        System.out.println(employee1);
        System.out.println(employee2);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

通过测试结果发现,sql语句只执行了一次,说明从数据库查询只进行了一次,二级缓存开启成功

而且,通过图中倒数第三行可以发现,第二查询的缓存命中率为0.5,也说明了第二次查询是直接从缓存中获取的数据

4.2.2 二级缓存失效的情况

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

4.2.3 二级缓存相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

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

4.3 Mybatis缓存查询顺序

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

4.4 整合第三方缓存EHCache

了解即可,最终实现的功能都是一样的

4.4.1 添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.11</version>
</dependency>

导入依赖后的各个依赖jar包

4.4.2 各jar包功能

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

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

配置文件名必须为ehcache.xml

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- ehcache缓存的数据在磁盘中的保存路径 -->
    <diskStore path="D:\ehcache"/>
    
    <defaultCache
        maxElementsInMemory="1000"  
        maxElementsOnDisk="10000000"
        eternal="false"
        overflowToDisk="true"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

4.4.4 设置二级缓存的类型

mapper映射文件中设置<cache>的type属性,表示二级缓存的类型,默认为mybatis本身的二级缓存

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

4.4.5 加入logback日志

存在SLF4J时,作为简易日志的log4j将失效,此时我们需要借助SLF4J的具体实现logback来打印日志

创建logback的配置文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 指定日志输出的位置 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- 日志输出的格式 -->
            <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
            <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
        </encoder>
    </appender>
    
    <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
    <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
    <root level="DEBUG">
        <!-- 指定打印日志的appender,这里通过"STDOUT"引用了前面配置的appender -->
        <appender-ref ref="STDOUT" />
    </root>
    
    <!-- 根据特殊需求指定局部日志级别 -->
    <logger name="com.atguigu.crowd.mapper" level="DEBUG"/>
</configuration>

4.4.6 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(先进先出)

五、Mybatis逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源

  • Java实体类
  • Mapper接口
  • Mapper映射文件

5.1 创建逆向工程的步骤

重新创建了一个新模块

5.1.1 添加依赖和插件

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
    <!--这个mysql依赖也得加上,不然等会儿QBC查询会报错。虽然在build中引入了插件所需要的mysql驱动,但是那个只是用于逆向生成时连接数据库使用的,这个是后面我们测试需要用的-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
</dependencies>

<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.4.1</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.4.1</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.5.5</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.28</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

5.1.2 创建mybatis-config.xml配置文件

创建mybatis全局配置文件mybatis-config.xml和数据库配置文件jdbc.properties

配置内容根据自己需求进行增删改

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

<configuration>
    <!--引入properties文件-->
    <properties resource="jdbc.properties"/>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    <!--设置类型别名-->
    <typeAliases>
        <package name="com.mybatis.entity"/>
    </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="com.mybatis.mapper"/>
    </mappers>
</configuration>

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

文件名必须是generatorConfig.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
        targetRuntime: 执行生成的逆向工程的版本
            MyBatis3Simple: 生成基本的CRUD(简洁版)
            MyBatis3: 生成带条件的CRUD(完善版)
        指定生成的版本不同生成出来的文件内容也不同
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root" password="root"/>
        <!-- javaBean的生成策略-->
        <!--
            targetPackage:生成包
            targetProject:生成位置
            enableSubPackages:开启生成子包功能
            trimStrings:开启去除字段名中的空格
        -->
        <javaModelGenerator targetPackage="com.mybatis.entity" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.mybatis.mapper" targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <!--type:该属性用于选择一个预定义的客户端代码(可以理解为Mapper接口)生成器,用户可以自定义实现
            MyBatis3:
                ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
                MIXEDMAPPER:XML和注解的混合形式,(上面这种情况中的)SqlProvider注解方法会被XML替代
                XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
            MyBatis3Simple:
                ANNOTATEDMAPPER:基于注解的Mapper接口,不会有对应的XML映射文件
                XMLMAPPER:所有的方法都在XML中,接口调用依赖XML文件。
        -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.mybatis.mapper" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_employee" domainObjectName="Employee"/>
        <table tableName="t_department" domainObjectName="Department"/>
    </context>
</generatorConfiguration>

5.1.4 执行MBG插件的generate目标

执行mybatis-generator插件之前,除了配置文件以外没有其他文件

双击执行插件

执行mybatis-generator插件之后,java实体类、mapper接口、mapper映射文件都被生成出来了。这里就不展示文件中的内容了

生成的简洁版:

生成的完善版:在entity包中多了两个Example类,这个是用于条件查询的类,里面包含了条件查询的各种方法,并且mapper接口文件和mapper映射文件中的内容也更丰富完善

5.2 QBC查询

QBC(Query By Condition):根据条件查询

//这里只测试部分功能,其他就可以自己继续研究
@Test
public void testQBC(){
    try {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        //查询所有数据
        List<Employee> employees = employeeMapper.selectByExample(null);
        employees.forEach(employee -> System.out.println(employee));
        //根据条件查询
        EmployeeExample employeeExample = new EmployeeExample();
        //下面这些方法就是Example类中的方法,这些方法都是逆向工程根据配置自动生成的
        //andEmployeeNameEqualTo:员工名等于?
        //andEmployeeAgeGreaterThanOrEqualTo:员工年龄大于等于?
        //or():或者
        //andDidIsNotNull:员工所属部门id不为null
        employeeExample.createCriteria().andEmployeeNameEqualTo("zhangsan").andEmployeeAgeGreaterThanOrEqualTo(20);
        employeeExample.or().andDidIsNotNull();
        List<Employee> employees1 = employeeMapper.selectByExample(employeeExample);
        employees1.forEach(employee1 -> System.out.println(employee1));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

通过测试发现,sql语句中会自动帮我们拼接好条件并格式化进行查询

感受:这个功能虽然非常快捷方便,但是我认为初学者还是应该自己搭建这些环境,等到以后足够熟悉了,只需要注重业务时再用比较好

六、Mybatis分页插件

6.1 分页插件的使用步骤

6.1.1 添加依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.0</version>
</dependency>

6.1.2 配置分页插件

MyBatis的核心配置文件中配置插件

<!--注意位置-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

6.2 分页插件的使用

MySQL中指定查询位置及条数的语法:limit index, pagesize,也通常用于分页

index:当前页的起始索引

pagesize:每页显示的条数

pageNum:当前页的页码

index=( pageNum-1 )*pagesize

分页插件Pagehelper的使用:

  1. 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
    • pageNum:当前页的页码
    • pageSize:每页显示的条数
  2. 在查询获取list集合之后,可以使用PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int navigatePages)获取分页相关数据
    • list:分页之后的数据
    • navigatePages:导航分页的页码数(比如,我们当前在第5页,而指定的navigatePages为5,那么此时可以显示出来的页码就是3、4、5、6、7这5个页码,其余页码都用...代替)
    • 类似这种功能:

测试:

@Test
public void testPageHelper(){
    try {
        InputStream resource = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        PageHelper.startPage(1, 3);//开启分页
        //查询所有数据
        List<Employee> employees = employeeMapper.selectByExample(null);
        employees.forEach(employee -> System.out.println(employee));
        PageInfo<Employee> pageInfo = new PageInfo<>(employees,4);//通过PageInfo对象获取页面相关信息,4为我们指定的导航页显示的页码个数
        System.out.println(pageInfo);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

此时,PageInfo对象为:

PageInfo{pageNum=1, pageSize=3, size=3, startRow=1, endRow=3, total=13, pages=5, list=Page{count=true, pageNum=1, pageSize=3, startRow=0, endRow=3, total=13, pages=5, reasonable=false, pageSizeZero=false}

中间这部分是我们查询到的数据

prePage=0, nextPage=2, isFirstPage=true, isLastPage=false, hasPreviousPage=false, hasNextPage=true, navigatePages=4, navigateFirstPage=1, navigateLastPage=4, navigatepageNums=[1, 2, 3, 4]}

此时的PageInfo对象为:

PageInfo{pageNum=3, pageSize=3, size=3, startRow=7, endRow=9, total=13, pages=5, list=Page{count=true, pageNum=3, pageSize=3, startRow=6, endRow=9, total=13, pages=5, reasonable=false, pageSizeZero=false}

中间部分仍是我们查询到的数据

prePage=2, nextPage=4, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=4, navigateFirstPage=1, navigateLastPage=4, navigatepageNums=[1, 2, 3, 4]}

PageInfo对象中,常用的数据:

pageNum:当前页的页码

pageSize:每页显示的条数

size:当前页显示的真实条数

total:总记录数(即我们数据库中的数据条数)

pages:总页数(由我们的总记录数计算得到)

prePage:上一页的页码(由当前页的页码计算得到)

nextPage:下一页的页码(由当前页的页码计算得到)

isFirstPage/isLastPage:是否为第一页/最后一页

hasPreviousPage/hasNextPage:是否存在上一页/下一页

navigatePages:导航分页的页码数

navigateFirstPage:导航分页要显示出来的第一页的页码

navigateLastPage:导航分页要显示出来的最后一页的页码

navigatepageNums:导航分页的页码,[1,2,3,4],即我们会先显示出来的页码

评论