MyBatis

MyBatis

思维导图:点击打开

1.框架概述

1.1三层架构

三层架构分别是:

界面层(User Interface Layer)、业务逻辑层(Business Logic Layer)、数据访问层(Date Access Layer)。

  1. 界面层(表示层/视图层):和用户打交道的,用于接收用户发送过来的请求参数,显示请求的处理结果。
  2. 业务逻辑层:用于接收界面层传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
  3. 数据访问层(持久层):和数据库打交道的,主要实现对表中数据的增删查改。

三层架构对应的包:

  1. 界面层:controller包(servlet)
  2. 业务逻辑层:service包(XXXService类)
  3. 数据访问层:dao包(XXXDao类)

三层架构间处理请求的交互:

三层架构对应的处理框架:

  1. 界面层:servlet---->SpringMVC(框架)
  2. 业务逻辑层:service类---->Spring(框架)
  3. 数据访问层:dao类---->MyBatis(框架)

1.2框架的概念

框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件以及构件实例间交互的方法。简单来说,框架就是半成品软件,就是一组组件,供你完成自己的系统。

另一种说法认为,框架是可被应用开发者定制的应用骨架、模板。比如说常见的ppt模板和简历模板,都是预先规定好了一些条款和内容样式,然后再加入自己的东西,实现自己需要的功能。

框架是安全的,可复用的,不断升级的软件。

1.3MyBatis框架概述

1.3.1什么是MyBatis?

Mybatis是MyBatis SQL Mapper Framework for Java(SQL映射框架)

1)SQL Mapper:SQL映射

  1. 可以把数据库表中的一行数据映射为一个java对象。
  2. 一行数据就可以看作是一个java对象。操作这个对象,就相当于操作表中的数据。

2)Data Access Objects(DAOs):数据访问,对数据库执行增删改查

  • MyBatis是一款优秀的持久层框架
  • 它支持自定义SQL、存储过程以及高级映射。
  • MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
  • MyBatis可以通过简单的XML或注解,来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
  • MyBatis本是apache的一个开源项目iBatis,2010年这个项目迁移到了google code,并改名为MyBatis。
  • 2013年11月迁移到Github。

1.3.2如何获取MyBatis?

1.第一种:添加依赖

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

2.第二种:下载jar包

GitHub:https://github.com/mybatis/mybatis-3/releases

3.中文帮助文档:https://mybatis.org/mybatis-3/zh/index.html

1.3.3什么是数据持久层?

数据持久化是将程序中的数据从瞬时状态转化为持久状态的过程。

数据在内存中时,断电即失。为了让一些重要的数据持久化,可以使用JDBC等将数据保存到数据库中。(IO文件持久化)。

数据持久层就是完成数据持久化的代码模块。

1.3.4为什么需要MyBatis取代JDBC?

JDBC的缺陷:

  1. 代码比较多,开发效率低
  2. 代码冗余
  3. 需要手动对Connection、Statement和ResultSet对象进行创建和销毁
  4. 对ResultSet查询的结果需要自己封装进集合
  5. 业务代码和数据库的操作混合在一起

MyBatis提供的功能:

  1. 提供了自动创建Connection、Statement和ResultSet对象的能力,不用开发人员手动创建了
  2. 提供了自动执行sql语句的能力,不用开发人员手动执行语句
  3. 提供了自动处理查询结果集的能力,自动把sql的结果转为java对象、List集合
  4. 提供了自动关闭资源的能力,不需要开发人员手动关闭
  5. 开发人员只需要提供sql语句给MyBatis,Mybatis会自动处理sql语句,返回存放着表中数据的List集合或java对象。

1.3.5总结

MyBatis是一个sql映射框架,提供对数据库的操作功能,是一个增强的JDBC。

使用MyBatis可以让开发人员集中精神写SQL语句,不用关心Connection、Statement、ResultSet对象的创建和销毁以及SQL语句的执行。

2.编写一个MyBatis程序

2.1实现步骤

思路:搭建环境---->导入MyBatis---->编写代码---->测试

步骤:

1.创建数据库及数据库表

2.创建maven项目,添加依赖和插件

3.创建数据库表对应实体类

4.创建持久层的Dao接口,定义操作数据库的方法

5.创建一个mybatis使用的配置文件,叫sql映射文件。

  • 用于写sql语句,一般一个表一个sql映射文件
  • 文件类型是xml
  • 在步骤4的Dao接口同级目录下创建,要求文件名称与接口名一致。

6.创建mybatis的主配置文件

  • 一个项目只有一个主配置文件
  • 主配置文件提供了数据库的连接信息和sql映射文件的位置信息

7.创建使用mybatis的类,通过mybatis访问数据库

2.2创建数据库及数据库表

sql语句:

create database `db_mybatis`;
use `db_mybatis`;
create table `t_student`(
  `id` int primary key,
  `name` varchar(255) default null,
  `email` varchar(255) default null,
  `age` int default null
)engine=InnoDB default charset=utf8;
insert into `t_student`(`id`,`name`,`email`,`age`) values
(1001,'张三','zhangsan@qq.com',20),
(1002,'李四','lisi@qq.com',21),
(1003,'王五','wangwu@qq.com',22);
select * from `t_student`;

2.3创建maven项目

1.新建一个普通的父maven项目【Mybatis-Project】

2.删除父maven项目的src目录

3.在pom.xml文件里添加maven依赖和maven插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tsccg</groupId>
    <artifactId>MyBatis-Project</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <!-- 添加maven依赖 -->
    <dependencies>
        <!-- 单元测试依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- mybatis依赖 -->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- mysql驱动依赖 -->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
    </dependencies>
    <!--添加maven插件-->
    <build>
        <!-- 指定编译时扫描范围 -->
        <resources>
            <resource>
                <directory>src/main/java</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties和.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties和.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
        <!-- 配置jdk版本为1.8 -->
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.创建一个子模块

创建一个子maven模块【mybatis-01】,这个子模块可以继承父模块的所有配置

2.4创建数据库表对应实体类

在子模块的main/java目录下,创建t_student表对应实体类【com.tsccg.entity.Student】

实体类用于存放表中的某一条记录信息

package com.tsccg.entity;

/**
 * @Author: TSCCG
 * @Date: 2021/09/06 21:40
 * t_student表对应实体类
 */
public class Student {
    //定义属性名,要求和t_student表的列名一致
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    public Student() {
    }

    public Student(Integer id, String name, String email, Integer age) {
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

2.5创建持久层的Dao接口

在子模块的main/java目录下,创建Dao接口【com.tsccg.dao.StudentDao】

在Dao接口中定义操作数据库的方法,这里先只定义了查询方法

package com.tsccg.dao;

import com.tsccg.entity.Student;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/06 21:59
 * 接口操作t_student表
 */
public interface StudentDao {
    /**
     * 查询表中所有数据
     * @return 返回一个存放Student对象的List集合
     */
    List<Student> findAll();
}

2.6创建sql映射文件

一个mybatis使用的配置文件,叫sql映射文件,也叫sql mapper文件。

在上一步创建的StudentDao接口同级目录下,创建一个同名的映射文件【StudentDao.xml】

在映射文件里编写sql语句,mybatis会执行编写的sql语句。

不过不能直接就上去写,需要参照MyBatis帮助文档中所规定的格式。

将文中代码直接复制到StudentDao.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="org.mybatis.example.BlogMapper">
  <select id="selectBlog" resultType="Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>

代码说明:

1.指定约束文件

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

其中,【mybatis-3-mapper.dtd】是约束文件的名称,拓展名为.dtd

约束文件作用:

  1. 限制、检查出现在映射文件中的标签和属性,检验是否符合mybatis规范。
  2. 只有符合规范的标签才能被mybatis所识别。
  3. 这一部分的语句是固定的,不需要记忆,直接复制即可。

2.mapper:当前文件的根标签

<!--
mapper:当前文件的根标签

namespace:命名空间,自定义的唯一字符串。
要求使用Dao接口的全限定名称:com.tsccg.dao.StudentDao
-->
<mapper namespace="com.tsccg.dao.StudentDao">
  <!-- 
   select:表示查询操作
	在当前文件中,可以使用特定的标签,表示对数据库的特定操作:
        <select>:表示执行查询操作,在此标签中编写select语句
        <update>:表示执行更新操作,在此标签中编写update语句
        <insert>:表示执行插入操作,在此标签中编写insert语句
        <delete>:表示执行删除操作,在此标签中编写delete语句

	id:你要执行的sql语句的唯一标识,mybatis会根据这个id的值找到要执行的sql语句
		id的值可以自定义,但是要求使用接口中的方法名,这是在开发中的规则

	resultType:表示查询语句的返回结果类型。使用全限定类名
  -->
  <select id="findAll" resultType="com.tsccg.entity.Student">
      <!--需要执行的sql语句-->
    select * from t_student order by id asc
  </select>
</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.tsccg.dao.StudentDao">
    <select id="findAll" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student order by id asc
    </select>
</mapper>

2.7创建主配置文件

一个项目只有一个主配置文件,同样是xml格式。

主配置文件提供了数据库的连接信息和sql映射文件的位置信息。

在main目录下的resources里,创建主配置文件【mybatis.xml】

MyBatis帮助文档中找到主配置文件的标准格式,复制到mybatis.xml里去。

主配置文件标准格式:

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 指定约束文件 -->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--environments:环境配置(数据库的连接信息)。可以配置多个环境
        default:告诉mybatis使用哪个数据库的连接信息。
      -->
    <environments default="mydev">
        <!-- environment:一个数据库信息的配置,环境
              id:表示环境的名称,是自定义的唯一变量
        -->
        <environment id="mydev">
            <!-- transactionManager:mybatis的事务类型
                  type:JDBC(表示使用JDBC中的Connection对象的commit,rollback做事务处理)
              -->
            <transactionManager type="JDBC"/>
            <!-- dataSource:表示数据源,连接数据库的
                  type:表示数据源的类型,POOLED表示使用连接池
              -->
            <dataSource type="POOLED">
                <!-- driver,url,username,password都是固定的,不能自定义 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis?useUnicode=true&amp;serverTimezone=GMT&amp;useSSL=false&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 指定sql mapper(sql映射文件)的位置
          注意:如果之前在pom.xml文件里,没有添加maven插件去指定扫描src/main/java和src/main/resources目录下的xml文件,这里是找不到映射文件的
          -->
    <mappers>
        <!-- 一个mapper标签指定一个文件的位置
             从类路径(target/classes)开始的路径信息
        -->
        <mapper resource="com/tsccg/dao/StudentDao.xml"/>
    </mappers>
</configuration>

2.8创建查询语句测试类

创建Mybatis的SqlSession对象执行sql语句

package com.tsccg;

import com.tsccg.entity.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

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

/**
 * @Author: TSCCG
 * @Date: 2021/09/07 01:22
 * 访问mybatis读取t_student表中信息
 */
public class MyApp {
    public static void main(String[] args) throws IOException {
        //1.定义mybatis主配置文件的名称,从类路径的根开始(target/classes)
        String config = "mybatis.xml";
        //2.读取这个config表示的文件
        InputStream in = Resources.getResourceAsStream(config);
        //3.创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //4.创建SqlSessionFactory对象
        SqlSessionFactory factory = builder.build(in);
        //5.【重要】获取SqlSession对象,从SqlSessionFactory中获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //6.【重要】指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
        String sqlId = "com.tsccg.dao.StudentDao.findAll";
        //7.执行sql语句,通过sqlId找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        //8.输出结果
        for (Student student : studentList) {
            System.out.println(student);
        }
        //9.关闭SqlSession对象
        sqlSession.close();
    }
}

2.9运行测试

我用的maven是3.3.9;idea是2020.1。

运行时出现了Error:(3, 28) java: 程序包org.apache.ibatis.io不存在的错误,

如下图:

这说明maven的版本和idea不兼容。

解决方法:

打开settings,进行如下操作

重新运行:

这次虽然已经查出数据了,但是显示中文乱码。

解决方案:

设置idea server编码。在菜单栏找到”Run->Editconfigration” 在右侧设置vm option为-Dfile.encoding=utf-8

-Dfile.encoding=utf-8

重新运行程序:

还是乱码?莫慌。重新打开该项目:

运行成功。

如果仍未解决可以看看这篇博客:https://www.cnblogs.com/wcxcc/p/11545076.html

2.10打印日志

在mybatis.xml文件中加入日志配置,可以在控制台输出执行的 sql语句和参数

在指定约束条件后面添加下面语句:

<?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>
    <!--settings:控制mybatis全局行为-->
    <settings>
        <!--设置mybatis向控制台输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

重新运行项目,显示日志:

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building mybatis-01 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ mybatis-01 ---
//初始化,调用mybatis的日志实现类StdOutImpl
Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.//使用连接池
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
//开启数据库连接
Opening JDBC Connection
//创建Connection对象
Created connection 1589683045.
//关闭自动提交事务(开启一个事务)
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//需要执行的预编译sql语句
==>  Preparing: select id,name,email,age from t_student order by id asc 
==> Parameters://向预编译的sql语句中添加参数(这里执行的是查询语句,故没有参数)
		//查询结果
<==    Columns: id, name, email, age//字段名
<==        Row: 1001, 张三, zhangsan@qq.com, 20//记录
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 王五, wangwu@qq.com, 22
<==      Total: 3//共得到3条记录
//我们自己写的打印查询到的3个Student对象
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='王五', email='wangwu@qq.com', age=22}
//开启事务的自动提交(关闭事务)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//关闭连接
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5ec0a365]
//将Connection对象归还给连接池,方便其他地方再次调用
Returned connection 1589683045 to pool.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.954 s
[INFO] Finished at: 2021-09-07T20:40:57+08:00
[INFO] Final Memory: 11M/153M
[INFO] ------------------------------------------------------------------------

2.11补全基本的CRUD

2.11.1在StudentDao接口中添加方法

package com.tsccg.dao;

import com.tsccg.entity.Student;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/06 21:59
 * 接口操作t_student表
 */
public interface StudentDao {
    /**
     * 查询表中所有数据
     * @return 返回一个存放Student对象的List集合
     */
    List<Student> findAll();

    /**
     * 向表中插入记录
     * @return 返回影响的记录条数
     */
    int insertStudent();

    /**
     * 删除表中记录
     * @return 返回影响条数
     */
    int deleteStudent();

    /**
     * 更新表中纪律
     * @return 返回影响条数
     */
    int updateStudent();
}

2.11.2在sql映射文件里添加sql语句

<?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.tsccg.dao.StudentDao">
    <!--查询操作-->
    <select id="findAll" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student order by id asc
    </select>
    <!--插入操作-->
    <insert id="insertStudent">
		<!--这里的#{}指的是sql语句里的占位符【?】;id、name指的是Student实体类里的id、name属性,当插入一条记录时,传过来的是一个保存着数据的Student对象,这样可以直接读取对象里的数据-->
        insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
    </insert>
    <!--删除操作-->
    <delete id="deleteStudent">
        delete from t_student where id = #{id}
    </delete>
    <!--更新操作-->
    <update id="updateStudent">
        update t_student set name = #{name} where id = #{id}
    </update>
</mapper>

2.11.3编写测试方法

为了方便起见,把CRUD都搬到测试类里实现。

在test/java目录下新建【com.tsccg.TestStudentDao】

package com.tsccg;

import com.tsccg.entity.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

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

/**
 * @Author: TSCCG
 * @Date: 2021/09/07 17:58
 * 未使用工具类的测试类
 */
public class TestStudentDao {
    /**
     * 获取SqlSession对象
     * @return 返回SqlSession对象
     * @throws IOException
     */
    private static SqlSession getSqlSession() throws IOException {
        String config = "mybatis.xml";
        InputStream in = Resources.getResourceAsStream(config);
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(in);
        //获取非自动提交的SqlSession对象,从SqlSessionFactory中获取SqlSession
        return factory.openSession();
    }

    /**
     * 测试查询功能
     * @throws IOException
     */
    @Test
    public void testFindAll() throws IOException {
        //【重要】获取SqlSession对象,从SqlSessionFactory中获取SqlSession
        SqlSession sqlSession = getSqlSession();
        //【重要】指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
        String sqlId = "com.tsccg.dao.StudentDao.findAll";
        //执行sql语句,通过sqlId找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        //输出结果
        for (Student student : studentList) {
            System.out.println(student);
        }
        //关闭SqlSession对象
        sqlSession.close();
    }

    /**
     * 测试插入功能
     * @throws IOException
     */
    @Test
    public void testInsertStudent() throws IOException {
        //获取SqlSession对象
        SqlSession sqlSession = getSqlSession();
        //准备需要插入的Student数据,以实体类对象为载体
        Student newStudent = new Student(1004,"赵六","zhaoliu@qq.com",25);
        //指定要执行的sql语句的表示。sql映射文件中的namespace + "." + 标签id值
        String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
        //执行sql语句,得到处理结果
        int result = sqlSession.insert(sqlId,newStudent);
        //由于我们获取的是一个非自动提交的SqlSession对象,所以需要手动提交事务
        sqlSession.commit();
        //输出结果
        System.out.println("insert执行结果:" + result);
        //关闭SqlSession对象
        sqlSession.close();
    }

    /**
     * 测试删除功能
     * @throws IOException
     */
    @Test
    public void testDeleteStudent() throws IOException {
        SqlSession sqlSession = getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
        int result = sqlSession.delete(sqlId,1004);
        //提交事务
        sqlSession.commit();
        System.out.println("delete执行结果:" + result);
        sqlSession.close();
    }

    /**
     * 测试更新功能
     * @throws IOException
     */
    @Test
    public void testUpdateStudent() throws IOException {
        SqlSession sqlSession = getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
        Student newStudent = new Student(1003,"张飞",null,null);
        int result = sqlSession.update(sqlId,newStudent);
        //提交事务
        sqlSession.commit();
        System.out.println("update执行结果:" + result);
        sqlSession.close();
    }

}

2.11.4开始测试

1.测试插入功能

2.测试删除功能

3.测试更新功能

3.MyBatis对象分析

3.1使用mybatis时用到的对象

IDEA快捷键:ctrl+h---->显示实现类和父类

3.1.1Resources类

Resources是mybatis中的一个类,负责读取主配置文件。

有很多方法通过加载并解析资源文件,返回不同类型的IO流对象。

InputStream in = Resources.getResourceAsStream("mybatis.xml");

3.1.2SqlSessionFactoryBuilder类

其对象负责调用build()方法创建SqlSessionFactory对象。

SqlSessionFactoryBuilder对象在创建完SqlSessionFactory对象后,就完成了其使命,可以被随即销毁掉。因此,一般会将该对象创建为一个方法内的局部对象,方法一结束,对象就被销毁。

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);

3.1.3SqlSessionFactory接口【重要】

SqlSessionFactory接口对象是一个重量级对象。程序创建该对象的耗时比较长,使用资源较多,是线程安全的,在整个项目中,有一个就够用了。

其职责是调用openSession()方法创建SqlSession对象。

SqlSession sqlSession = factory.openSession();
  1. openSession(true):创建一个有自动提交功能的SqlSession对象
  2. openSession(false):创建一个没有自动提交功能的SqlSession对象,执行完增删改操作后需要手动提交
  3. openSession():无参数时默认同openSession(false)

3.1.4SqlSession接口【重要】

SqlSession接口对象用于执行持久化操作。一个SqlSession对象对应一次数据库会话,一次会话以SqlSession对象的创建开始,以SqlSession对象的关闭结束。

SqlSession接口定义了操作数据的方法。如:selectOne()、selectList()、insert()、update()、delete()、commit()、rollback()

其实现类为:DefaultSqlSession(可以使用ctrl+h找到其实现类)

使用要求:

SqlSession接口对象不是线程安全的,在每次数据库会话结束前,需要马上调用其close()方法,将其关闭。

为了实现这点,需要在方法内部创建,使用完毕后关闭。

3.2封装Mybatis工具类

3.2.1工具类

package com.tsccg.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @Author: TSCCG
 * @Date: 2021/09/08 14:08
 * MyBatis工具类
 */
public class MyBatisUtil {
    private static SqlSessionFactory factory = null;
    static {
        //必须和主配置文件名相同
        String config = "mybatis.xml";
        try {
            InputStream in = Resources.getResourceAsStream(config);
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            factory = builder.build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取非自动提交事务的SqlSession对象
     * @return 返回一个SqlSession对象
     */
    public static SqlSession getSqlSession() {
        SqlSession sqlSession = null;
        if (factory != null) {
            //创建非自动提交事务的SqlSession对象
            sqlSession = factory.openSession();
        }
        return sqlSession;
    }
}

3.2.2测试类

package com.tsccg;

import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/07 17:58
 * 使用工具类的测试类
 */
public class TestStudentDao2 {
    /**
     * 测试查询功能
     */
    @Test
    public void testFindAll() {
        //获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.findAll";
        List<Student> studentList = sqlSession.selectList(sqlId);
        //输出结果
        for (Student student : studentList) {
            System.out.println(student);
        }
        //关闭SqlSession对象
        sqlSession.close();
    }

    /**
     * 测试插入功能
     */
    @Test
    public void testInsertStudent() {
        //获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        Student newStudent = new Student(1004,"赵六","zhaoliu@qq.com",25);
        String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
        int result = sqlSession.insert(sqlId,newStudent);
        sqlSession.commit();
        //输出结果
        System.out.println("insert执行结果:" + result);
        //关闭SqlSession对象
        sqlSession.close();
    }

    /**
     * 测试删除功能
     */
    @Test
    public void testDeleteStudent() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
        int result = sqlSession.delete(sqlId,1004);
        //提交事务
        sqlSession.commit();
        System.out.println("delete执行结果:" + result);
        sqlSession.close();
    }

    /**
     * 测试更新功能
     */
    @Test
    public void testUpdateStudent() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
        Student newStudent = new Student(1003,"张飞",null,null);
        int result = sqlSession.update(sqlId,newStudent);
        //提交事务
        sqlSession.commit();
        System.out.println("update执行结果:" + result);
        sqlSession.close();
    }
}

测试结果:

4.Mybatis使用传统方式进行Dao开发

使用传统方式就是使用Dao的实现类来操作数据库

4.1传统Dao开发方式步骤

4.1.1创建Dao接口实现类

package com.tsccg.dao.impl;

import com.tsccg.dao.StudentDao;
import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/08 16:41
 * dao实现类
 */
public class StudentDaoImpl implements StudentDao {

    @Override
    public List<Student> selectStudents() {
        //通过工具类获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //指定要执行的sql语句的标识:sql映射文件中的namespace + "." + 标签id值
        String sqlId = "com.tsccg.dao.StudentDao.selectStudents";
        //执行sql语句,通过sqlId找到语句
        List<Student> studentList = sqlSession.selectList(sqlId);
        //关闭资源
        sqlSession.close();
        //将执行结果返回
        return studentList;
    }

    @Override
    public int insertStudent(Student student) {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.insertStudent";
        int result = sqlSession.insert(sqlId,student);
        //提交事务
        sqlSession.commit();
        sqlSession.close();
        return result;
    }
    @Override
    public int deleteStudent(Integer id) {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.deleteStudent";
        int result = sqlSession.delete(sqlId,id);
        sqlSession.commit();
        sqlSession.close();
        return result;
    }

    @Override
    public int updateStudent(Student student) {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        String sqlId = "com.tsccg.dao.StudentDao.updateStudent";
        int result = sqlSession.update(sqlId,student);
        sqlSession.commit();
        sqlSession.close();
        return result;
    }
}

4.1.2在sql mapper文件里编写sql语句

<?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.tsccg.dao.StudentDao">
    
    <select id="selectStudents" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student
    </select>
    <insert id="insertStudent">
        insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
    </insert>
    <delete id="deleteStudent">
        delete from t_student where id = #{id}
    </delete>
    <update id="updateStudent">
        update t_student set name = #{name} where id = #{id}
    </update>
    
</mapper>

4.1.3创建测试类

package com.tsccg;

import com.tsccg.dao.StudentDao;
import com.tsccg.dao.impl.StudentDaoImpl;
import com.tsccg.entity.Student;
import org.junit.Test;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/08 16:34
 * 调用dao接口实现类进行测试
 */
public class TestStudentDao {
    @Test
    public void testSelectStudents() {
        //创建dao接口实现类对象
        StudentDao dao = new StudentDaoImpl();
        //调用实现类对象执行查询方法,得到处理结果
        List<Student> studentList = dao.selectStudents();
        //输出查询结果
        for (Student student : studentList) {
            System.out.println(student);
        }
    }
    @Test
    public void testInsertStudents() {
        //创建实现类对象
        StudentDao dao = new StudentDaoImpl();
        Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
        //调用插入方法
        int result = dao.insertStudent(student);
        System.out.println("insert执行结果:" + result);
    }
    @Test
    public void testDeleteStudents() {
        StudentDao dao = new StudentDaoImpl();
        int id = 1004;
        int result = dao.deleteStudent(id);
        System.out.println("delete执行结果:" + result);
    }
    @Test
    public void testUpdateStudents() {
        StudentDao dao = new StudentDaoImpl();
        Student student = new Student(1004,"关羽",null,null);
        int result = dao.updateStudent(student);
        System.out.println("update执行结果:" + result);
    }
}

4.2分析传统Dao开发方式

我们来看上面Dao接口实现类做了什么工作(以实现类的selectStudents方法为例):

@Override
public List<Student> selectStudents() {
    //通过工具类获取SqlSession对象
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    //指定要执行的sql语句的标识:sql映射文件中的namespace + "." + 标签id值
    String sqlId = "com.tsccg.dao.StudentDao.selectStudents";
    //执行sql语句,通过sqlId找到语句
    List<Student> studentList = sqlSession.selectList(sqlId);
    //关闭资源
    sqlSession.close();
    //将执行结果返回
    return studentList;
}

我们可以发现,Dao的实现类并没有做什么实质性的工作,仅仅是通过SqlSession的相关API定位到映射文件mapper中相应id的SQL语句。

真正对数据库进行操作的工作其实是由框架通过映射文件mapper中的SQL完成的。

<mapper namespace="com.tsccg.dao.StudentDao">
    <select id="selectStudents" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student
    </select>
</mapper>

因此,MyBatis框架就通过动态代理的方式摒弃了Dao的实现类,直接定位到mapper映射文件中的相应sql语句,对数据库进行操作。这种对Dao接口的实现方式被称为Mapper动态代理方式。

Mapper动态代理方式不需要程序员创建Dao接口实现类,实现类由MyBatis结合映射文件通过动态代理创建的。

5.MyBatis框架Dao代理

5.1分析使用动态代理的条件

我们先来看看使用传统方式进行Dao开发时sql语句的执行方式:

//创建dao接口实现类对象
StudentDao dao = new StudentDaoImpl();
//调用实现类对象执行查询方法,得到处理结果
List<Student> studentList = dao.selectStudents();

1.dao接口对象:

  1. 类型:StudentDao
  2. 全限定名称:com.tsccg.dao.StudentDao,和mapper映射文件里的namespace属性值一致

2.调用的方法名:selectStudents,和mapper映射文件中的id属性值一致

3.通过dao接口里方法的返回值可以确定MyBatis要调用SqlSession里哪一个方法

  1. 如果返回值是List,调用的是SqlSession.selectList()方法
  2. 如果返回值是int或其他非List类型,那么可以查看mapper文件里的sql语句标签< select>,< insert>来确定方法

MyBatis的动态代理:

mybatis根据dao的方法调用,获取执行sql语句的信息,根据这些信息创建出一个dao接口的实现类,并创建这个类的对象。然后通过SqlSession对象调用相应方法,访问数据库。

5.2Mybatis使用Dao代理进行开发

5.2.1调用getMapper方法获取代理对象

只需要调用SqlSession的getMapper()方法,就可以获取指定接口的实现类对象。该对象的参数为指定Dao接口的class值。

SqlSession session = factory.openSession(); 
//使用mapper动态代理获取接口的代理对象
StudentDao dao = session.getMapper(StudentDao.class);

使用工具类:

SqlSession sqlSession = MyBatisUtil.getSqlSession();
//使用mapper动态代理获取接口的代理对象
StudentDao dao = sqlSession.getMapper(StudentDao.class);

5.2.2使用Dao代理对象执行sql语句

package com.tsccg;

import com.tsccg.dao.StudentDao;
import com.tsccg.entity.Student;
import com.tsccg.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

/**
 * @Author: TSCCG
 * @Date: 2021/09/08 16:34
 * 使用mapper动态代理
 */
public class TestStudentDao {
    @Test
    public void testSelectStudents() {
        //调用工具类获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //使用mapper动态代理获取接口的代理对象
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        List<Student> studentList = dao.selectStudents();
        for (Student student : studentList) {
            System.out.println(student);
        }
    }
    @Test
    public void testInsertStudents() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
        //调用插入方法
        int result = dao.insertStudent(student);
        //提交事务
        sqlSession.commit();
        System.out.println("insert执行结果:" + result);
    }
    @Test
    public void testDeleteStudents() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        int id = 1004;
        int result = dao.deleteStudent(id);
        //提交事务
        sqlSession.commit();
        System.out.println("delete执行结果:" + result);
    }
    @Test
    public void testUpdateStudents() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        Student student = new Student(1004,"关羽",null,null);
        int result = dao.updateStudent(student);
        //提交事务
        sqlSession.commit();
        System.out.println("update执行结果:" + result);
    }
}

测试结果:

5.3深入理解参数

5.3.1parameterType

我们在使用dao代理执行sql语句时,需要从java代码把参数传递到mapper映射文件,而parameterType就是给sql语句指明传过去的参数是什么类型的。

parameterType的值是java的数据类型全限定名称或者是mybatis定义的别名

如:parameterType="java.lang.Integer"【全限定名称】

​ parameterType="int"【mybatis定义的别名】

传参单位:dao代理对象

@Test
public void testDeleteStudents() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    int id = 1004;//参数id
    int result = dao.deleteStudent(id);//传参
    //提交事务
    sqlSession.commit();
    System.out.println("delete执行结果:" + result);
}

接收参数单位:mapper映射文件

<mapper namespace="com.tsccg.dao.StudentDao">
    <!--指明参数类型是java.lang.Integer-->
    <delete id="deleteStudent" parameterType="java.lang.Integer">
        delete from t_student where id = #{id}
    </delete>
</mapper>

注意:这个属性是可选的,因为mybatis通过反射机制能够自动发现接口参数的数据类型,所以这个属性可以没有,我们一般在开发中也不写。

5.3.2只有一个简单类型参数

简单类型参数:mybatis把String和java基本数据类型(int)以及对应的引用数据类型(Integer)都叫做简单类型。

当Dao接口中方法的参数列表只有一个且为简单类型时,在mapper文件里获取简单类型的参数值,使用:

也就是说#{}里的名称可以是任意的,无论与简单类型参数/是否相同,都能拿到参数值。

//接口中方法:
int deleteStudent(Integer id);

//mapper文件:
<delete id="deleteStudent">
    delete from t_student where id = #{studentId}
</delete>
    
//测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int id = 1004;
int result = dao.deleteStudent(id);

5.3.3mybatis是封装的jdbc操作

使用#{}后,mybatis执行sql是使用jdbc里的PreparedStatement对象。

由mybatis执行下面的代码:(以select为例)

1.mybatis创建Connection,PreparedStatement对象

//#{}就相当于占位符”?“
String sql = "select id,name,email,age from t_student where id = ?";
PreparedStatement ps = conn.preparedStatement(sql);
ps.setInt(1,1004);

2.执行sql,封装为resultType="com.tsccg.entity.Student"这个对象

ResultSet rs = ps.executeQuery();
Student student = null;
while(rs.next()) {
    student = new Student(rs.getInt("id"),rs.getString("name"),rs.getEmail("email"),rs.getInt("age"));
}
return student;//将结果返回

5.3.4多个参数-使用@Param【常用】

当sql语句需要传入多个参数时,如果直接把参数传过去,mapper映射文件会无法识别。

Dao接口方法:
int updateStudent(Integer id,String name);

mapper文件:
<update id="updateStudent">
    update t_student set name = #{name} where id = #{id}
</update>
    
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateStudent(1004,"刘备");//直接传递两个简单类型参数

mapper文件无法识别参数:

为了让mapper文件能够识别传过去的参数,我们可以使用@Param("自定义参数")的方式命名参数。

Dao接口方法:
int updateStudent(@Param("gId") Integer id, @Param("newName") String name);

mapper文件:
<!--多个参数,使用@Param命名参数-->
<update id="updateStudent">
    update t_student set name = #{newName} where id = #{gId}
</update>
    
dao代理传参:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateStudent(1004,"刘备");

mepper可以识别参数:

5.3.5多个参数-使用对象【常用】

使用java对象也可以传递参数,java的属性值就是sql需要的参数值。每一个属性就是一个参数。

语法格式:

#{属性名,javaType=java中数据类型名,jdbcType=数据中类型名}

如:

Dao接口方法:
int insertStudent(Student student);

mapper文件:
<insert id="insertStudent">
    insert into t_student(id,name,email,age) value (
    #{id,javaType=java.lang.Integer,jdbcType=INTEGER},
    #{name,javaType=java.lang.String,jdbcType=VARCHAR},
    #{email,javaType=java.lang.String,jdbcType=VARCHAR},
    #{age,javaType=java.lang.Integer,jdbcType=INTEGER}
    )
</insert>
    
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Student student = new Student(1004,"赵六","zhaoliu@qq.com",30);
int result = dao.insertStudent(student);

当然,如此繁琐的代码一定会有简化方式的。

简化后格式:

#{属性名}

其中,javaType和jdbType的值,mybatis可以通过反射获取,不需要手动提供。

<insert id="insertStudent">
    insert into t_student(id,name,email,age) value(#{id},#{name},#{email},#{age})
</insert>

5.3.6多个参数-按位置传参【少用】

按位置传参语法:

#{arg位置}

参数位置从0开始,第一个参数是#{arg0},第二个参数是#{arg1}

注意:mybatis-3.3版本和之前的版本使用#{0},#{1}方式,从3.4版本开始使用#{arg0}方式

Dao接口方法:
int updateEmailStudent(Integer id,String email);

mapper文件:
<!--按位置-->
<update id="updateEmailStudent">
    update t_student set email = #{arg1} where id = #{arg0}
</update>
    
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
int result = dao.updateEmailStudent(1004,"liubei@qq.com");

当方法定义参数顺序出错时,此方式就会出错,故在开发中使用较少。

5.3.7Map传参【少用】

Map集合可以存储多个值,使用Map向mapper文件一次传入多个参数。

Map集合使用String的key,Object类型的值存储参数。mapper文件使用#{key}引用参数值。

Dao接口方法:
int updateAgeStudent(Map<String,Object> map);

mapper文件:
<!--使用Map传参-->
<update id="updateAgeStudent">
    <!--通过key引用参数值-->
    update t_student set age = #{stuAge} where id = #{stuId}
</update>
    
测试方法:
StudentDao dao = sqlSession.getMapper(StudentDao.class);
Map<String,Object> data = new HashMap<>();
data.put("stuId",1004);
data.put("stuAge",22);
int result = dao.updateAgeStudent(data);

这种方式不推荐使用,理由如下:

  1. 使用Map传参维护成本高。一旦传入的key值发生改变,那么在引用参数时,也必须跟着改变;
  2. 使用Map作为参数可读性差。无法从“Map<String,Object> map”中明确参数值是什么类型,有几个参数值。

5.3.8#和$的区别

:占位符:

  1. 告诉mybatis使用实际的参数值代替所在位置
  2. 使用PreparedStatement对象执行sql语句,用#{...}代替sql语句的“?”
  3. 这样做可以防止sql注入,我们通常使用的都是这个

$:字符串替换:

  1. 告诉mybatis使用$包含的字符串替换所在位置
  2. 使用Statement对象执行sql语句,把sql语句和${}的内容拼接起来
  3. 不能防止sql注入,主要用于替换表名,列名,不同列排序等操作

我们打开日志输出:

<settings>
    <!--设置mybatis向控制台输出日志-->
    <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

1.使用#:

select id,name,email,age from t_student where name = #{studentName}

打印日志:

==>  Preparing: select id,name,email,age from t_student where name = ? 
==> Parameters: 张三(String)

2.使用$:

select id,name,email,age from t_student where name = ${studentName}

打印日志:

==>  Preparing: select id,name,email,age from t_student where name = '张三' 
==> Parameters: 

5.3.9对$修饰的语句进行sql注入

Dao接口方法:
Student selectStudentUse$(@Param("studentName") String name);

mapper文件:
<!--使用$-->
<select id="selectStudentUse$" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student where name = ${studentName}
</select>
    
测试方法:
@Test
public void testSelectStudentsUse$() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    //使用mapper动态代理获取接口实现类对象
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    //sql注入:【'' or id = 1001】或者【"'';drop table t_student;"】
    Student student = dao.selectStudentUse$("'' or id = 1001");
    System.out.println(student);
}

查看日志:

==>  Preparing: select id,name,email,age from t_student where name = '' or id = 1004 
==> Parameters: 
<==    Columns: id, name, email, age
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 1
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

5.3.10使用$替换列名

Dao接口方法:
List<Student> selectUse$Order(@Param("colName") String colName);
mapper文件:
<select id="selectUse$Order" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student order by ${colName}
</select>
测试方法:
@Test
public void testSelectUse$Order() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    //替换列名
    List<Student> studentList = dao.selectUse$Order("name");
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看日志:

==>  Preparing: select id,name,email,age from t_student order by name 
==> Parameters: 
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 4
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

5.4封装MyBatis输出结果

5.4.1resultType

1.resultType是什么

resultType:指定执行sql得到的ResultSet所转换的类型,使用类型的全限定名或别名。

注意:如果返回的是一个集合,那么应该设置resultType为集合里所包含的类型,而不是集合本身。

mapper文件:

<!-- sql执行后的列的数据转换为java对象Student -->
<select id="selectStudents" resultType="com.tsccg.entity.Student">
	select id,name,email,age from t_student
</select>

jdbc处理查询结果集:同样的操作

ResultSet rs = stmt.executeQuery("select * from t_student");
List<Student> studentList = new ArrayList<>();
while(rs.next()) {
    Student student = new Student(stmt.getInt("id"),stmt.getString("name"),stmt.getEmail("email"),stmt.getInt("age"));
    //将从数据库中拿到的数据装进Student对象,封装进List集合
    studentList.add(student);
}
return studentList;//将结果返回
2.返回值是简单类型

接口方法:

public interface StudentDao {
    //统计学生数量,返回类型:简单类型
    int countStudent();
}

mapper文件:

<mapper namespace="com.tsccg.dao.StudentDao">
    <!-- 结果为简单类型 -->
    <!--<select id="countStudent" resultType="java.lang.Integer">--><!--全限定名-->
    <select id="countStudent" resultType="int"><!--别名-->
        select count(*) from t_student;
    </select>
</mapper>

测试类:

public class TestStudentDao {
    @Test
    public void testCountStudent() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        //执行接口方法并接收返回值
        int count = dao.countStudent();
        System.out.println("学生数量:" + count);
    }
}
3.返回值是对象类型

处理方式:

  1. mybatis执行sql语句,然后mybatis调用类的有参/无参构造方法,创建java对象。
  2. mybatis把ResultSet指定列值赋给同名的对象属性。

注意:如果ResultSet指定列值和对象属性名不一致,则无法将数据写入对象中。

接口方法:

public interface StudentDao {
    //查询一条学生信息,返回类型:对象类型
    Student selectOneStudent(Integer id);
}

mapper文件:

<mapper namespace="com.tsccg.dao.StudentDao">
    <!--  -->
    <!--查询一个学生,结果为对象类型-->
    <select id="selectOneStudent" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student where id = #{id};
    </select>
</mapper>

测试类:

public class TestStudentDao {
    @Test
    public void testSelectStudent() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        //使用mapper动态代理获取接口实现类对象
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        Map<Object,Object> map = dao.selectStudent(1003);
        System.out.println(map);
    }
}

日志报告:

==>  Preparing: select id,name,email,age from t_student where id = ?; 
==> Parameters: 1004(Integer)
<==    Columns: id, name, email, age
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 1
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

resultType指明的对象类型是任意的,只要能把查询出的结果装进一个对象就行,不用非得是实体类类型。

4.返回值是Map类型

sql的查询结果作为Map的key和value,推荐使用Map<Object,Object>。

注意:Map作为接口返回值,sql语句的查询结果最多只能有一条记录,大于一条记录会报错。

接口方法:

public interface StudentDao {
    //返回类型:Map
    Map<Object,Object> selectStudent(Integer id);
}

mapper文件:

<!--结果类型为Map-->
<select id="selectStudent" resultType="java.util.HashMap">
    select id,name,email,age from t_student where id = #{id};
</select>

测试方法:

@Test
public void testSelectStudents() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    //使用mapper动态代理获取接口实现类对象
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    Map<Object,Object> map = dao.selectStudent(1003);
    System.out.println(map);
}

日志报告:

==>  Preparing: select id,name,email,age from t_student where id = ?; 
==> Parameters: 1003(Integer)
<==    Columns: id, name, email, age
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 1
{name=张飞, id=1003, email=wangwu@qq.com, age=22}

5.4.2resultMap

结果映射,指定列名和java对象的属性对应关系。

  1. 可以自定义列值赋给一个指定的属性
  2. 当列名和属性名不一样时,一定要使用resultMap,不然无法将查询结果写入对象中

使用resultMap步骤:

  1. 先定义resultMap格式
  2. 在select标签内,使用resultMap属性引用1所定义的格式

接口方法:

public interface StudentDao {
    //resultMap
    List<Student> selectAllStudent();
}

mapper文件:

<!-- 定义resultMap
	id: 自定义名,作为你定义的这个resultMap的标识
	type:使用的java类型的全限定名
-->
<resultMap id="myResultMap" type="com.tsccg.entity.Student">
    <!--列名和java属性的关系-->
    <!--主键列,使用id标签
        column:数据库表列名
        property:java对象属性名
    -->
    <id column="id" property="id"/>
    <!--非主键列,使用result标签
        column:数据库表列名
        property:java对象属性名
    -->
    <result column="name" property="email"/><!--将数据库表中的name赋给java对象的email属性-->
    <result column="email" property="name"/><!--数据库表中的email赋给java对象的name属性-->
    <result column="age" property="age"/>
</resultMap>

<!--使用resultMap-->
<select id="selectAllStudent" resultMap="myResultMap">
    select id,name,email,age from t_student
</select>
<!--定义的resultMap可以复用-->
<select id="selectOneStudent" resultMap="myResultMap">
    select id,name,email,age from t_student where id = #{id};
</select>

测试方法:

@Test
public void testSelectAllStudent() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    //使用mapper动态代理获取接口实现类对象
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    List<Student> studentList = dao.selectAllStudent();
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看日志:

==>  Preparing: select id,name,email,age from t_student 
==> Parameters: 
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 4
Student{id=1001, name='zhangsan@qq.com', email='张三', age=20}
Student{id=1002, name='lisi@qq.com', email='李四', age=21}
Student{id=1003, name='wangwu@qq.com', email='张飞', age=22}
Student{id=1004, name='liubei@qq.com', email='赵六', age=22}

从日志可见,数据库表中的name被赋给了java对象的email属性,数据库表中的email被赋给了java对象的name属性。

resultMap常用来处理表中列名和java对象属性名不一致时的情况。

比如说现在有一个实体类:StudentPlus,其属性名与列名不一致:

public class StudentPlus {
    //定义属性名,和t_student表的列名不一致
    private Integer studentId;
    private String studentName;
    private String studentEmail;
    private Integer studentAge;
    ...
}

现使用该类作为返回结果。

接口方法:

public interface StudentDao {
    List<StudentPlus> selectAllStudent2();
}

mapper文件:

<!-- 定义resultMap,指定列名赋给对象中哪一个属性 -->
<resultMap id="myResultMap" type="com.tsccg.entity.StudentPlus">
    <!--列名和java属性的关系-->
    <id column="id" property="studentId"/>
    <!--非主键列,使用result标签-->
    <result column="name" property="studentName"/>
    <result column="email" property="studentEmail"/>
    <result column="age" property="studentAge"/>
</resultMap>
<!--使用resultMap-->
<select id="selectAllStudent2" resultMap="myResultMap">
    select id,name,email,age from t_student
</select>

测试方法:

@Test
public void testSelectAllStudent2() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    List<StudentPlus> studentList = dao.selectAllStudent2();
    for (StudentPlus student : studentList) {
        System.out.println(student);
    }
}

查看日志:

==>  Preparing: select id,name,email,age from t_student 
==> Parameters: 
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 4
StudentPlus{studentId=1001, studentName='张三', studentEmail='zhangsan@qq.com', studentAge=20}
StudentPlus{studentId=1002, studentName='李四', studentEmail='lisi@qq.com', studentAge=21}
StudentPlus{studentId=1003, studentName='张飞', studentEmail='wangwu@qq.com', studentAge=22}
StudentPlus{studentId=1004, studentName='赵六', studentEmail='liubei@qq.com', studentAge=22}

当然,解决数据库表列名和java对象属性名不一致的方法还有很多。

比如:在mapper文件中执行sql语句时,添加别名:

select id as studentId,name as studentName from t_student;

5.5模糊查询like

有两种方式实现:

1.将“%”作为查询数据的一部分,从java代码传到mapper文件中。

2.在mapper文件中的sql语句里提前写好“%”

5.5.1第一种方式:%作为参数

接口方法:

public interface StudentDao {
    //模糊查询1:java代码传参--->%张%
    List<Student> selectLikeOne(String name);
}

mapper文件:

<!--第一种方式实现模糊查询:java代码传参%-->
<select id="selectLikeOne" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student where name like #{name}
</select>

测试方法:

//第一种
@Test
public void testSelectLikeOne() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    String name = "%张%";
    List<Student> studentList = dao.selectLikeOne(name);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看执行日志:

==>  Preparing: select id,name,email,age from t_student where name like ? 
==> Parameters: %张%(String)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

5.5.2第二种方式:提前写好%

接口方法:

public interface StudentDao {
    //模糊查询2:提前写好%
    List<Student> selectLikeTwo(String name);
}

mapper文件:

<!--第二种方式实现模糊查询:提前写好%-->
<select id="selectLikeTwo" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student where name like "%" #{name} "%"
</select>

测试方法:

//第二种like
@Test
public void testSelectLikeTwo() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    String name = "张";
    List<Student> studentList = dao.selectLikeTwo(name);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看执行日志:

==>  Preparing: select id,name,email,age from t_student where name like "%" ? "%" 
==> Parameters: 张(String)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

6.动态sql

6.1动态sql的概念

动态sql就是通过Mybatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。

常用的动态SQL标签有:<if >、<where >、<choose/ >、<foreach >等。

6.2动态sql适用的情况

动态sql主要用于解决查询条件不确定的情况。

在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,需要执行的sql语句也不同。

如果将每种可能的情况都列出来,对所有条件进行排列组合,会出现大量的sql语句。

此时,就可以使用动态sql来解决这样的问题。

6.3动态sql之<if >

<if >是判断条件的。对于该标签的执行,当test的值为true时,会将其包含的sql片段拼接到其所在的SQL语句中。

语法:

<select id="selectIf" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student 
    where 1 = 1<!--添加(1=1)是为了避免拼接sql语句时出现语法错误-->
    <if test="判断java对象的属性值">
        sql片段
    </if>
</select>

dao接口方法:

public interface StudentDao {
    /**
     * 动态sql--if
     * 参数必须为java对象
     */
    List<Student> selectIf(Student student);
}

mapper文件:

<select id="selectIf" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student
    where 1 = 1
    <if test="name != null and name != ''">
        and name like #{name}
    </if>
    <if test="age > 0">
        <!--在 mapper的动态SQL中若出现大于号(>)、小于号(<)等,
            最好将其转换为实体符号。否则,XML可能会出现解析出错问题。
            (&lt;)对应(<)
       		(&gt;)对应(>)
       		(&gt;=)对应(>=)
       		(&lt;=)对应(<=)
         -->
        and age &gt; #{age}
    </if>
</select>

测试方法:

@Test
public void testSelectIf() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    Student student = new Student();
    student.setName("%张%");
    student.setAge(20);
    List<Student> studentList =  dao.selectIf(student);
    for(Student student1 : studentList) {
        System.out.println(student1);
    }
}

执行日志:

==>  Preparing: select id,name,email,age from t_student where 1 = 1 and name like ? and age > ? 
==> Parameters: %张%(String), 20(Integer)
<==    Columns: id, name, email, age
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 1
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

去掉测试方法中的student.setAge(20);再次执行:

==>  Preparing: select id,name,email,age from t_student where 1 = 1 and name like ? 
==> Parameters: %张%(String)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 2
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

6.4动态sql之<where >

<where >标签用来包含多个<if >标签。当有任意if标签成立时,<where >会自动增加一个关键字,并去掉if中多余的and、or等。

接口方法:

public interface StudentDao {
    /**
     * 动态sql--where
     * @param student 参数必须为java对象
     * @return 返回一个List集合
     */
    List<Student> selectWhere(Student student);
}

mapper文件:

<select id="selectWhere" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student
    <where>
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age > 0">
            or age &gt; #{age}
        </if>
    </where>
</select>

测试方法:

@Test
public void testSelectWhere() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    Student student = new Student();
    student.setName("%张%");
    student.setAge(20);
    List<Student> studentList =  dao.selectWhere(student);
    for(Student student1 : studentList) {
        System.out.println(student1);
    }
}

当name和age都符合if标签条件时,查看执行日志:

==>  Preparing: select id,name,email,age from t_student WHERE name = ? or age > ? //自动添加WHERE
==> Parameters: %张%(String), 20(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 3
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

当只有age符合if标签条件时,查看执行日志:

==>  Preparing: select id,name,email,age from t_student WHERE age > ? //age前的or被删除了
==> Parameters: 20(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 3
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

当所有的if标签条件都为false时,查看执行日志:

==>  Preparing: select id,name,email,age from t_student //没有添加where
==> Parameters: 
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==        Row: 1004, 赵六, liubei@qq.com, 22
<==      Total: 4
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
Student{id=1004, name='赵六', email='liubei@qq.com', age=22}

6.5动态sql之<foreach >

6.5.1作用?格式?

当参数为数组或集合时,可以用<foreach >标签进行遍历。

语法:

<foreach collection="集合类型" item="集合中的成员" open="开始的字符" close="结束的字符" separator="集合成员间的分隔符">
    #{集合中的成员}
</foreach>

collection:表示接口中的方法参数的类型,如list、array等

item:自定义的,表示数组和集合成员的变量

open、close:表示循环开始时和结束时需要拼入的字符,如"(" 和 ")"

separator:集合成员间的分隔符,如:","

相当于下面的java命令:

List<Integer> idList = new ArrayList<>();
idList.add(1001);
idList.add(1002);
idList.add(1003);
String sql = "select id,name,email,age from t_student where id in";
//把idList集合对象中的数据拼接为in格式字符串-->(1001,1002,1003)
StringBuilder strB = new StringBuilder();
strB.append("(");//开始时需要拼接的字符
for(Integer item:idList) {
    //拼接数据和逗号
    strB.append(item).append(",");
}
strB.deleteCharAt(strB.length()-1);//删除最后一个逗号
strB.append(")");//结束时需要拼接的字符
sql += strB;
//select id,name,email,age from t_student where id in(1001,1002,1003)
System.out.println(sql);

6.5.2遍历List<简单类型>

集合的内置类型为简单类型如:List<Integer >

需求:查询id为1001,1002,1003的学生

接口方法:

public interface StudentDao {
    List<Student> selectForeachOne(List<Integer> idList);
}

mapper文件:

<!--动态sql foreach List<Integer> -->
<select id="selectForeachOne" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student
    <if test="list != null and list.size > 0"><!--list.size表示集合长度-->
        where id in
        <foreach collection="list" open="(" close=")" item="myId" separator=",">
            #{myId}
        </foreach>
    </if>
</select>

测试方法:

@Test
public void testSelectForeach() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    List<Integer> idList = new ArrayList<>();
    idList.add(1001);
    idList.add(1002);
    idList.add(1003);
    List<Student> studentList = dao.selectForeachOne(idList);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看执行日志:

==>  Preparing: select id,name,email,age from t_student where id in ( ? , ? , ? ) 
==> Parameters: 1001(Integer), 1002(Integer), 1003(Integer)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 3
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

6.5.3遍历List<java对象>

集合的内置类型为简单类型如:List<Student >

需求:查询id为1001,1002,1003的学生

接口方法:

public interface StudentDao {
    List<Student> selectForeachTwo(List<Student> stuList);
}

mapper文件:

<!--动态sql foreach 2.List<Student> -->
<select id="selectForeachTwo" resultType="com.tsccg.entity.Student">
    select id,name,email,age from t_student
    <if test="list != null and list.size > 0"><!--list.size表示集合长度-->
        where id in
        <foreach collection="list" open="(" close=")" item="stu" separator=",">
            #{stu.id}<!--调用对象属性-->
        </foreach>
    </if>
</select>

测试方法:

@Test
public void testSelectForeachTwo() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao dao = sqlSession.getMapper(StudentDao.class);
    List<Student> stuList = new ArrayList<>();
    Student s1 = new Student();
    s1.setId(1001);
    stuList.add(s1);

    s1 = new Student();//指定新对象地址
    s1.setId(1002);
    stuList.add(s1);

    s1 = new Student();
    s1.setId(1003);
    stuList.add(s1);
    List<Student> studentList = dao.selectForeachTwo(stuList);
    for (Student student : studentList) {
        System.out.println(student);
    }
}

查看执行日志:

==>  Preparing: select id,name,email,age from t_student where id in ( ? , ? , ? ) 
==> Parameters: 1001(Integer), 1002(Integer), 1003(Integer)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==        Row: 1002, 李四, lisi@qq.com, 21
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 3
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}

6.6动态sql之代码片段

<sql />标签用于定义sql片段,以便其他标签复用。

格式:

1.定义sql片段

<sql id="自定义名称">
	select id,name,email,age from t_student
</sql>

2.使用sql片段

<select id="selectStudentById" resultType="com.tsccg.entity.Student">
	<include refid="id的值"/> where id=#{id}
</select>

演示:

dao接口方法:

Student selectStudentById(Integer id);

mapper文件:

<!--定义sql代码片段-->
<sql id="selectAllStudent">
    select id,name,email,age from t_student
</sql>
<select id="selectStudentById" resultType="com.tsccg.entity.Student">
    <!--使用sql代码片段-->
    <include refid="selectAllStudent"/> where id = #{id}
</select>

测试方法:

@Test
public void testSelectStudentById() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
    Student student = mapper.selectStudentById(1002);
    System.out.println(student);
}

查看执行日志:

==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}

7.MyBatis缓存机制

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地进行配置和定制。缓存可以极大地提升查询效率。

Mybatis中默认定义了两级缓存

  1. 默认情况下,只有一级缓存(SqlSession级别的缓存,也被称为本地缓存)开启。
  2. 二级缓存(namespace级别的缓存,也被称为全局缓存)需要手动开启和配置。
  3. 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

7.1一级缓存

一级缓存(本地缓存):SqlSession级别的缓存

​ 1.在与数据库同一次会话期间,查询到的数据会放到本地缓存中,如果之后需要获取相同的数据,会直接从缓存中拿,没必要再去查询数据库。

​ 2.一级缓存是一直开启的

​ 3.一级缓存其实就是SqlSession级别的一个Map。在本次会话期间,查询到的所有结果都会放进这个Map里。

7.1.1演示一级缓存

dao接口方法:

public interface StudentDao {
    Student selectStudentById(Integer id);
}

mapper文件:

<mapper namespace="com.tsccg.dao.StudentDao">
    <select id="selectStudentById" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student where id = #{id}
    </select>
</mapper>

测试方法:

/**
 * 演示一级缓存
 * SqlSession相同,查询条件相同
 */
@Test
public void testSelectStudentById1() {
    //使用工具类获取SqlSession对象
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
    //第一次查询
    Student student1 = mapper.selectStudentById(1002);
    System.out.println(student1);
    //第二次查询:使用同一个SqlSession创建的代理对象查询同一条记录
    Student student2 = mapper.selectStudentById(1002);
    System.out.println(student2);
    //比较两次查询得到的结果对象是否一致
    System.out.println(student1 == student2);//true
    sqlSession.close();
}

查看执行日志:

//开启会话
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
true
//关闭会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.

从日志可见,两次查询只执行了一条sql语句,而且两次查询得到的结果对象是同一个

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

一级缓存失效有四种情况:

  1. SqlSession对象不同
  2. SqlSession对象相同,查询条件不同(当前一级缓存中还没有这条数据)
  3. SqlSession对象相同,查询条件相同,在两次查询期间,有增删改操作(可能会对当前数据有影响)
  4. 在两次查询期间,手动清除了缓存
失效情况1:SqlSession对象不同

测试方法:

/**
 * 失效情况1:查询条件相同,SqlSession对象不同
 */
@Test
public void testSelectStudentById2() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();//创建第一个SqlSession对象
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
    Student student1 = mapper.selectStudentById(1002);//查询1002
    System.out.println(student1);
	
    SqlSession sqlSession2 = MyBatisUtil.getSqlSession();//创建第二个SqlSession对象
    StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
    Student student2 = mapper2.selectStudentById(1002);//同样查询1002
    System.out.println(student2);
    System.out.println(student1 == student2);//false
    
    sqlSession.close();
    sqlSession2.close();
}

查看执行日志:

//开启会话1
Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//开启会话2
Opening JDBC Connection
Created connection 1159114532.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
false
//关闭所有会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4516af24]
Returned connection 1159114532 to pool.

由日志可见,两次查询执行了两条sql语句,得到的结果对象也不一样。

失效情况2:查询条件不同

测试方法:

/**
 * 失效情况2:SqlSession对象相同,查询条件不同
 */
@Test
public void testSelectStudentById3() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
    Student student1 = mapper.selectStudentById(1002);
    System.out.println(student1);

    Student student2 = mapper.selectStudentById(1003);//改变查询条件
    System.out.println(student2);
    System.out.println(student1 == student2);
    sqlSession.close();
}

查看执行日志:

Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]

==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}

==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1003(Integer)
<==    Columns: id, name, email, age
<==        Row: 1003, 张飞, wangwu@qq.com, 22
<==      Total: 1
Student{id=1003, name='张飞', email='wangwu@qq.com', age=22}
false

Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
失效情况3:期间有增删改操作

在dao接口中,添加修改方法

public interface StudentDao {
    /**
     * 查询操作
     */
    Student selectStudentById(Integer id);
    /**
     * 修改数据
     */
    int updateStudent(@Param("id") Integer id,@Param("age") Integer age);
}

mapper文件:

<mapper namespace="com.tsccg.dao.StudentDao">
    <select id="selectStudentById" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student where id = #{id}
    </select>
    <update id="updateStudent">
        update t_student set age = #{age} where id = #{id}
    </update>
</mapper>

测试方法:

/**
 * 失效情况3:SqlSession对象相同,查询条件同相同.但在两次查询中,有增删改操作
 */
@Test
public void testSelectStudentById4() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);
	//第一次查询
    Student student1 = mapper.selectStudentById(1002);
    System.out.println(student1);
	//执行修改操作
    int result = mapper.updateStudent(1004,25);
    System.out.println(result > 0 ? "更新成功":"更新失败");
	//第二次查询
    Student student2 = mapper.selectStudentById(1002);
    System.out.println(student2);

    System.out.println(student1 == student2);
    sqlSession.close();
}

查看执行日志:

Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
//第一次查询
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//执行修改操作
==>  Preparing: update t_student set age = ? where id = ? 
==> Parameters: 25(Integer), 1004(Integer)
<==    Updates: 1
更新成功
//第二次查询
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}

false

Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.
失效情况4:期间手动清除缓存

测试方法:

/**
 * 失效情况4:SqlSession对象相同,查询条件同相同;期间,手动清除缓存
 */
@Test
public void testSelectStudentById5() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentDao mapper = sqlSession.getMapper(StudentDao.class);

    Student student1 = mapper.selectStudentById(1002);
    System.out.println(student1);

    sqlSession.clearCache();//手动清除缓存

    Student student2 = mapper.selectStudentById(1002);
    System.out.println(student2);

    System.out.println(student1 == student2);
    sqlSession.close();
}

查看执行日志:

Opening JDBC Connection
Created connection 1134612201.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
//第一次查询
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}
//第二次查询
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1002(Integer)
<==    Columns: id, name, email, age
<==        Row: 1002, 李四, lisi@qq.com, 21
<==      Total: 1
Student{id=1002, name='李四', email='lisi@qq.com', age=21}

false//结果对象不同

Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@43a0cee9]
Returned connection 1134612201 to pool.

7.2二级缓存

二级缓存(全局缓存):基于namespace级别的缓存

  1. 一个namespace对应一个二级缓存

  2. 工作机制

    1. 在一个会话中,查询一条数据,这个数据就会被放在当前会话的一级缓存中
    2. 如果会话关闭或者提交,一级缓存中的数据会被保存到二级缓存中
    3. 当新的会话查询同样的数据时,就可以直接拿二级缓存中存储的数据
  3. 不同的namespace查出的数据会放在自己对应的缓存(Map)中

7.2.1使用二级缓存步骤

1.开启全局二级缓存配置

二级缓存默认是开启的,在官方文档中就有写出:

但是我们还是要显式地指定每一个我们需要更改的配置的值,防止版本更新带来的问题。

在主配置文件中添加如下语句:

<settings>
    <!--cacheEnable:全局性地开启和关闭所有mapper文件中已配置地任何缓存。默认为true-->
	<setting name="cacheEnabled" value="true"/>
</settings>
2.在mapper文件中配置使用二级缓存

添加<cache />标签即可,也可以根据参数自定义配置。

<?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.tsccg.dao.StudentDao">
    <!-- 配置二级缓存 -->
    <!--
		eviction:缓存的回收策略:
			LRU(默认)-最近最少使用的:移除最长时间不被使用的对象
			FIFO-先进先出:按对象进入缓存的顺序来移除它们
			SOFT-软引用:移除基于垃圾收集器状态和弱引用规则的对象
			WEAK-弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
		flushInterval:缓存刷新间隔。指定多长时间清空一次缓存,按毫秒计。默认不刷新。
		readOnly:是否只读
			true:只读。mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
				  1.因此,mybatsi为了加快获取数据速度,直接将数据在缓存中的引用交给用户。
				  2.这种方式虽然速度快,但是不安全。
			false:非只读(默认)。mybatis认为获取的数据可能会被修改。
				  1.mybatis为了数据安全,会利用序列化和反序列化技术克隆一份新的数据给你。
				  2.这种方式虽然速度慢,但是安全。
		size:指定缓存中能放多少个元素
		type:指定自定义缓存的全类名。自定义缓存只需要实现Cache接口即可。
	-->
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024">
        
    </cache>
    
    
    <select id="selectStudentById" resultType="com.tsccg.entity.Student">
        select id,name,email,age from t_student where id = #{id}
    </select>
    <update id="updateStudent">
        update t_student set age = #{age} where id = #{id}
    </update>
</mapper>
3.在POJO(实体类)里实现序列化接口
public class Student implements Serializable {
    //指定序列化版本号
    private static final long serialVersionUID = 1L;
    //定义属性名,要求和t_student表的列名一致
    private Integer id;
    private String name;
    private String email;
    private Integer age;

    public Student() {
    }
    ...
}
4.演示二级缓存

编写测试方法:

  1. 分别创建两个SqlSession对象,同时使用两个SqlSession对象分别生成StudentDao接口的代理对象。
  2. 使用第一个代理对象查询学号为1001的学生信息,得到一个Student对象,然后关闭第一个SqlSession对象,也就是关闭第一个会话。
  3. 然后使用第二个代理对象再次查询学号为1001的学生信息,同样得到一个Student对象。
  4. 查看执行日志。
/**
 * 演示二级缓存
 */
@Test
public void testSecondLevelCache1() {
    //创建第一个SqlSession对象
    SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
    StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
    //创建第二个SqlSession对象
    SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
    StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
    //执行第一次查询
    Student student1 = mapper1.selectStudentById(1001);
    System.out.println(student1);
    sqlSession1.close();//关闭第一个会话
    //执行第二次查询
    Student student2 = mapper2.selectStudentById(1001);//查询相同记录
    System.out.println(student2);

    System.out.println(student1 == student2);
    sqlSession2.close();//关闭第二个会话
}

执行日志:

//Cache Hit Ratio:缓存命中率。0.0表示在一级缓存和二级缓存中都没有找到对应数据
Cache Hit Ratio [com.tsccg.dao.StudentDao]: 0.0
//开启会话
Opening JDBC Connection
Created connection 1282811396.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
//第一次查询
==>  Preparing: select id,name,email,age from t_student where id = ? 
==> Parameters: 1001(Integer)
<==    Columns: id, name, email, age
<==        Row: 1001, 张三, zhangsan@qq.com, 20
<==      Total: 1
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}
//关闭会话
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4c762604]
Returned connection 1282811396 to pool.
//第二次查询是从二级缓存中拿到的数据,并没有发送新的sql
//Cache Hit Ratio:缓存命中率。0.5表示第二次查缓存有一次命中,拿到了Student对象
Cache Hit Ratio [com.tsccg.dao.StudentDao]: 0.5
Student{id=1001, name='张三', email='zhangsan@qq.com', age=20}

false

注意:

  1. 查到的数据默认先放到一级缓存中,只有当会话提交或关闭时,才会把一级缓存中的数据放到二级缓存中。
  2. 一个mapper对应一个二级缓存。只有在mapper文件里写了cache标签,这个mapper才会有二级缓存。

7.2.2和缓存有关的设置

1.主配置文件里的:<setting name="cacheEnabled" value="true/false" />

​ true:开启全局缓存

​ false:关闭缓存(只关二级缓存,一级缓存一直可用)

2.每个select标签都有useCache="true/false"属性

​ 如果为false,则表明不使用缓存(一级缓存仍使用,二级缓存不使用)

3.增删查改标签中的属性flushCache="true/false"

​ true:增删查改执行完后清空缓存(一二级缓存全部清空)

​ false:不会清空缓存

4.sqlSession.clearCache();

​ 只清除当前会话的一级缓存

5.localCacheScope:本地缓存作用域

​ setting中的name属性

​ MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。

​ 默认值为 SESSION,会缓存一个会话中执行的所有查询。

​ 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存(禁用一级缓存)。

8.Mybatis常用配置

8.1配置数据库属性配置文件

为了便于修改、保存、处理多个数据库的信息,可以把数据库连接信息从mybatis主配置文件中单独放到一个配置文件中。

实现步骤:

1.在main目录下的resources目录里创建一个属性配置文件jdbc.properties

在jdbc.properties文件中编写连接数据库信息,格式为key=value

#key值使用三级名称,可以做多级目录
jdbc.mysql.driver=com.mysql.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/db_mybatis?ullNamePatternMatchesAll=true&amp;serverTimezone=GMT%2b8?useUnicode=true&useSSL=false&characterEncoding=utf8
jdbc.mysql.username=root
jdbc.mysql.password=123456

2.在mybatis的主配置文件中读取配置文件信息

  1. 在configuration标签的第一行,使用properties标签的resource属性指定数据库属性配置文件的位置。
  2. 然后在下方dataSource的property标签内,使用${key}的方式读取指定属性值
<configuration>
    <!--指定jdbc配置文件-->
    <properties resource="jdbc.properties">

    </properties>
    <environments default="mydev">
        <environment id="mydev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.mysql.driver}"/>
                <property name="url" value="${jdbc.mysql.url}"/>
                <property name="username" value="${jdbc.mysql.username}"/>
                <property name="password" value="${jdbc.mysql.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <!-- 指定sql mapper(sql映射文件)的位置-->
    <mappers>
        <mapper resource="com/tsccg/dao/StudentDao.xml"/>
    </mappers>
</configuration>

8.2typeAliases(自定义类型别名)

MyBatis支持默认别名,也支持自定义别名。

别名主要使用在<select resultType="类型别名" >

实现步骤:

1.在主配置文件里定义别名

有两种方式定义,二选一。

<typeAliases>
    <!-- 1.定义单个类型的别名 type:全限定名 alias:自定义别名 -->
    <typeAlias type="com.tsccg.entity.Student" alias="myStudent"/>
    <!-- 2.批量定义别名,扫描整个包下的类。
		name:包名;别名为类名
 	-->
    <package name="com.tsccg.entity"/>
</typeAliases>

2.在mapper文件里使用别名表示类型

<!--使用单个定义的别名-->
<select id="selectStudentById" resultType="myStudent">
    select id,name,email,age from t_student where id = #{id}
</select>
---------------------------------------------------------------
<!--使用批量定义的别名-->
<select id="selectStudentById" resultType="Student">
    select id,name,email,age from t_student where id = #{id}
</select>

8.3批量加载mapper文件

在主配置文件中,有两种方式加载mapper文件,二选一:

<!-- 指定mapper文件(sql映射文件)的位置-->
    <mappers>
        <!--1.逐个加载mapper文件 -->
        <mapper resource="com/tsccg/dao/StudentDao.xml"/>
        <mapper resource="com/tsccg/dao/StudentPlusDao.xml"/>
        <!--
        2.批量加载com.tsccg.dao目录下的所有mapper文件
        使用package的要求:
            1.mapper文件要和dao接口名一致
            2.mapper文件要和dao接口位于同一目录下
        -->
        <package name="com.tsccg.dao"/>
    </mappers>
</configuration>
posted @ 2021-09-12 20:04  TSCCG  阅读(28)  评论(0编辑  收藏  举报