转载

MyBatis使用详解

准备写一个系列文章,MyBatis作为JavaEE框架开发的入门技术,所以作为开篇吧。

后续会推出MyBatisPlus、Spring、SpringMVC、Oracle、Maven、ElasticSearch、SpringBoot、SpringCloud、RabbitMQ、Nginx...等等,涉及到常用的JavaEE开发技术。

系列文章是用来记录自己在实际开发中常用的技术和内容,也可以做回顾,有遗漏的地方可以留言,待我记录后及时补充。

才疏学浅,写的文章难免有出错的地方,还望指正,大家相互进步。:smile:

​ ——现役码农 2019-12-17

什么是框架?

它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。

使用框架的好处:框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率。

MyBatis是解决持久层问题,SpringMVC是解决表现层问题。

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。

简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。

框架一般处在低层应用平台(如 J2EE )和高层业务逻辑之间的中间层。

软件开发的分层重要性: ​ 框架的重要性在于它实现了部分功能,并且能够很好的将低层应用平台和高层业务逻辑进行了缓和。为了实现软件工程中的“高内聚、低耦合”。把问题划分开来各个解决,易于控制,易于延展,易于分配资源。我们常见的MVC 软件设计思想就是很好的分层思想。

三层架构

MyBatis使用详解

表现层:是用于展示数据的(SpringMVC)

业务层:是处理业务需求

持久层:是和数据库交互的(MyBatis)

常见的 JavaEE 开发框架:

1 、解决数据的持久化问题的框架——MyBatis MyBatis:作为持久层的框架,还有一个封装程度更高的框架就是Hibernate,但这个框架因为各种原因目前在国内的流行程度下降太多,现在公司开发也越来越少使用。目前使用 Spring Data 来实现数据持久化也是一种趋势。 2、解决 WEB层问题的MVC框架——SpringMVC 3 、解决技术整合问题——Spring,Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架

什么是 MyBatis ?

MyBatis 是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

mybatis是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

mybatis通过 xml 或注解的方式将要执行的各种statement配置起来,并通过java对象和statement 中sql 的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。

采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

为了我们能够更好掌握框架运行的内部过程,并且有更好的体验,下面我们将从自定义 Mybatis 框架开始来学习框架。此时我们将会体验框架从无到有的过程体验,也能够很好的综合前面阶段所学的基础。

JDBC问题分析

用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作:

  • 准备数据

    -- ----------------------------
    -- Table structure for t_user
    -- ----------------------------
    DROP TABLE IF EXISTS `t_user`;
    CREATE TABLE `t_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(32) NOT NULL,
      `password` varchar(32) NOT NULL,
      `birthday` date DEFAULT NULL,
      `sex` bit(1) DEFAULT NULL,
      `address` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `username` (`username`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of t_user
    -- ----------------------------
    INSERT INTO `t_user` VALUES ('1', 'AA', '123456', '2019-12-13', '', '北京');
    INSERT INTO `t_user` VALUES ('2', 'BB', '123456', '2019-12-10', '/0', '上海');
    INSERT INTO `t_user` VALUES ('3', 'CC', '123456', '2019-12-04', '', '广州');
    复制代码
MyBatis使用详解
MyBatis使用详解

Java代码:

/**
 * 用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作
 */
public class JdbcDemo {
    public static void main(String[] args) {

        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet resultSet = null;
        try {
            //1. 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2. 获取连接对象
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC", "root", "123456");
            //3. 准备SQL语句
            String sql = "select * from t_user";
            //4. 获取预处理statement
            ps = conn.prepareStatement(sql);
            //5.执行SQL语句
            resultSet = ps.executeQuery();
            //6.处理结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                String password = resultSet.getString("password");
                Date birthday = resultSet.getDate("birthday");
                boolean sex = resultSet.getBoolean("sex");
                String address = resultSet.getString("address");
                //System.out.println(id + "--" + username + "--" + password + "--" + birthday + "--" + sex + "--" + address);
                User user = new User(id, username, password, birthday, sex, address);
                System.out.println(user);
            }

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            //7. 释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                    resultSet = null;
                }
            }
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                    ps = null;
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                    conn = null;
                }
            }
        }


    }
}
复制代码

User实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -2661131928069328685L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
}
复制代码

输出日志:

User(id=1, username=AA, password=123456, birthday=2019-12-13, sex=true, address=北京)
User(id=2, username=BB, password=123456, birthday=2019-12-10, sex=false, address=上海)
User(id=3, username=CC, password=123456, birthday=2019-12-04, sex=true, address=广州)
复制代码

补充:

  • Lombok使用, blog.csdn.net/ThinkWon/ar…

  • 自动生成 serialVersionUID 的设置, www.cnblogs.com/godtrue/p/7…

  • 为了使MySQL JDBC驱动程序的5.1.33版本与UTC时区配合使用,必须在连接字符串中明确指定serverTimezone

    jdbc:mysql://localhost:3306/mybatis?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    复制代码
  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可以解决此问题;
  2. Sql语句在代码中硬编码,造成代码不易维护,实际应用Sql变化可能较大,Sql变动需要修改java代码;
  3. 使用PreparedStatement向占位符传参存在硬编码,因为Sql语句的where条件不一定,可能多也可能少,修改Sql还要修改代码,系统不易维护;
  4. 对结果集解析存在硬编码(查询列名),Sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

MyBatis框架概述

  1. MyBatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费经历去处理加载驱动、创建连接、创建statement等繁杂过程;
  2. MyBatis通过xml或注解的形式将要执行的各种statement配置起来,并通过java对象和statement中的sql的动态参数进行映射生成最终执行的sql语句,最后由MyBatis框架执行sql并将结果映射为java对象并返回;
  3. 采用ORM思想解决了实体和数据库映射的问题,对jdbc进行了封装,屏蔽了jdbc api底层访问细节,使我们不用与jdbc api打交道,就可以完成对数据库的持久化操作。Mybatis的原名 ibatis,属于半orm框架,而Hibernate属于orm框架。

它使用了ORM思想实现了结果集的封装,Object Relational Mappging 对象关系映射,简单的说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表。

MyBatis快速入门

环境参数

  • 文档创建时间:2019-12-12

  • 开发平台:Windows

  • JDK版本:JDK1.8.0_131

  • 开发工具:IntelliJ IDEA 2019.3

    IntelliJ IDEA 2019.3 (Ultimate Edition) Build #IU-193.5233.84, built on November 25, 2019
    

Mybatis的环境搭建步骤

XML版本

  1. idea创建一个不用骨架的java maven项目,导入坐标
  2. 准备数据,sql语句
  3. 创建user实体类,实现Serializable,属性名和数据库表名保持一致
  4. 创建一个IUserDao接口,或者叫IUserMapper,用户的持久层接口,增删改查功能
  5. 在resources目录下创建一个Mybatis的主配置文件SqlMapConifg.xml,指定映射配置文件的位置
  6. 创建映射配置文件IUserMapper.xml,目录和接口IUserDao(IUserMapper)保持一致
  7. 编写测试类
    1. 读取配置文件
    2. 创建SqlSessionFactory工厂
    3. 创建SqlSession
    4. 创建Dao接口的代理对象
    5. 执行dao中的方法
    6. 释放资源

注解版本

  1. idea创建一个不用骨架的java maven项目,导入坐标
  2. 准备数据,sql语句
  3. 创建student实体类,实现Serializable,属性名和数据库表名保持一致
  4. 创建一个IStudentDao接口,或者叫IUserMapper,用户的持久层接口,使用注解实现增删改查功能**@Select**
  5. 在resources目录下创建一个Mybatis的主配置文件SqlMapConifg.xml,指定映射配置文件的位置, 使用classs属性
  6. 创建映射配置文件IUserMapper.xml,目录和接口IUserDao(IUserMapper)保持一致
  7. 编写测试类
    1. 读取配置文件
    2. 创建SqlSessionFactory工厂
    3. 创建SqlSession
    4. 创建Dao接口的代理对象
    5. 执行dao中的方法
    6. 释放资源

MyBatis的CRUD操作(xml版本)

先放上完整的项目目录结构图:

MyBatis使用详解

1. 创建不用骨架的java maven项目,导入坐标

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

复制代码

2. 准备数据,sql语句

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) NOT NULL,
  `password` varchar(32) NOT NULL,
  `birthday` date DEFAULT NULL,
  `sex` bit(1) DEFAULT NULL,
  `address` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES ('1', 'AA', '123456', '2019-12-13', '', '北京');
INSERT INTO `t_user` VALUES ('2', 'BB', '123456', '2019-12-10', '/0', '上海');
INSERT INTO `t_user` VALUES ('3', 'CC', '123456', '2019-12-04', '', '广州');
复制代码

3. 创建user实体类,实现Serializable,属性名和数据库表名保持一致

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -2661131928069328685L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
}
复制代码

4. 创建一个IUserDao接口,或者叫IUserMapper,用户的持久层接口

public interface IUserDao {

    /**
     * 查询所有
     *
     * @return 返回用户集合
     */
    List<User> findAll();
}
复制代码

5. 在resources目录下创建一个Mybatis的主配置文件SqlMapConifg.xml,指定映射配置文件的位置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="development">
        <!--配置mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/ivyzh/dao/IUserMapper.xml"/>
    </mappers>
</configuration>
复制代码

6. 创建映射配置文件IUserMapper.xml,目录和接口IUserDao(IUserMapper)保持一致

<?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.ivyzh.dao.IUserDao">
    <select id="findAll" resultType="com.ivyzh.domain.User">
        select * from t_user
    </select>
</mapper>
复制代码

7. 编写测试类

/**
 * MyBatis快速入门
 */
public class MyBatisQuickStart {
    public static void main(String[] args) throws Exception {

        //1. 读取配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        IUserDao userMapper = sqlSession.getMapper(IUserDao.class);
        //5. 使用代理对象执行方法
        List<User> users = userMapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

8. 输出日志

User(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京)
User(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海)
User(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州)
复制代码

注意事项

  • 解决org.apache.ibatis.binding.BindingException: Type interface xxx is not known to the MapperRegistry
    • 解决方案:检查定义的接口和实体类映射的接口名是否一致
    • 参考: blog.csdn.net/zeal9s/arti…
MyBatis使用详解
  • log4j:WARN No appenders could be found for logger 解决办法
    • MyBatis要配合log4j使用
    • 参考: blog.csdn.net/chw0629/art…
    • Log4j入门教程参考: blog.csdn.net/qq_35787138…

环境搭建的注意事项:

第一个:创建IUserDao.xml (IUserMapper.xml)和 IUserDao.java时名称是为了和我们之前的知识保持一致。

​ 在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper

​ 所以:IUserDao 和 IUserMapper是一样的

第二个:在idea中创建目录的时候,它和包是不一样的

​ 包在创建时:com.ivyzh.dao它是三级结构

​ 目录在创建时:com.ivyzh.dao是一级目录

第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同

第四个:映射配置文件的mapper标签 namespace属性的取值必须是dao接口的全限定类名

第五个:映射配置文件的操作配置(select), id属性的取值必须是dao接口的方法名

当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。

小结

通过快速入门示例,我们发现使用 mybatis 是非常容易的一件事情,因为只需要编写 Dao 接口 并且按照mybatis 要求编写 两个配置文件(SqlMapCofig.xml和IUserMapper.xml) ,就可以实现功能。远比我们之前的 jdbc 方便多了。(我们使用注解之后,将变得更为简单,只需要编写一个 mybatis 配置文件(SqlMapCofig.xml)就够了。)

但是,这里面包含了许多细节,比如为什么会有工厂对象(SqlSessionFactory),为什么有了工厂之后还要有构建者对象(SqlSessionFactoryBuilder),为什么 IUserDao.xml 在创建时有位置和文件名的要求等等。

这些问题我们在自定义 mybatis 框架的章节,通过层层剥离的方式,给大家讲解。

请注意:我们讲解自定义 Mybatis 框架,不是让大家回去自己去写个 mybatis,而是让我们能更好的了解mybatis 内部是怎么执行的,在以后的开发中能更好的使用 mybatis 框架,同时对它的设计理念(设计模式)有一个认识。

MyBatis的CRUD操作(注解版本)

注:为了演示使用注解版,我们换操作另外一张表t_student,里面的数据和结构和t_user是一模一样的。

先放目录结构和用到的类:

MyBatis使用详解

1. idea创建一个不用骨架的java maven项目,导入坐标

2. 准备数据,sql语句

3. 创建student实体类,实现Serializable,属性名和数据库表名保持一致

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
    private static final long serialVersionUID = -4227290463847762031L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
}
复制代码

4. 创建一个IStudentDao接口,或者叫IUserMapper,用户的持久层接口,使用注解实现增删改查功能**@Select**

public interface IStudentDao {
    /**
     * 查询所有
     *
     * @return 返回学生集合
     */
    @Select("select * from t_student")
    List<Student> findAll();
}
复制代码

5. 在resources目录下创建一个Mybatis的主配置文件SqlMapConifg.xml,指定映射配置文件的位置,使用classs属性

如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名:

<?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">
<!--mybatis主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="development">
        <!--配置mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/ivyzh/dao/IUserMapper.xml"/>
        <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
        <mapper class="com.ivyzh.dao.IStudentDao"/>
    </mappers>
</configuration>
复制代码

6. 创建映射配置文件IUserMapper.xml,目录和接口IUserDao(IUserMapper)保持一致

使用注解方式,没有这个步骤。

7. 编写测试类

/**
 * MyBatis快速入门-注解形式
 */
public class StudentTest {
    public static void main(String[] args) throws Exception {

        //1. 读取配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //4. 使用SqlSession创建Dao接口的代理对象
        IStudentDao studentMapper = sqlSession.getMapper(IStudentDao.class);
        //5. 使用代理对象执行方法
        List<Student> students = studentMapper.findAll();
        for (Student student : students) {
            System.out.println(student);
        }
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

8. 输出日志

Student(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京)
Student(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海)
Student(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州)
复制代码

小结

  • 比xml形式省事好多:不用写select标签,配置id,也不用写封装结果resultType
  • 把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句
  • 同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名

MyBatis的CRUD操作(自己写实现类的方式)

常用MyBatis的CRUD方式XML和注解都已经介绍过了,还有一种是dao实现类方式实现CRUD,这种方式不常用,只是证明有这种方式,下面简单演示一下,就以User案例。

1. 创建IUserDao的实现类UserDaoImpl

public class UserDaoImpl implements IUserDao {

    private SqlSessionFactory factory;

    public UserDaoImpl(SqlSessionFactory factory) {
        this.factory = factory;
    }

    @Override
    public List<User> findAll() {
        //使用工厂创建SqlSession对象
        SqlSession sqlSession = factory.openSession();
        //使用sqlSession执行查询所有方法
        List<User> users = sqlSession.selectList("com.ivyzh.dao.IUserDao.findAll");
        sqlSession.close();
        //返回结果集
        return users;
    }
}
复制代码

2. 测试方法

/**
 * MyBatis快速入门-Dao实现类形式
 */
public class UserImplTest {
    public static void main(String[] args) throws Exception {

        //1. 读取配置文件
        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 创建IUserDao实现类对象
        IUserDao userDao = new UserDaoImpl(factory);
        //4. 调用查询方法
        List<User> users = userDao.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //5. 释放资源
        is.close();
    }
}
复制代码

3. 输出日志

User(id=1, username=AA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京)
User(id=2, username=BB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海)
User(id=3, username=CC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州)
复制代码

完整版MyBatis的CRUD操作(基于xml版本)

上面的3种实现方式(XML、注解、dao实现)只是实现了findAll方法,接下来我们要真正实现增删改查功能了,是基于XML形式的,使用的表是Teacher了,数据格式和之前是User、Student是一模一样的,用户名改成TAA、TBB、TCC吧。

实现步骤就不具体列举了,直接贴代码

基础功能

  • 基本的增删改查包含如下功能
    • 查询所有
    • 新增
    • 删除
    • 查询单条记录
    • 获取总记录数
    • 模糊查询,两种方式
    • 扩展:新增用户id的返回

Teacher实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher implements Serializable {
    private static final long serialVersionUID = -4399789553021464706L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
}
复制代码

ITeacherDao实现类:

public interface ITeacherDao {

    /**
     * 查询所有
     *
     * @return 返回老师集合
     */
    List<Teacher> findAll();

    /**
     * 保存操作
     *
     * @param teacher 老师数据
     */
    void saveTeacher(Teacher teacher);

    /**
     * 保存操作:问题拓展,新增用户id的返回
     *
     * @param teacher 老师数据
     */
    void saveTeacher2(Teacher teacher);

    /**
     * 更新操作
     *
     * @param teacher 老师数据
     */
    void updateTeacher(Teacher teacher);

    /**
     * 删除操作
     *
     * @param id 编号id
     */
    void deleteTeacher(Integer id);

    /**
     * 查询一个,根据id查询
     *
     * @param id 用户id
     * @return 用户数据
     */
    Teacher findById(Integer id);

    /**
     * 模糊查询,根据用户名查询
     *
     * @param username 用户名
     * @return 用户数据列表
     */
    List<Teacher> findByName(String username);

    /**
     * 模糊查询,根据用户名查询,方式2
     *
     * @param username 用户名
     * @return 用户数据列表
     */
    List<Teacher> findByName2(String username);

    /**
     * 获取总记录数
     *
     * @return 总记录数
     */
    Integer findTotal();
}
复制代码

ITeacherMapper.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ivyzh.dao.ITeacherDao">
    <!--查询所有-->
    <select id="findAll" resultType="com.ivyzh.domain.Teacher">
         select * from t_teacher
    </select>

    <!--保存操作-->
    <insert id="saveTeacher" parameterType="com.ivyzh.domain.Teacher">
        insert into t_teacher(username,password,birthday,sex,address) values (#{username},#{password},#{birthday},#{sex},#{address})
    </insert>

    <!--保存操作,问题拓展,新增用户id的返回-->
    <insert id="saveTeacher2" parameterType="com.ivyzh.domain.Teacher">
        /*keyProperty:属性名,keyColumn数据库中字段名*/
        <selectKey keyProperty="id" keyColumn="id" resultType="java.lang.Integer" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into t_teacher(username,password,birthday,sex,address) values
        (#{username},#{password},#{birthday},#{sex},#{address})
    </insert>

    <!--更新操作-->
    <update id="updateTeacher" parameterType="com.ivyzh.domain.Teacher">
        update t_teacher set username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
    </update>
    <!--删除操作,只有一个参数的时候mapper可以随便写,这是一个占位符-->
    <delete id="deleteTeacher" parameterType="java.lang.Integer">
        delete from t_teacher where id=#{uid}
    </delete>
    <!--查询一个,根据id查询-->
    <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Teacher">
         select * from t_teacher where id=#{id}
    </select>

    <!--模糊查询方式1-->
    <select id="findByName" parameterType="java.lang.String" resultType="com.ivyzh.domain.Teacher">
         select * from t_teacher where username like #{username}
    </select>
    <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value-->
    <select id="findByName2" parameterType="java.lang.String" resultType="com.ivyzh.domain.Teacher">
         select * from t_teacher where username like '%${value}%'
    </select>

    <!--mybatis也支持聚合函数,支持返回一行一列,获取总记录数-->
    <select id="findTotal" resultType="java.lang.Integer">
         select count(id) from t_teacher
    </select>
</mapper>
复制代码

SqlMapConfig.xml文件:配置映射文件的位置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="development">
        <!--配置mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/ivyzh/dao/IUserMapper.xml"/>
        <mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/>
        <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
        <mapper class="com.ivyzh.dao.IStudentDao"/>
    </mappers>
</configuration>
复制代码

以上需要注意的地方有: findByName2saveTeacher2 两个方法,分别演示了模糊查询和新增用户id的返回问题。

入参深入-使用实体类的包装对象作为查询条件OGNL

parameterType类型:3种

  • 基础类型,如查询一个,parameterType="java.lang.Integer"

    • java.lang.Integer,可以写成Integer或者int都行
    <!--查询一个,根据id查询-->
    <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Teacher">
             select * from t_teacher where id=#{id}
    </select>
    复制代码
  • 传递pojo对象:如更新操作,传递的就是Teacher对象

    • MyBatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
    <!--更新操作-->
    <update id="updateTeacher" parameterType="com.ivyzh.domain.Teacher">
            update t_teacher set username=#{username},password=#{password},birthday=#{birthday},sex=#{sex},address=#{address} where id = #{id}
    </update>
    复制代码
  • 传递pojo对象:如根据用户名查询用户信息,查询条件放到QueryVo的teacher属性中

    <!--根据queryVo的条件查询用户-->
    <select id="findByVo" parameterType="com.ivyzh.domain.QueryVo" resultType="com.ivyzh.domain.Teacher">
             select * from t_teacher where username like #{teacher.username}
    </select>
    复制代码

OGNL表达式:(Apache)

​ Object Graphic Navigation Language

​ 对象 图 导航 语言

它是通过对象的取值方法来获取数据。在写法上把get给省略了。

​ 比如:我们获取用户的名称

​ 类中的写法:user.getUsername();

​ OGNL表达式写法:user.username

​ mybatis中为什么能直接写username,而不用user.呢:

​ 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名

演示:根据用户名查询用户信息,查询条件放到QueryVo的teacher属性中

QueryVo类,放在了 package com.ivyzh.domain 包下面

@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryVo {
    private Teacher teacher;
}
复制代码

ITeacherMapp.xml

</mapper>

	....

	<!--根据queryVo的条件查询用户-->
    <select id="findByVo" parameterType="com.ivyzh.domain.QueryVo" resultType="com.ivyzh.domain.Teacher">
         select * from t_teacher where username like #{teacher.username}
    </select>
</mapper>
复制代码

TeacherTest.java测试类方法:

@Test
    public void testFindByVo() {
        ITeacherDao mapper = sqlSession.getMapper(ITeacherDao.class);
        QueryVo vo = new QueryVo();
        Teacher teacher = new Teacher();
        teacher.setUsername("%zs%");
        vo.setTeacher(teacher);
        List<Teacher> teachers = mapper.findByVo(vo);
        for (Teacher t : teachers) {
            System.out.println(t);
        }
    }
复制代码

输出日志:

Teacher(id=10, username=zs1, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州)
Teacher(id=12, username=zs2, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州)
Teacher(id=13, username=zs3, password=123456, birthday=Sat Dec 14 08:00:00 CST 2019, sex=false, address=杭州)
复制代码

返回值深入-实体类属性和数据库列名不对应

场景解释:就是数据库字段和自定义的实体类的属性名不是一一对应,那会是怎样呢?

为了演示这样一个问题,我们copy一份t_teacher数据,然后创建t_worker数据库来演示,里面的内容结构和之前的都一样,如下:

MyBatis使用详解

接下来就是copy之前的Worker实体类、IWorkerDao接口、Mapper、测试类、SqlMapperConfig

Worker实体类:

Teacher的实体类是这样的,是和数据库字段一样的,

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher implements Serializable {
    private static final long serialVersionUID = -4399789553021464706L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
}
复制代码

我们做一下简单的修改,每个字段前面加一个user:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Worker implements Serializable {
    private static final long serialVersionUID = -4399789553021464706L;
    private int userId;
    private String userName;
    private String userPassword;
    private Date userBirthday;
    private boolean userSex;
    private String userAddress;
}
复制代码

IWorkerDao接口:

public interface IWorkerDao {

    /**
     * 查询所有
     *
     * @return 返回老师集合
     */
    List<Worker> findAll();

    /**
     * 保存操作
     *
     * @param worker 老师数据
     */
    void saveWorker(Worker worker);

    /**
     * 保存操作:问题拓展,新增用户id的返回
     *
     * @param worker 老师数据
     */
    void saveWorker2(Worker worker);

    /**
     * 更新操作
     *
     * @param worker 老师数据
     */
    void updateWorker(Worker worker);

    /**
     * 删除操作
     *
     * @param id 编号id
     */
    void deleteWorker(Integer id);

    /**
     * 查询一个,根据id查询
     *
     * @param id 用户id
     * @return 用户数据
     */
    Worker findById(Integer id);

    /**
     * 模糊查询,根据用户名查询
     *
     * @param username 用户名
     * @return 用户数据列表
     */
    List<Worker> findByName(String username);

    /**
     * 模糊查询,根据用户名查询,方式2
     *
     * @param username 用户名
     * @return 用户数据列表
     */
    List<Worker> findByName2(String username);

    /**
     * 获取总记录数
     *
     * @return 总记录数
     */
    Integer findTotal();
}
复制代码

IWorkerMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ivyzh.dao.IWorkerDao">
    <!--查询所有-->
    <select id="findAll" resultType="com.ivyzh.domain.Worker">
         select * from t_worker
    </select>

    <!--保存操作-->
    <insert id="saveWorker" parameterType="com.ivyzh.domain.Worker">
        insert into t_worker(username,password,birthday,sex,address) values (#{userName},#{userPassword},#{userBirthday},#{userSex},#{userAddress})
    </insert>

    <!--保存操作,问题拓展,新增用户id的返回-->
    <insert id="saveWorker2" parameterType="com.ivyzh.domain.Worker">
        /*keyProperty:属性名,keyColumn数据库中字段名*/
        <selectKey keyProperty="userId" keyColumn="id" resultType="java.lang.Integer" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into t_worker(username,password,birthday,sex,address) values
        (#{userName},#{userPassword},#{userBirthday},#{userSex},#{userAddress})
    </insert>

    <!--更新操作-->
    <update id="updateWorker" parameterType="com.ivyzh.domain.Worker">
        update t_worker set username=#{userName},password=#{userPassword},birthday=#{userBirthday},sex=#{userSex},address=#{userAddress} where id = #{userId}
    </update>
    <!--删除操作,只有一个参数的时候mapper可以随便写,这是一个占位符-->
    <delete id="deleteWorker" parameterType="java.lang.Integer">
        delete from t_worker where id=#{uid}
    </delete>
    <!--查询一个,根据id查询-->
    <select id="findById" parameterType="java.lang.Integer" resultType="com.ivyzh.domain.Worker">
         select * from t_worker where id=#{id}
    </select>

    <!--模糊查询方式1-->
    <select id="findByName" parameterType="java.lang.String" resultType="com.ivyzh.domain.Worker">
         select * from t_worker where username like #{userName}
    </select>
    <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value-->
    <select id="findByName2" parameterType="java.lang.String" resultType="com.ivyzh.domain.Worker">
         select * from t_worker where username like '%${value}%'
    </select>

    <!--mybatis也支持聚合函数,支持返回一行一列,获取总记录数-->
    <select id="findTotal" resultType="java.lang.Integer">
         select count(id) from t_worker
    </select>
</mapper>
复制代码

注意这里的findAll、findById、findByName、findByName2这几个方法如果不修改是有问题的。

这里先简单说下情况就是调用findAll之后的方法,日志如下:

Worker(userId=0, userName=TAA, userPassword=null, userBirthday=null, userSex=false, userAddress=null)
Worker(userId=0, userName=TBB, userPassword=null, userBirthday=null, userSex=false, userAddress=null)
Worker(userId=0, userName=TCC, userPassword=null, userBirthday=null, userSex=false, userAddress=null)
复制代码

可以发现只有userName字段查出来了,为什么呢?因为mysql在windows环境下不区分大小写!

SqlMapConfig.xml:

加入IWorkerMapper映射配置文件的位置,

<mappers>
	<mapper resource="com/ivyzh/dao/IUserMapper.xml"/>
	<mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/>
	<mapper resource="com/ivyzh/dao/IWorkerMapper.xml"/>
    <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
	<mapper class="com.ivyzh.dao.IStudentDao"/>
</mappers>
复制代码

WorkerTest测试类:

/**
 * MyBatis快速入门-Worker基于XML形式的增删改查测试
 */
public class WorkerTest {

    InputStream is;
    SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 查询所有用户数据
     */
    @Test
    public void testFindAll() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        List<Worker> workers = mapper.findAll();
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    /**
     * 保存操作
     */
    @Test
    public void testSave() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        Worker t = new Worker(0, "zs14", "123456", new Date(), false, "杭州");
        mapper.saveWorker(t);
        List<Worker> workers = mapper.findAll();
        for (Worker worker : workers) {
            System.out.println(worker);
        }
        //提交事务,或者放到destory里面
        //sqlSession.commit();
    }

    /**
     * 保存操作
     */
    @Test
    public void testSave2() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        Worker t = new Worker(0, "zs15", "123456", new Date(), false, "杭州");
        System.out.println("保存操作之前:" + t);
        mapper.saveWorker2(t);
        System.out.println("保存操作之后:" + t);
        List<Worker> workers = mapper.findAll();
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    @Test
    public void testUpdate() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        Worker t = new Worker(13, "zs13", "", new Date(), true, "南京");
        mapper.updateWorker(t);
        List<Worker> workers = mapper.findAll();
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    @Test
    public void testDelete() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        mapper.deleteWorker(6);
        List<Worker> workers = mapper.findAll();
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    @Test
    public void testFindById() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        Worker worker = mapper.findById(1);
        System.out.println(worker);
    }

    @Test
    public void testFindByName() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        List<Worker> workers = mapper.findByName("%T%");
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    @Test
    public void testFindByName2() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        List<Worker> workers = mapper.findByName2("T");
        for (Worker worker : workers) {
            System.out.println(worker);
        }
    }

    @Test
    public void testFindTotal() {
        IWorkerDao mapper = sqlSession.getMapper(IWorkerDao.class);
        Integer total = mapper.findTotal();
        System.out.println("total -> " + total);
    }


    @After
    public void destory() throws IOException {
        //提交事务,或者放到destory里面
        sqlSession.commit();
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

问题解决

可以发现在查询的时候,并没有将查询字段封装到Domain实体类里面,那么该怎么解决呢?这里有两个方案

  1. 起别名
  2. 使用resultMap
  • 起别名

    这种方式是执行最快的,比如这里我只修改findAll方法如下:

    <!--查询所有-->
    <select id="findAll" resultType="com.ivyzh.domain.Worker">
        select id as userId,username as userName,password as userPassword,birthday as userBirthday,sex as userSex,address as userAddress from t_worker
    </select>
    复制代码

    运行结果:

    Worker(userId=1, userName=TAA, userPassword=123456, userBirthday=Fri Dec 13 08:00:00 CST 2019, userSex=true, userAddress=北京)
    Worker(userId=2, userName=TBB, userPassword=123456, userBirthday=Tue Dec 10 08:00:00 CST 2019, userSex=false, userAddress=上海)
    Worker(userId=3, userName=TCC, userPassword=123456, userBirthday=Wed Dec 04 08:00:00 CST 2019, userSex=true, userAddress=广州)
    复制代码

    可以发现数据已经填充好了。

  • 使用resultMap

    即配置查询结果的列名和实体类的属性名对应关系

    <mapper namespace="com.ivyzh.dao.IWorkerDao">
    
        <!--配置查询结果的列名和实体类的属性名的对应关系-->
        <resultMap id="workerMap" type="com.ivyzh.domain.Worker">
            <!--主键字段的对应,property:对应实体类,column:对应数据库列名-->
            <id property="userId" column="id"/>
            <!--非主键字段的对应-->
            <result property="userName" column="username"/>
            <result property="userPassword" column="password"/>
            <result property="userSex" column="sex"/>
            <result property="userAddress" column="address"/>
        </resultMap>
        ...
        <!--查询一个,根据id查询-->
        <select id="findById" parameterType="java.lang.Integer" resultMap="workerMap">
             select * from t_worker where id=#{id}
        </select>
        
        <!--模糊查询方式1-->
        <select id="findByName" parameterType="java.lang.String" resultMap="workerMap">
             select * from t_worker where username like #{userName}
        </select>
        
        <!--模糊查询方式2,了解这种写法就好,实际开发中,不要用value-->
        <select id="findByName2" parameterType="java.lang.String" resultMap="workerMap">
             select * from t_worker where username like '%${value}%'
        </select>
        ...
    </mapper>
    复制代码

    这样调用findByName方法,就会打印如下内容:

    Worker(userId=1, userName=TAA, userPassword=123456, userBirthday=null, userSex=true, userAddress=北京)
    Worker(userId=2, userName=TBB, userPassword=123456, userBirthday=null, userSex=false, userAddress=上海)
    Worker(userId=3, userName=TCC, userPassword=123456, userBirthday=null, userSex=true, userAddress=广州)
    复制代码

两种方式对比:

第一种执行效率高,是在sql层面上修改的,起别名。但是sql语句该找了改造量大!对于开发效率比较低。

第二种方式因为多引入了xml文件,需要多一步xml解析。所以效率不如上面的高,但是封装好的resultMap多处可以复用!开发效率高!

再回顾一下,至此项目的目录:

MyBatis使用详解

依次用到了User、Student、Teacher、Worker实体类,为了演示不同的场景,才新建这么多测试类的,到时候回头来看,比较清晰。自己研究琢磨的时候,可以用一个实体类就行 :smile:

常用标签的介绍

properties

properties标签作用:用于抽取数据库连接配置信息。

我们现在将数据库连接池写在了 SqlMapConfig.xml 文件里面,如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis主配置文件-->
<configuration>
    <!--配置环境-->
    <environments default="development">
        <!--配置mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--指定映射配置文件的位置,映射配置文件指的是每一个dao独立的配置文件-->
    <mappers>
        <mapper resource="com/ivyzh/dao/IUserMapper.xml"/>
        <mapper resource="com/ivyzh/dao/ITeacherMapper.xml"/>
        <mapper resource="com/ivyzh/dao/IWorkerMapper.xml"/>
        <!--如果使用的注解来配置的话,此处应该使用class属性指定被注解的dao全限定类名-->
        <mapper class="com.ivyzh.dao.IStudentDao"/>
    </mappers>
</configuration>
复制代码

properties的功能就是将dataSource标签下面的数据库连接信息抽出来,为此我们可以有这样几种操作:

第一种:

<configuration>
    <!--配置properties-->
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </properties>

    <!--配置环境-->
    <environments default="development">
        <!--配置mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--配置数据源(连接池)-->
            <dataSource type="POOLED">
                <!--配置连接数据库的4个基本信息,使用properties标签-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    
    ...
 
</configuration>
复制代码

第二种:

使用properties的resource属性,指定配置文件位置。即可以在 properties 标签内部配置连接数据的信息,也可以通过属性引用外部配置文件信息。

resource属性:用于指定配置文件的位置,是按照类路径的写法来写,必须存在于类路径下。

jdbcConfig.properties的存放路径和内容:

MyBatis使用详解

引用的时候,可以这样配置:

<configuration>
    <!--配置properties-->
    <properties resource="jdbcConfig.properties"/>
    ...
</configuration>
复制代码

第三种:

使用properties的url属性。

比如我把jdbcConfig.properties连接信息copy到D盘根目录,则引用的时候应该这样做:

<configuration>
    <!--配置properties-->
    <properties url="file:///D:/jdbcConfig.properties"/>
    ...
</configuration>
复制代码

typeAliases

回顾IWorkerMapper.xml这个文件中有几个写的是类全限定名,如图:

MyBatis使用详解

在resultMap标签type属性、select标签的resultType、parameterType属性都写了全限定名 com.ivyzh.domain.Worker

这时候typeAliases出来就是来优化这种写法的。

使用typeAliases配置别名,它只能配置domain中类的别名。

typeAlias的type属性指的是实体类全限定名,alias属性指起的别名,不区分大小写。

同样这个需要在SqlMapConfig.xml文件里面去配置:

MyBatis使用详解

使用的时候可以直接写别名了,且不区分大小写:

MyBatis使用详解

typeAliases配置过的时候也是很麻烦的,比如又N个实体类需要配置,那么就需要在 typeAliases 标签下面写N个 typeAlias ,所以它提供了packge标签。

package

配置如下:

<!--使用typeAliases配置别名,它只能配置domain中类的别名-->
<typeAliases>
    <package name="com.ivyzh.domain"/>
</typeAliases>
复制代码

这样表示在 com.ivyzh.domain 包下面的实体类都不用再一个个配置了,他们的默认别名就是类名。

同理:mappers标签下面也有package标签,是指定接口所在的包,当指定了之后就不需要写mapper、resources、class了。

比如我们用到的User、Student、Teacher、Worker实体类所对应的Mapper.xml或者Dao的实现类文件都要配置在mappers标签下面。

MyBatis使用详解

用了package标签之后,就简洁很多了。

<mappers>
    <!--pack标签是用于指定dao接口所在的包,当指定了之后就不需要再写mapper及resource或者class了-->
    <!--目前这个功能运行报错,不知道为什么?暂时记录一下-->
    <!--<package name="com.ivyzh.dao"/>-->
</mappers>
复制代码

调用查询所有的报错信息:

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.ivyzh.dao.IWorkerDao.findAll
复制代码

最后解决了,参考文章https://blog.csdn.net/weixin_43951534/article/details/90416363

MyBatis注册映射文件,package 加载方式(class加载方式的简写方式),适用于类路径下,接口文件与映射文件在同一路径下,且 接口名与映射文件名相同 ,并且映射文件命名为接口全类名的情况

即IWorkerMapper.xml要改成IWorkerDao.xml才行!太坑了!

修改完之后的目录结构:

MyBatis使用详解

关于package、mappers几篇参考文章:

www.jianshu.com/p/4e50fa289…

blog.csdn.net/u014268482/…

blog.csdn.net/qq_31457665…

blog.csdn.net/weixin_4395…

MyBatis连接池及事务控制

这一节内容:原理部分了解,应用部分会用即可。

连接池

连接池作用

它可以减少我们获取连接所消耗的时间

Mybatis连接池分类

mybatis连接池提供了3种方式的配置: 配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签,type属性就是表示采用何种连接池方式,type属性的取值3种。

  • POOLED
  • UNPOOLED
  • JNDI(了解)

POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现

UNPOOLED: 采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。

JNDI(拓展了解): 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。

注意:如果不是web或者maven的war工程,是不能使用的。tomcat服务器,采用连接池就是dbcp连接池。

事务控制

mybatis中的事务,它是通过sqlsession对象的commit方法和rollback方法实现事务的提交和回滚。

如何设置代码让其自动提交?

sqlSession = sqlSessionFactory.openSession(boolean autoCommit);//设置true就可以了。

这一块,没有具体研究过,先暂存吧,后面找到合适例子再补充。

动态SQL

这一节内容:会用即可。

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL 是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

  • if标签
  • where标签
  • foreach标签
  • SQL片段

再新建一个厨师表 t_chef 数据和 t_worker 一模一样。

Chef实体类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Chef implements Serializable {
    private static final long serialVersionUID = -6815279398228636657L;
    private int id;
    private String username;
    private String password;
    private Date birthday;
    private Boolean sex;
    private String address;
}
复制代码

注意这里sex是Boolen类型。

IChefDao.java:

public interface IChefDao {

    /**
     * 查询所有
     *
     * @return 返回老师集合
     */
    List<Chef> findAll();


    /**
     * 根据条件查询,查询内容可能是姓名、性别、地址...
     * 演示if标签
     *
     * @param chef 用户数据
     * @return 用户数据列表
     */
    List<Chef> findByCondition(Chef chef);

    /**
     * 根据条件查询,查询内容可能是姓名、性别、地址...
     * 演示where标签
     *
     * @param chef 用户数据
     * @return 用户数据列表
     */
    List<Chef> findByCondition2(Chef chef);


    /**
     * 根据ChefQueryVo中提供的id集合,查询用户信息
     *
     * @param vo 查询条件
     * @return 用户数据列表
     */
    List<Chef> findByIds(ChefQueryVo vo);
}
复制代码

ChefQueryVo.java:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChefQueryVo {
    private List<Integer> ids;
}
复制代码

注:由于在SQLMapConfig文件使用package标签,则在定义Mapper映射文件的时候要和接口名称定义成相同的,IChefDao.xml

测试类ChefTest:

/**
 * MyBatis快速入门-ChefTest基于XML形式的增删改查测试
 */
public class ChefTest {

    InputStream is;
    SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 查询所有用户数据
     */
    @Test
    public void testFindAll() {
        IChefDao mapper = sqlSession.getMapper(IChefDao.class);
        List<Chef> chefs = mapper.findAll();
        print(chefs);

    }

    private void print(List<Chef> chefs) {
        for (Chef chef : chefs) {
            System.out.println(chef);
        }
    }

    @Test
    public void testFindByCondition() {
        IChefDao mapper = sqlSession.getMapper(IChefDao.class);
        Chef chef = new Chef();
        chef.setUsername("TAA");
        List<Chef> chefs = mapper.findByCondition(chef);
        print(chefs);
    }

    @Test
    public void testFindByCondition2() {
        IChefDao mapper = sqlSession.getMapper(IChefDao.class);
        Chef chef = new Chef();
        chef.setAddress("杭州");
        List<Chef> chefs = mapper.findByCondition2(chef);
        print(chefs);
    }


    @Test
    public void testFindByQueryVo() {
        IChefDao mapper = sqlSession.getMapper(IChefDao.class);
        ChefQueryVo vo = new ChefQueryVo();
        ArrayList<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(3);
        ids.add(5);
        ids.add(8);
        vo.setIds(ids);
        List<Chef> chefs = mapper.findByIds(vo);
        print(chefs);
    }

    @After
    public void destory() throws IOException {
        //提交事务,或者放到destory里面
        sqlSession.commit();
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

SQL片段

sql片段意思是抽取重复语句,如:

<mapper namespace="com.ivyzh.dao.IChefDao">

    <!--抽取sql代码块-->
    <sql id="selectAll">
        select * from t_chef
    </sql>

    <!--查询所有-->
    <select id="findAll" resultType="chef">
        <include refid="selectAll"/>
    </select>
    
    ...
<mapper/>    
复制代码

if标签

<mapper namespace="com.ivyzh.dao.IChefDao">

    <!--抽取sql代码块-->
    <sql id="selectAll">
        select * from t_chef
    </sql>

    <!--查询所有-->
    <select id="findAll" resultType="chef">
        <include refid="selectAll"/>
    </select>

    <!--根据条件查询,查询内容可能是姓名、性别、地址...-->
    <select id="findByCondition" parameterType="chef" resultType="chef">
        select * from t_chef where 1 = 1
        <if test="username != null">
            and username = #{username}
        </if>
        <if test="sex != null">
            and sex = #{sex}
        </if>
        <if test="address != null">
            and address = #{address}
        </if>
    </select>
    
    ...
<mapper/>
复制代码

where标签

if标签需要手写 where 1 = 1 ,使用where标签我们可以这样做:

...
    <!--根据条件查询,查询内容可能是姓名、性别、地址...演示where标签-->
    <select id="findByCondition2" parameterType="chef" resultType="chef">
        select * from t_chef
        <where>
            <if test="username != null">
                and username = #{username}
            </if>
            <if test="sex != null">
                and sex = #{sex}
            </if>
            <if test="address != null">
                and address = #{address}
            </if>
        </where>
    </select>
...
复制代码

foreach标签

...
    <!--根据ChefQueryVo中提供的id集合,查询用户信息-->
    <!--注意size()可以写成size-->
    <select id="findByIds" parameterType="queryvo" resultType="chef">
        <include refid="selectAll"/>
        <where>
            <if test="ids != null and ids.size() >0">
                <foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
                    #{uid}
                </foreach>
            </if>
        </where>
    </select>
...
复制代码

p6sy

p6spy是一个开源项目,通常使用它来跟踪数据库操作,查看程序运行过程中执行的sql语句。

参考文章: blog.csdn.net/gnd15732625…

多表查询(一对一)

其实上面这种方式在实际开发中并不常用,应用场景举例,一个用户可以有多个银行账户,一个银行账户唯一对应一个用户。

根据账户id查询用户信息,就是一个一对一的案例。实现起来不难,可以单表查询就行,这里我们直接演示另一种情况,一对多。后面还会提到一对一查询,详见 延迟加载-一对一实现立即加载章节

多表查询(一对多)

一对多情况:一个用户可以有多张银行卡,场景是根据用户id查询他的用户信息和银行卡信息。

Step1. 建表

创建客户表 t_account

DROP TABLE IF EXISTS t_account;

CREATE TABLE t_account (
  id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
  money INT(10),
  uid INT(10) NOT NULL
);
复制代码

创建客户表 t_customer

表结构和数据和之前的Worker一样。

t_customer 的数据内容:

MyBatis使用详解

t_accountt_customer 建立外键约束:

MyBatis使用详解

t_account 表数据:

MyBatis使用详解

先验证一下sql查询:

  • 查询用户和其对应的账户信息

    SELECT * FROM t_customer,t_account WHERE t_customer.id = t_account. uid

MyBatis使用详解
  • 查询用户id为2的用户和其对应的账户信息

    SELECT * FROM t_customer c,t_account a WHERE c.id = a. uid AND c.id =1

MyBatis使用详解

Step2. 建实体类对应关系

账户表Account:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account implements Serializable {
    private static final long serialVersionUID = -5255167209921667740L;
    private Integer id;
    private Integer money;
    private Integer uid;
}
复制代码

顾客表Customer:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer implements Serializable {
    private static final long serialVersionUID = 3456456284746844493L;
    private Integer id;
    private String username;
    private String password;
    private Date birthday;
    private Boolean sex;
    private String address;

    /**
     * 一对多关系映射:主表实体类中应该包含从表实体的集合引用
     */
    private List<Account> accounts;
}
复制代码

顾客操作接口ICustomerDao:

public interface ICustomerDao {

    /**
     * 查询所有
     *
     * @return 返回数据集合
     */
    List<Customer> findAll();

    /**
     * 查询所有并包含账户信息
     *
     * @return 返回数据集合
     */
    List<Customer> findAllWithAccount();
}
复制代码

Step3.编写查询Mapper

ICustomerDao.xml:

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

    <!--配置Customer的resultMap-->
    <resultMap id="caMap" type="customer">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
        <result property="address" column="address"/>
        <!--配置customer对象中accounts集合的映射-->
        <collection property="accounts" ofType="account">
            <id property="id" column="aid"/>
            <result property="money" column="money"/>
            <result property="uid" column="uid"/>
        </collection>
    </resultMap>

    <!--抽取sql代码块-->
    <sql id="selectAll">
        select * from t_customer
    </sql>

    <!--查询所有-->
    <select id="findAll" resultType="customer">
        <include refid="selectAll"/>
    </select>

    <!--查询所有并包含账户信息-->
    <select id="findAllWithAccount" resultMap="caMap">
       select c.*,a.id as aid ,a.money money,a.uid uid from t_customer c left outer join t_account a on c.id = a.uid
    </select>

</mapper>
复制代码

Step4. 测试

测试类CustomerTest:

/**
 * MyBatis快速入门-CustomerTest基于XML形式的增删改查测试
 */
public class CustomerTest {

    InputStream is;
    SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 查询所有用户数据
     */
    @Test
    public void testFindAll() {
        ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class);
        List<Customer> customers = mapper.findAll();
        print(customers);
    }

    @Test
    public void testFindAllWithAccount() {
        ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class);
        List<Customer> customers = mapper.findAllWithAccount();
        print(customers);
    }

    private void print(List<Customer> objs) {
        for (Customer obj : objs) {
            System.out.println(obj);
        }
    }

    @After
    public void destory() throws IOException {
        //提交事务,或者放到destory里面
        sqlSession.commit();
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

输出结果:

  • testFindAll方法

    Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=null)
    Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=null)
    Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=null)
    复制代码
  • testFindAllWithAccount方法

    Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=[Account(id=1, money=1000, uid=1), Account(id=2, money=2000, uid=1), Account(id=3, money=3000, uid=1)])
    Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=[Account(id=4, money=4000, uid=2), Account(id=5, money=5000, uid=2)])
    Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=[Account(id=6, money=6000, uid=3)])
    复制代码

注意事项:collection标签里面的书写格式。

多表查询(多对多)

示例:用户和角色

​ 一个用户可以有多个角色

​ 一个角色可以赋予多个用户

步骤:

​ 1、建立两张表:用户表,角色表

​ 让用户表和角色表具有多对多的关系。需要使用中间表,中间表中包含各自的主键,在中间表中是外键。

​ 2、建立两个实体类:用户实体类和角色实体类

​ 让用户和角色的实体类能体现出来多对多的关系

​ 各自包含对方一个集合引用

​ 3、建立两个配置文件

​ 用户的配置文件

​ 角色的配置文件

​ 4、实现配置:

​ 当我们查询用户时,可以同时得到用户所包含的角色信息

​ 当我们查询角色时,可以同时得到角色的所赋予的用户信息

演示案例:当我们查询用户时,可以同时得到用户所包含的角色信息

Step1. 建表

t_role 角色表:

DROP TABLE IF EXISTS t_role;

CREATE TABLE t_role (
  id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
  role_name VARCHAR(10),
  role_desc VARCHAR(10)
);
复制代码

t_employee 员工表:

结构和数据与 t_customer 表一致。

t_employee_role 角色权限对应表

DROP TABLE IF EXISTS t_employee_role;

CREATE TABLE t_employee_role (
  id INT(10) PRIMARY KEY NOT NULL AUTO_INCREMENT,
  cid INT(10) NOT NULL,
  rid INT(10) NOT NULL
);
复制代码

加入外键:

MyBatis使用详解

加一下数据:

t_employee:

MyBatis使用详解

t_role:

MyBatis使用详解

t_employee_role:

MyBatis使用详解

简单写一个sql查询一下:

SELECT e.*,r.id AS rid,r.role_name,r.role_desc FROM t_role r LEFT OUTER JOIN t_employee_role er ON r.id = er.rid LEFT OUTER JOIN t_employee e ON e.id = er.cid;
MyBatis使用详解

Step2.建立两个实体类

Employee:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable {
    private static final long serialVersionUID = -2661131928069328685L;
    private Integer id;
    private String username;
    private String password;
    private Date birthday;
    private boolean sex;
    private String address;
    /**
     * 一对多关系映射:主表实体类中应该包含从表实体的集合引用
     */
    private List<Role> roles;
}
复制代码

Role:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
    private static final long serialVersionUID = -2661131928069328685L;
    private Integer roleId;
    private String roleName;
    private String roleDesc;
}
复制代码

注意这里我们故意将字段名和数据库存储字段写的不一样。

IEmployeeDao:

public interface IEmployeeDao {

    /**
     * 查询所有
     *
     * @return 返回数据集合
     */
    List<Employee> findAll();

    /**
     * 查询所有并包含角色信息
     *
     * @return 返回数据集合
     */
    List<Employee> findAllWithRole();
}
复制代码

Step3. 建立Mapper配置文件

IEmployeeDao.xml

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

    <!--配置Customer的resultMap-->
    <resultMap id="erMap" type="employee">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
        <result property="address" column="address"/>
        <!--配置customer对象中accounts集合的映射-->
        <collection property="roles" ofType="role">
            <id property="roleId" column="rid"/>
            <result property="roleName" column="role_name"/>
            <result property="roleDesc" column="role_desc"/>
        </collection>
    </resultMap>

    <!--抽取sql代码块-->
    <sql id="selectAll">
        select * from t_employee
    </sql>

    <!--查询所有-->
    <select id="findAll" resultType="employee">
        <include refid="selectAll"/>
    </select>

    <!--查询所有并包含账户信息-->
    <select id="findAllWithRole" resultMap="erMap">
        SELECT e.*,r.id AS rid,r.`role_name`,r.`role_desc` FROM t_employee e
        LEFT OUTER JOIN t_employee_role er ON e.id = er.`cid`
        LEFT OUTER JOIN t_role r ON r.`id` = er.`rid`
    </select>

</mapper>
复制代码

Step4. 测试

EmployeeTest:

/**
 * EmployeeTest演示多对多——一个雇员对应多个身份
 */
public class EmployeeTest {

    InputStream is;
    SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 查询所有用户数据
     */
    @Test
    public void testFindAll() {
        IEmployeeDao mapper = sqlSession.getMapper(IEmployeeDao.class);
        List<Employee> employees = mapper.findAll();
        print(employees);
    }

    @Test
    public void testFindAllWithRole() {
        IEmployeeDao mapper = sqlSession.getMapper(IEmployeeDao.class);
        List<Employee> employees = mapper.findAllWithRole();
        print(employees);
    }

    private void print(List<Employee> objs) {
        for (Employee obj : objs) {
            System.out.println(obj);
        }
    }

    @After
    public void destory() throws IOException {
        //提交事务,或者放到destory里面
        sqlSession.commit();
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

日志输出:

  • testFindAll

    Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null)
    Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=null)
    Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=null)
    复制代码
  • testFindAllWithRole

    Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=[Role(roleId=2, roleName=校长, roleDesc=管理整个学校), Role(roleId=1, roleName=院长, roleDesc=管理整个学院)])
    Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=[Role(roleId=1, roleName=院长, roleDesc=管理整个学院)])
    Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=[Role(roleId=3, roleName=班长, roleDesc=管理班级)])
    复制代码

以上就是查询用户下面所有的角色,那么如何查询角色获取角色下所属用户信息呢?

Role实体类加入employees引用:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role implements Serializable {
    private static final long serialVersionUID = -2661131928069328685L;
    private Integer roleId;
    private String roleName;
    private String roleDesc;
    /**
     * 一对多关系映射:主表实体类中应该包含从表实体的集合引用
     */
    private List<Employee> employees;
}
复制代码

IRoleDao:

public interface IRoleDao {

    /**
     * 查询所有
     *
     * @return 返回数据集合
     */
    List<Role> findAll();

    /**
     * 查询所有角色并包含用户信息
     *
     * @return 返回数据集合
     */
    List<Role> findAllWithEmployee();
}
复制代码

IRoleDao.xml:

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

    <!--配置Role的resultMap-->
    <resultMap id="reMap" type="role">
        <id property="roleId" column="rid"/>
        <result property="roleName" column="role_name"/>
        <result property="roleDesc" column="role_desc"/>
        <!--配置role对象中employees集合的映射-->
        <collection property="employees" ofType="employee">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="password" column="password"/>
            <result property="sex" column="sex"/>
            <result property="birthday" column="birthday"/>
            <result property="address" column="address"/>
        </collection>
    </resultMap>
    <!--查询所有-->
    <select id="findAll" resultMap="reMap">
        select r.id AS rid,r.`role_name`,r.`role_desc` from t_role r
    </select>

    <!--查询所有角色并包含用户信息-->
    <select id="findAllWithEmployee" resultMap="reMap">
        SELECT r.id AS rid,r.`role_name`,r.`role_desc`,e.*  FROM t_role r
        LEFT OUTER JOIN t_employee_role er ON r.id = er.`rid`
        LEFT OUTER JOIN t_employee e ON e.id = er.`cid`
    </select>

</mapper>
复制代码

测试类RoleTest:

/**
 * RoleTest演示多对多——角色对应的用户信息
 */
public class RoleTest {

    InputStream is;
    SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 查询所有用户数据
     */
    @Test
    public void testFindAll() {
        IRoleDao mapper = sqlSession.getMapper(IRoleDao.class);
        List<Role> roles = mapper.findAll();
        print(roles);
    }

    @Test
    public void findAllWithEmployee() {
        IRoleDao mapper = sqlSession.getMapper(IRoleDao.class);
        List<Role> roles = mapper.findAllWithEmployee();
        print(roles);
    }

    private void print(List<Role> objs) {
        for (Role obj : objs) {
            System.out.println(obj);
        }
    }

    @After
    public void destory() throws IOException {
        //提交事务,或者放到destory里面
        sqlSession.commit();
        //6. 释放资源
        sqlSession.close();
        is.close();
    }
}
复制代码

输出:

  • testFindAll

    Role(roleId=1, roleName=院长, roleDesc=管理整个学院, employees=[])
    Role(roleId=2, roleName=校长, roleDesc=管理整个学校, employees=[])
    Role(roleId=3, roleName=班长, roleDesc=管理班级, employees=[])
    复制代码
  • testFindAllWithRole

    Role(roleId=2, roleName=校长, roleDesc=管理整个学校, employees=[Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null)])
    Role(roleId=1, roleName=院长, roleDesc=管理整个学院, employees=[Employee(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, roles=null), Employee(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, roles=null)])
    Role(roleId=3, roleName=班长, roleDesc=管理班级, employees=[Employee(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, roles=null)])
    复制代码

至此,项目的目录结构如图:

MyBatis使用详解
MyBatis使用详解

延迟加载

延迟加载和立即加载的概念

问题:在一对多中,当我们有一个用户,它有100个账户。 在查询用户的时候,要不要把关联的账户查出来? 在查询账户的时候,要不要把关联的用户查出来?

在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。 在查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来。

什么是延迟加载 在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载) 什么是立即加载 不管用不用,只要一调用方法,马上发起查询。

在对应的四种表关系中:一对多,多对一,一对一,多对多 一对多,多对多:通常情况下我们都是采用延迟加载。 多对一,一对一:通常情况下我们都是采用立即加载。

一对一实现延迟加载

这个案例可以用Customer和Account这个表来演示,即一个账户对应一个用户。

之前在讲多表查询(一对一)章节的时候略过了,但这里需要用到这个案例,看来还是躲不过去的。

那么我们先用Customer和Account来演示立即加载的情况吧。

注:只演示AccountDao.findById查询账户信息且带出用户信息的情况。

立即加载

修改Account实体类,加入Customer:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account implements Serializable {
    private static final long serialVersionUID = 1113742752197915016L;
    private Integer id;
    private Integer money;
    private Integer uid;

    /**
     * 演示一对一查询
     */
    private Customer customer;

    @Override
    public String toString() {
        return "Account{" + "id=" + id + ", money=" + money + ", uid=" + uid + "}";
    }
}
复制代码

IAccountDao:

public interface IAccountDao {

    /**
     * 根据id查询账户信息
     *
     * @param id 账户id
     * @return 账户信息
     */
    Account findById(Integer id);
}
复制代码

IAccountDao.xml:

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

    <!--配置Account的resultMap-->
    <resultMap id="acMap" type="account">
        <id property="id" column="aid"/>
        <result property="money" column="money"/>
        <result property="uid" column="uid"/>
        <!--配置account对象中customer集合的映射-->
        <collection property="customer" ofType="customer">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="password" column="password"/>
            <result property="sex" column="sex"/>
            <result property="birthday" column="birthday"/>
            <result property="address" column="address"/>
        </collection>
    </resultMap>
    <!--查询所有-->
    <select id="findById" parameterType="int" resultMap="acMap">
       SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id
    </select>
</mapper>
复制代码

测试类AccountTest:

...
    /**
     * 根据id查询账户信息
     */
    @Test
    public void testFindById() {
        IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
        Account account = mapper.findById(4);
        System.out.println(account);
        System.out.println(account.getCustomer());
    }
    ...
复制代码

输出日志:

SQL:

==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
==> Parameters: 4(Integer)
<==      Total: 1
复制代码
Account{id=4, money=4000, uid=2}
Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=null)
复制代码

可以发现不仅仅查询到了Account信息,连带着Customer信息也查询出来了。

那么我们改造一下,只有调用的时候才查询。

延迟加载

这里我们要用Assocation实现延迟加载。

IAccountDao里面新加一个方法findById2来演示延迟加载,

public interface IAccountDao {

    /**
     * 根据id查询账户信息,立即加载
     *
     * @param id 账户id
     * @return 账户信息
     */
    Account findById(Integer id);

    /**
     * 根据id查询账户信息,延迟加载
     *
     * @param id 账户id
     * @return 账户信息
     */
    Account findById2(Integer id);
}
复制代码

改造IAccountDao.xml加入acMapLazy的resultMap和findById2方法:

<?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.ivyzh.dao.IAccountDao">

    <!--配置Account的resultMap-->
    <resultMap id="acMap" type="account">
        <id property="id" column="aid"/>
        <result property="money" column="money"/>
        <result property="uid" column="uid"/>
        <!--配置account对象中customer集合的映射-->
        <collection property="customer" ofType="customer">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="password" column="password"/>
            <result property="sex" column="sex"/>
            <result property="birthday" column="birthday"/>
            <result property="address" column="address"/>
        </collection>
    </resultMap>

    <!--配置Account的resultMap 延迟加载-->
    <resultMap id="acMapLazy" type="account">
        <id property="id" column="aid"/>
        <result property="money" column="money"/>
        <result property="uid" column="uid"/>
        <!--配置account对象中customer集合的映射-->
        <association property="customer" column="uid" javaType="customer" select="com.ivyzh.dao.ICustomerDao.findById"/>
    </resultMap>
    <!--查询所有-->
    <select id="findById" parameterType="int" resultMap="acMap">
       SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id
    </select>


    <select id="findById2" parameterType="int" resultMap="acMapLazy">
      select * from t_account where id = #{aid}
    </select>
</mapper>
复制代码

可以发现用到了ICustomerDao的findById方法,这个时候我们也要改造ICustomerDao和ICustomerDao.xml

public interface ICustomerDao {

    /**
     * 查询所有
     *
     * @return 返回数据集合
     */
    List<Customer> findAll();

    /**
     * 查询所有并包含账户信息
     *
     * @return 返回数据集合
     */
    List<Customer> findAllWithAccount();

    /**
     * 根据id查询用户信息
     *
     * @param id 用户id
     * @return
     */
    Customer findById(Integer id);
}
复制代码

ICustomerDao.xml

...
    <!--根据id查询用户信息-->
    <select id="findById" parameterType="int" resultType="customer">
       select * from t_customer where id = #{uid}
    </select>
    ...
    
复制代码

AccountTest测试类:

...
    /**
     * 根据id查询账户信息-延迟加载
     */
    @Test
    public void testFindById2() {
        IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
        Account account = mapper.findById2(1);
        //System.out.println(account);
        //System.out.println(account.getCustomer());
    }
    ...
复制代码

为了方便演示,我们将输出注释,只看sql语句就行。

我们运行 testFindById2 方法,观察日志输出:

==>  Preparing: select * from t_account where id = ? 
 ==> Parameters: 1(Integer)
 ====>  Preparing: select * from t_customer where id = ? 
 ====> Parameters: 1(Integer)
 <====      Total: 1
 <==      Total: 1
复制代码

可以发现执行了两条sql语句,并没有延迟加载啊?原因在哪里呢?

因为Mybatis默认是不开启延迟加载的,如果要开启需要在SqlMapConfig.xml文件里面配置:

MyBatis使用详解
...
    <!--配置参数-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
...    
复制代码

配置完成之后运行,看日志,发现只有一条SQL语句执行了,说明实现了延迟加载:

==>  Preparing: select * from t_account where id = ? 
==> Parameters: 1(Integer)
<==      Total: 1
复制代码

只有主动调用 account.getCustomer() 方法才会执行另一条查询sql。

一对多实现延迟加载

之前CustomerTest测试类中的testFindAllWithAccount是查询所有信息:

Customer(id=1, username=TAA, password=123456, birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address=北京, accounts=[Account{id=1, money=1000, uid=1}, Account{id=2, money=2000, uid=1}, Account{id=3, money=3000, uid=1}])
Customer(id=2, username=TBB, password=123456, birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address=上海, accounts=[Account{id=4, money=4000, uid=2}, Account{id=5, money=5000, uid=2}])
Customer(id=3, username=TCC, password=123456, birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address=广州, accounts=[Account{id=6, money=6000, uid=3}])
复制代码

我们改造一下这个方法,期望的效果是当调用查询用户方法的时候,不主动查询与其关联的账户信息。

IAccountDao加入findByUid方法:

public interface IAccountDao {

    /**
     * 根据id查询账户信息,立即加载
     *
     * @param id 账户id
     * @return 账户信息
     */
    Account findById(Integer id);


    /**
     * 根据用户id查询账户信息
     *
     * @param id 用户id
     * @return 账户信息
     */
    List<Account> findByUid(Integer id);

    /**
     * 根据id查询账户信息,延迟加载
     *
     * @param id 账户id
     * @return 账户信息
     */
    Account findById2(Integer id);
}

复制代码

IAccountDao.xml实现:

...
    <select id="findByUid" parameterType="int" resultType="account">
      select * from t_account where uid = #{uid}
    </select>
...
复制代码

ICustomerDao加入findAllWithAccount2方法:

public interface ICustomerDao {

    /**
     * 查询所有
     *
     * @return 返回数据集合
     */
    List<Customer> findAll();

    /**
     * 查询所有并包含账户信息
     *
     * @return 返回数据集合
     */
    List<Customer> findAllWithAccount();

    /**
     * 查询所有并包含账户信息,延迟加载
     *
     * @return 返回数据集合
     */
    List<Customer> findAllWithAccount2();

    /**
     * 根据id查询用户信息
     *
     * @param id 用户id
     * @return
     */
    Customer findById(Integer id);
}
复制代码

ICustomerDao.xml实现:

...
    <!--配置Customer的resultMap-->
    <resultMap id="caMapLazy" type="customer">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
        <result property="sex" column="sex"/>
        <result property="birthday" column="birthday"/>
        <result property="address" column="address"/>
        <!--配置customer对象中accounts集合的映射-->
        <collection property="accounts" column="id" ofType="account" select="com.ivyzh.dao.IAccountDao.findByUid"/>
    </resultMap>
    <!--查询所有并包含账户信息-延迟加载-->
    <select id="findAllWithAccount2" resultMap="caMapLazy">
       select * from t_customer
    </select>
...
复制代码

CustomerTest测试类:

...
    @Test
    public void testFindAllWithAccount2() {
        ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class);
        List<Customer> customers = mapper.findAllWithAccount2();
        for (Customer customer : customers) {
            System.out.println("+++" + customer);
            List<Account> accounts = customer.getAccounts();
            for (Account account : accounts) {
                System.out.println("---" + account);
            }
        }
    }
...
复制代码

输出日志:

==>  Preparing: select * from t_customer 
==> Parameters: 
<==      Total: 3
==>  Preparing: select * from t_account where uid = ? 
==> Parameters: 1(Integer)
<==      Total: 3
+++Customer{id=1, username='TAA', password='123456', birthday=Fri Dec 13 08:00:00 CST 2019, sex=true, address='北京'}
---Account{id=1, money=1000, uid=1}
---Account{id=2, money=2000, uid=1}
---Account{id=3, money=3000, uid=1}
==>  Preparing: select * from t_account where uid = ? 
==> Parameters: 2(Integer)
<==      Total: 2
+++Customer{id=2, username='TBB', password='123456', birthday=Tue Dec 10 08:00:00 CST 2019, sex=false, address='上海'}
---Account{id=4, money=4000, uid=2}
---Account{id=5, money=5000, uid=2}
==>  Preparing: select * from t_account where uid = ? 
==> Parameters: 3(Integer)
<==      Total: 1
+++Customer{id=3, username='TCC', password='123456', birthday=Wed Dec 04 08:00:00 CST 2019, sex=true, address='广州'}
---Account{id=6, money=6000, uid=3}
复制代码

把for循环注释了,

@Test
    public void testFindAllWithAccount2() {
        ICustomerDao mapper = sqlSession.getMapper(ICustomerDao.class);
        List<Customer> customers = mapper.findAllWithAccount2();
       /* for (Customer customer : customers) {
            System.out.println("+++" + customer);
            List<Account> accounts = customer.getAccounts();
            for (Account account : accounts) {
                System.out.println("---" + account);
            }
        }*/
    }
复制代码

观察输出:

==>  Preparing: select * from t_customer 
==> Parameters: 
<==      Total: 3
复制代码

至此,完成了一对多的延迟加载

一级缓存

一级缓存:

​ 它指的是Mybatis中SqlSession对象的缓存。

​ 当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。

​ 该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中

​ 查询是否有,有的话直接拿出来用。

​ 当SqlSession对象消失时,mybatis的一级缓存也就消失了。

触发清空一级缓存的情况:

​ 一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。

使用AccountTest这个测试类来演示一级缓存:

  • 查询id=4的账户信息
/**
 * AccountFirstCacheTest一级缓存测试
 */
public class AccountFirstCacheTest {

    InputStream is;
    SqlSession sqlSession;
    SqlSessionFactory factory;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 根据id查询账户信息
     */
    @Test
    public void testFindById() {
        IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
        Account account = mapper.findById(4);
        Account account2 = mapper.findById(4);
        System.out.println(account);
        System.out.println(account2);
    }
}
复制代码

运行日志输出:

==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
==> Parameters: 4(Integer)
<==      Total: 1
Account{id=4, money=4000, uid=2}
Account{id=4, money=4000, uid=2}
复制代码
  • 查询不同账户id

    ...  
       @Test
        public void testFindById() {
            IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
            Account account = mapper.findById(4);
            Account account2 = mapper.findById(1);
            System.out.println(account);
            System.out.println(account2);
        }
    ...
    复制代码

    日志输出:

    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 4(Integer)
    <==      Total: 1
    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 1(Integer)
    <==      Total: 1
    
    Account{id=4, money=4000, uid=2}
    Account{id=1, money=1000, uid=1}
    复制代码
  • 清空sqlSession缓存

    ...
        public void testFindById() {
            IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
            Account account = mapper.findById(4);
    
            //此方法也可以清空缓存
            //sqlSession.clearCache();
            sqlSession.close();
            sqlSession = factory.openSession();
            mapper = sqlSession.getMapper(IAccountDao.class);
    
            Account account2 = mapper.findById(4);
            System.out.println(account);
            System.out.println(account2);
        }
    ...
    复制代码

    日志:

    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 4(Integer)
    <==      Total: 1
    Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3]
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3]
    Returned connection 2028017635 to pool.
    Opening JDBC Connection
    Checked out connection 2028017635 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@78e117e3]
    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 4(Integer)
    <==      Total: 1
    
    Account{id=4, money=4000, uid=2}
    Account{id=4, money=4000, uid=2}
    复制代码
  • clearCache

    @Test
        public void testFindById() {
            IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
            Account account = mapper.findById(4);
    
            //此方法也可以清空缓存
            sqlSession.clearCache();
            /*sqlSession.close();
            sqlSession = factory.openSession();
            mapper = sqlSession.getMapper(IAccountDao.class);*/
    
            Account account2 = mapper.findById(4);
            System.out.println(account);
            System.out.println(account2);
        }
    复制代码

    日志:

    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 4(Integer)
    <==      Total: 1
    ==>  Preparing: SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = ? AND a.uid = c.id 
    ==> Parameters: 4(Integer)
    <==      Total: 1
    
    Account{id=4, money=4000, uid=2}
    Account{id=4, money=4000, uid=2}
    复制代码

二级缓存

二级缓存:

​ 它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。

​ 二级缓存的使用步骤:

​ 第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)

​ 第二步:让当前的映射文件支持二级缓存(在IAccountDao.xml中配置)

​ 第三步:让当前的操作支持二级缓存(在select标签中配置)

AccountSecondCacheTest:

/**
 * AccountFirstCacheTest一级缓存测试
 */
public class AccountSecondCacheTest {

    InputStream is;
    SqlSession sqlSession;
    SqlSessionFactory factory;

    @Before
    public void init() throws IOException {
        //1. 读取配置文件
        is = Resources.getResourceAsStream("SqlMapConfig.xml");
        //2. 创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(is);
        //3. 使用工厂生产SqlSession对象
        sqlSession = factory.openSession();
    }

    /**
     * 根据id查询账户信息
     */
    @Test
    public void testFindById() {
        IAccountDao mapper = sqlSession.getMapper(IAccountDao.class);
        Account account = mapper.findById(4);

        //一级缓存消失,注意这里不能用clearCache方法了
        sqlSession.close();

        SqlSession sqlSession2 = factory.openSession();
        IAccountDao mapper2 = sqlSession2.getMapper(IAccountDao.class);
        Account account2 = mapper2.findById(4);
        System.out.println(account);
        System.out.println(account2);
        System.out.println(account == account2);
    }
}
复制代码

SqlMapConfig.xml开启二级缓存setting标签:

...
    <!--配置参数-->
    <settings>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
        <!--开启延迟加载功能-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
...
复制代码

IAccountDao.xml映射文件支持二级缓存cache标签:

<mapper namespace="com.ivyzh.dao.IAccountDao">

    <!--开启account支持二级缓存-->
    <cache/>
    ...
</mapper>    
复制代码

让当前操作支持二级缓存useCache="true":

...
    <!--配置Account的resultMap 延迟加载-->
    <resultMap id="acMapLazy" type="account">
        <id property="id" column="aid"/>
        <result property="money" column="money"/>
        <result property="uid" column="uid"/>
        <!--配置account对象中customer集合的映射-->
        <association property="customer" column="uid" javaType="customer" select="com.ivyzh.dao.ICustomerDao.findById"/>
    </resultMap>
    <!--查询所有-->
    <select id="findById" parameterType="int" resultMap="acMap" useCache="true">
       SELECT a.id AS aid,a.money AS money,a.uid AS uid, c.* FROM t_account a,t_customer c WHERE a.id = #{aid} AND a.uid = c.id
    </select>
...
复制代码

Mybatis注解开发

  • 支持常规CRUD和其他操作
    • @Select
    • @Insert
    • @Update
    • @Delete
  • 支持一对一查询
    • @One
      • fetchType:LAZY、EAGER、DEAFULT
  • 支持一对多查询,含延迟功能。
    • @Many
      • fetchType:LAZY、EAGER、DEAFULT
  • 支持二级缓存(一级缓存不用开启,自动就有了)
    • @CacheNamespace(blocking = true)

因为在实际开发当中很少使用注解形式,所以具体案例以后再补充。

PageHelper插件

pagehelper.github.io/

作者有本书《MyBatis 从入门到精通》

PageHelper说白了,是一个拦截器。

pom.xml引入坐标:

<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.11</version>
</dependency>
复制代码

在 MyBatis 配置 SqlMapConfig.xml 中配置拦截器插件

<!-- 
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?, 
    typeAliases?, typeHandlers?, 
    objectFactory?,objectWrapperFactory?, 
    plugins?, 
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>
复制代码

分页插件支持以下几种调用方式:

//第一种,RowBounds方式的调用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));

//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(
            @Param("user") User user,
            @Param("pageNum") int pageNum, 
            @Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);

//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
    //其他fields
    //下面两个参数名和 params 配置的名字一致
    private Integer pageNum;
    private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<User> list = userMapper.selectByPageNumSize(user);

//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectLike(user);
    }
});
//lambda
total = PageHelper.count(()->userMapper.selectLike(user));
复制代码

推荐第二种和第三种。

逆向工程MyBatis-Generotor

一句话总结:项目初期可以使用,后面就不推荐使用了。

以后再找案例说明。

总结

待完善内容

  • mybatis入门案例中的设计模式分析
  • 自定义Mybatis
  • 事务控制

问题记录

  • package标签在封装mapper的时候,报错。
    • 已解决: blog.csdn.net/weixin_4395…
    • 适用于类路径下,接口文件与映射文件在 同一路径 下,且接口名与映射 文件名相同 ,并且映射文件命名为接口全类名的情况

欢迎反馈

文章不足之处,还望指正交流。有啥意见和建议的都可以提出来,后续会继续改进:smiley:

准备写一系列的文章MyBatis、MyBatisPlus、Spring、SpringMVC、Oracle、Maven、ElasticSearch、SpringBoot、SpringCloud、RabbitMQ、Nginx...

原文  https://juejin.im/post/5df8ce066fb9a0166316d333
正文到此结束
Loading...