Mybatis3


如果没有了解过Spring5的相关知识也没关系,依然可以去学习该框架,但是这里推荐先学习Spring和Java基础部分,在来学习本框架
Spring5全面详解–20万字教程


1. MyBatis的介绍

(1) MyBatis的历史

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

(2)为什么要使用Mybatis

  • Mybatis是一个半自动化的持久层框架
  • Jdbc
    • Sql夹在Java代码里,耦合度高,导致编码硬伤。
    • 维护不容易,且实际开发需求中sql 有变化。频繁修改的情况多见。
  • Hibernate和JPA
    • 长难复杂的Sql,对于Hibernate而言处理也不容易
    • 内部自动生产的Sql,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难,导致数据库性能下降。
  • 对于开发人员而言,核心Sql还需要自己优化
  • Sql和Java编码分开,功能边界清晰,一个专注业务,一个专注数据。

(3)Mybatis去哪里找

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

(4) Mybatis 中文文档

https://mybatis.org/mybatis-3/zh/getting-started.html

(5) 这里并不想去一个一个导入,我们选择使用Maven

<!-- 导入Mybatis依赖,版本3.4.6-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

<!-- 引入mysql依赖-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

2. Mybatis小案例-Helloworld

(1) 新建数据库创建mybatis数据库,并且创建对应的表,插入数据

-- 创建数据库,mybatis
create database mybatis;

-- 创建对应的表,tbl-employee
create table tbl_employee(
    -- 把ID作为主键,并且让他自增。
	id int(11) PRIMARY KEY AUTO_INCREMENT,
    last_name varchar(255),
    -- 我们使用0, 1来表示,这样只占用一个字符
    gender char(1),
    email varchar(255)
);

-- 插入数据
insert INTO tbl_employee values(1, '张三', '1', '1551881997@qq.com');

(2) 我们来写一个与正常表对应的JavaBean类

这是什么意思呢,一般来说。我们的每一张表,都会对应一个Java类,里面有相对应的字段,Get/Set/toString 方法。我们把这个类创建在model包下。类的属性,必须对应表里相对应的字段名称

package model;

public class Employee {

    private int id;

    private String last_name;

    private String gender;

    private String email;

    public int getId() {
        return id;
    }

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

    public String getLast_name() {
        return last_name;
    }

    public void setLast_name(String last_name) {
        this.last_name = last_name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

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

    @Override
    public String toString() {
        return "Employee{" +
            "id=" + id +
            ", last_name='" + last_name + '\'' +
            ", gender='" + gender + '\'' +
            ", email='" + email + '\'' +
            '}';
    }
}

(3) 创建全局配置文件

在创建好我们的类之后,我们创建一个全局配置文件。用来管理Mybatis。这个名字可以随便去起,这里我们把它规定为mybatis-config.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="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 将我们写好的Sql映射文件,一定要注册到全局配置文件中。-->
    <mappers>
        <mapper resource="employeeMapper.xml" />
    </mappers>

</configuration>

这个文件,通常是固定这样写的,我们看dataSource的属性,它连接数据库通常需要四个属性。

  • driver: 数据库的驱动
  • url: 数据库的链接地址
  • username:见名知意,链接数据库的账号
  • username:链接数据库的密码

mappers:把我们写好的sql映射文件,注册到全局配置文件。

(4) 创建对应的映射文件

这个时候,我们来配置一个我们的sql映射文件,用来从数据库中获取到对应的查询或者其他操作。因为我们这里是获取Employee类,所以我们对应的文件名起成employeeMapper.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.w">
    <!--
        namespace, 名称空间。
        id, 唯一标识,同Spring的Bean标签id一样。
        resultType: 返回值类型。我这里期望他给我返回一个员工对象。
        -->

    <select id="selectEmp" resultType="model.Employee">
        select * from tbl_employee where id= #{id}
    </select>

</mapper>

首先我们必须要有一个名称空间,namespace,这个名称空间你可以随便去起。但是不要和其他的映射文件命名空间重复。select标签,表示了查询。这个是很基础的东西。 里面的id属性,是唯一标识,就同Spring的Bean标签id是一样的。相当于起了一个别名。resultType 是你要返回的一个返回值类型,这里我们希望返回一个员工对象。所以我们这里写类路径下的包里的Employee类。

(5) 书写我们的测试类

现在我们去建立一个测试类。

import model.Employee;
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.jupiter.api.Test;

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

public class MyTest {

    @Test
    public void myMybatisXmlTest() throws IOException {

        /*
        * 根据xml配置文件(全局配置文件), 创建一个SqlSessionFactory对象。
        * */
        String resource = "mybatis-config.xml";

        InputStream resourceAsStream = Resources.getResourceAsStream(resource);

        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

        // 获取SqlSession实例。能直接执行已经映射的SQL语句。
        SqlSession sqlSession = build.openSession();

        /*
        * 第一个参数,传Sql的唯一标识,唯一表示也就是我们mapper.xml里的标签id。
        * 你必须保证id不冲突,所以为了保险起见。我们一般是 命名空间+id
        * com.w.mapper.selectEmp
        * 第二个参数,用于替换我们要占位的内容。传入参数,这里我们查询id为1的员工
        * */
        try {
            Employee e = sqlSession.selectOne("selectEmp", 1);

            System.out.println(e.toString());

        } finally {
            // 不管怎么样都要让他关闭掉sqlSession
            sqlSession.close();
        }

    }

}

首先我们需要把全局配置文件的路径给拿过来,因为我们这是Maven项目,直接在resources目录下建立文件,同等与直接在类路径下,所以我们直接拿到它的文件名即可。对应了

String resource = "mybatis-config.xml";

官方文档里有这样一句话。

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

所以我们第二部,使用一个输入流,也就是Mybatis官方推荐的 Resources 工具类。来获取到SqlSession的工厂类

InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);

既然他是一个工厂类,呢他是干什么的,创建对象的呗,我们怎么从这个工厂类里,拿到我们对应的SqlSession呢。同通过这个方法。

build.openSession();

该方法注释上也有写,获取SqlSession实例。能直接执行已经映射的SQL语句。

/*
        * 第一个参数,传Sql的唯一标识,唯一表示也就是我们mapper.xml里的标签id。
        * 你必须保证id不冲突,所以为了保险起见。我们一般是 命名空间+id
        * com.w.mapper.selectEmp
        * 第二个参数,用于替换我们要占位的内容。传入参数,这里我们查询id为1的员工
        * */
try {
    Employee e = sqlSession.selectOne("selectEmp", 1);

    System.out.println(e.toString());

} finally {
    // 不管怎么样都要让他关闭掉sqlSession
    sqlSession.close();
}

我们通过try , finally 的方法,让他在最后关闭掉我们打开的输入流。这里,可以详见注释的内容。

(6) 最终结果

到此之后,我们查看输出的结果。

Employee{id=1, last_name='张三', gender='1', email='1551881997@qq.com'}

成功的读取出了数据,数据与数据库的一致。

(7) 小扩展

上面其实还有一个输入流要关闭。

resourceAsStream.close();
sqlSession.close();

官方也提供了一种通过接口来写的方法。

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

诚然,这种方式能够正常工作,对使用旧版本 MyBatis 的用户来说也比较熟悉。但现在有了一种更简洁的方式——使用和指定语句的参数和返回值相匹配的接口(比如 BlogMapper.class),现在你的代码不仅更清晰,更加类型安全,还不用担心可能出错的字符串字面值以及强制类型转换。

例如:
try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}

3. 接口式编程

我们新建一个包,在包下创建一个接口,EmployeeMapper 因为我们还是对员工表的一个操作,所以见名知意,起了这个名字。

其实大差不差,我们使用着一种方式会更加方便和规范。

我们的接口书写方式是这样来写。

package dao;

import model.Employee;

public interface EmployeeMapper {

    // 返回一个Employee类型的结果,通过id查询。
    Employee selectById(Integer id);

}

然后我们需要一个映射的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="dao.EmployeeMapper">
    <!-- 这里命名空间与接口绑定,指定为接口的全路径-->

    <!-- 查询, 这里绑定方法名,不光与接口绑定,还与接口的方法绑定。-->
    <select id="selectById" resultType="model.Employee">
        select * from tbl_employee where id = #{id};
    </select>

</mapper>

我们仔细看注释,命名空间我们也不再随便去起名了,我们与接口绑定,名称空间起名为与接口绑定,写上接口路径的全类名,而标签的id值,我们也指定为对应的方法名称。不光与接口进行绑定,也与方法进行绑定。返回值类型依然是期望得到一个员工对象,Employee类即可。

Java的测试类写法

import dao.EmployeeMapper;
import model.Employee;
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.jupiter.api.Test;

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

public class MyTest {

    @Test
    public void myMybatisXmlTest() throws IOException {

        String resource = "mybatis-config.xml";

        InputStream resourceAsStream = Resources.getResourceAsStream(resource);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {

            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

            Employee employee = mapper.selectById(1);

            System.out.println(employee.toString());

        } finally {

            resourceAsStream.close();

            sqlSession.close();

        }

    }

}


与上面大同小异,仔细看看,做对比即可。

4. 小总结

我们在上面,通过两种方式,实现了两种不同的写法,一种是正常的通过xml,一种是通过接口式编程,通过xml的方式是比较老版本的一些,而我们现在是接口式编程,有一定的规范性。可以助于解耦,更安全的类型检查。以前是一个Dao接口,对应一个接口的实现类。我们使用Mybatis之后,就改变了这种格局,我们现有了一个接口,xxxMapper,其实起什么名称都一样,他只是一个名称而已。 这个接口我们并没有写一个实现类,而是有一个与之对应的配置文件。

SqlSession代表和数据库的一次回话,每次执行增删改查,不管是直接调用SqlSession还是通过getMapper的方式来使用,我们都会去SqlSession来操作数据库。

SqlSession是通过SqlSessionFactory.openSession();来获取到的。用完必须关掉。我们需要释放资源。

SqlSession和connection一样,都是非线程安全的。呢我们就不能把它当成一个成员变量来使用。如下

private SqlSession sqlSession;

因为他不是线程安全的,有可能在多线程竞争中,A线程拿来用,然后把他给关掉了,你B线程还拿着关掉的sqlSession来使用。每次使用我们都应该去获取新的对象。不要把它放成成员变量。

在写上面的案例的时候,发现我们的Mapper接口并没有对应的实现类,但是Mybatis会为这个接口生成一个代理对象。在调用sqlSession.getMapper(EmployeeMapper.class);的时候,拿到的是一个Employee对象。

Employee emoloyee = sqlSession.getMapper(EmployeeMapper.class);

在接口与Mybatis绑定后,就会为我们生成这个代理对象。后续会研究源码。

两个重要的配置文件,一个是我们称为Mybatis的核心全局配置文件。mybatis-config.xml,这个配置文件里包含,数据库连接池信息,事务管理器信息等。很多系统的运行环境信息。

第二个是我们的Sql映射文件,这个文件是很重要的,我们的Mybatis甚至可以没有核心配置文件,改为配置类的方式来实现,Sql映射文件一定要有,里面保存了我们的每一个sql语句的映射信息。我们在这个文件中,我们写的每一个sql语句,他的唯一标识是什么,等一些东西,全在这个文件里,而Mybatis整个增删改查,都是按照我们的Sql映射文件里的语句来进行操作的。所以Mybatis就是通过Sql映射文件,将我们关键的部分抽取出来,交给我们来操作。

5. Mybatis全局配置文件讲解

我们在之前讲了两个配置文件,一个是全局配置文件,一个是Sql映射文件。这两个文件该怎么写,都是我们接下来要仔细去研究的对象。

1.引入dtd约束

Mybatis的配置文件包含了影响Mybatis行为甚深的设置(settings) 和属性 (properties)信息。文档的顶层结构如下:

  • configuration配置
    • properties配置
    • settings设置
    • typeAliases类型命名
    • typeHandlers类型处理器
    • objectFactory对象工厂
    • plugins插件
    • environments环境
      • environments 环境
        • transactionManager 事务管理器
        • dataSource数据源
    • databaseldProvider 数据库厂商标识
    • mappers映射器

首先我们来研究,查看全局配置文件里面要怎么写。我们在这里引用一下上面所使用到的

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

<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 将我们写好的Sql映射文件,一定要注册到全局配置文件中。-->
    <mappers>
        <mapper resource="employeeMapper.xml" />
    </mappers>

</configuration>

我们来看XML文件头部的一行代码。

"http://mybatis.org/dtd/mybatis-3-config.dtd"

这一行就是XML的dtd约束文件。而这个约束文件,就是来规定我们XML中语法规则的。这一个文件在哪里,这个文件在Mybatis.jar包下

org.apache.builder.xml.mybatis-3-config.dtd

我们的Sql映射文件,也有一个同样的约束文件。

"http://mybatis.org/dtd/mybatis-3-mapper.dtd"

而他的约束文件也在Mybatis.jar包下,和全局约束文件在同一目录下。

org.apache.builder.xml.mybatis-3-mapper.dtd

2. 核心配置文件的属性讲解

properties
<properties></properties>

Mybatis可以使用properties标签,来引入外部properties配置文件的内容。我们在学Java基础的时候经常使用,特别是在用JDBC的时候,我们把数据源的信息,都配置在这里。

我们的数据源,是在内部写死的,维护起来不方便,而我们想把它抽出去,就可以把它写在一个properties文件里。

Mybtais的properties标签,就是用来引入的。它有两个参数

<properties resource></properties>
<properties url></properties>

这两个参数都是用来引入资源的。

  • resource
    • 引入类路径下的文件资源
  • url
    • 引入网络路径,或者磁盘路径下的资源

现在我们来尝试一下,在我们的Maven项目里,有一个文件夹,叫resources这个就相当于我们的类路径,我们只需要在这里新建对应的文件即可直接通过类路径的方式访问。然后我们把数据源的信息抽取出来,放在配置文件中,我们就不写死了。新建一个文件,data-source.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

我们把数据库的数据源信息以这样的方式抽取出来然后引入。.

我们在核心配置文件里引入这个文件跟我们上面所描述的一样,使用标签进行引入

<properties resource="data-source.properties" />

然后我们使用表达式引入值。${}

<dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</dataSource>

运行我们上面写的小案例,可以运行成功。输出结果为 :

Employee{id=1, last_name='张三', gender='1', email='1551881997@qq.com'}

可以看到,在这里,我们已经把数据进行了抽出。更方便我们去管理和维护。这也是这个标签的作用。

settings

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true | falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true | falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true | falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true | falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION | STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true | falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true | falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB | JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true | falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSql从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)true | falsefalse
defaultSqlProviderTypeSpecifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.A type alias or fully qualified class nameNot set

这一段是从官方文档里拿出来的。 一个配置完整的 settings 元素的示例如下:

<settings>
    <setting name="cacheEnabled" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="multipleResultSetsEnabled" value="true"/>
    <setting name="useColumnLabel" value="true"/>
    <setting name="useGeneratedKeys" value="false"/>
    <setting name="autoMappingBehavior" value="PARTIAL"/>
    <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
    <setting name="defaultExecutorType" value="SIMPLE"/>
    <setting name="defaultStatementTimeout" value="25"/>
    <setting name="defaultFetchSize" value="100"/>
    <setting name="safeRowBoundsEnabled" value="false"/>
    <setting name="mapUnderscoreToCamelCase" value="false"/>
    <setting name="localCacheScope" value="SESSION"/>
    <setting name="jdbcTypeForNull" value="OTHER"/>
    <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

我们如何使用这个标签。跟上方描述的差不多相同

<!--
 在settings里设置,里面有一个标签,setting,有对应的属性,name和value。分别对应设置的属性和属性的值
 不设置则为默认值。
-->
<settings>
	<setting name="xxxx" value="xxxx" />
</settings>

setting用来设置每一个设置项。name就是设置项的名字,value就是设置项的取值。可以对标上面的属性和值去做对应的使用。

typeAliasses

Aliasses翻译过来。就是别名的意思。typeAliasses标签,是我们Mybatis中的别名处理器,它的作用就是把我们Java类型给他起一个简单一点短一点别名,以后我们写的时候,直接引用就可以了,不用 在写呢么长的全类名了。

举个例子,依然拿最开始写的小案例来说,我们看Mapper映射文件。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.EmployeeMapper">
    <!-- 这里命名空间与接口绑定,指定为接口的全路径-->

    <!-- 查询, 这里绑定方法名,不光与接口绑定,还与接口的方法绑定。-->
    <select id="selectById" resultType="model.Employee">
        select * from tbl_employee where id = #{id};
    </select>

</mapper>

在这里, 我们的select标签,resultType返回值类型,引用了全路径,这里只是比较短。如果长一点的全路径,我们每一次去写都会很麻烦。呢么我们就可以用别名处理器,为我们经常来用的返回值类,来起一个别名。

现在我们来到我们的全局配置文件中,使用我们的别名处理器。

<!-- 别名处理器-->
<typeAliases>
    <!--
        为某一个Java类型起别名, type就是指定要起别名的全类名
        如果后面什么都不写。默认别名就是 employee,如果你后面加上alias
        alias后面的值就是你指定的别名名称,这里我们用默认的
        -->
    <typeAlias type="model.Employee" />
</typeAliases>

在注释中我写的非常清楚。他标签的作用,标签属性的用处。以及对应值的作用。然后我们在Mapper映射文件中,我们把返回值类型的全类名,改为别名名称然后测试

<select id="selectById" resultType="employee">
    select * from tbl_employee where id = #{id};
</select>

Employee{id=1, last_name='张三', gender='1', email='1551881997@qq.com'}

我们在来看一个问题,我们现在model包下仅仅有一个类,Employee类。如果我这个包下有二三十个类呢,我们要一一去对这个包里的类去起别名吗。起几十上千条。其他包下的呢。呢我们就有一种新的方式,呢就是批量起别名。这个用到了我们的标签。

<!-- 别名处理器-->
<typeAliases>
   	    <!--
        package 可以为一个包下的所有类,批量起别名。 name属性就是指定包名
        他会为当前指定的包以及下面所有的后代包的每一个类都起一个默认别名。默认别名跟之前一样
        还是跟之前一样的类名小写。
        -->
    <package name="model" />
</typeAliases>

然后我们的Mapper映射文件,返回值类型就是他的默认小写。

<select id="selectById" resultType="employee">
    select * from tbl_employee where id = #{id};
</select>

运行测试类代码得到以下结果,可以看到依然是可以成功运行的。

Employee{id=1, last_name='张三', gender='1', email='1551881997@qq.com'}

尽管如此,我们还是会有一些问题,举个最简单的例子,我们的model包下,还有一个子包,子包里也有一个类,叫做Employee。他们会同样注册到别名里。model包下会创建一个别名,叫做emoloyee,子包下也会创建一个别名,叫做employee,呢他们就出现了一种情况,别名冲突。mybatis就会报错。

@Alias

这个时候,我们就能使用一个注解 @Alias 里面可以设置值,值就是你指定的新别名,假设我们现在进入了model包下的Employee类。然后我们在上面加上一个注解

@Alias("employee111")
public class Employee {
    // .....
}

尽管不推荐像我这样起名字,哈哈哈,但是我是个很厌倦起名字的人。为了方便,我们就这样使用。这个时候,model包下的Employee类的别名就会变成 employee111,而子包内的Employee,在不用管他的情况下,他会默认注册别名,为employee。这样就不会出现别名冲突的问题了!

Java已经占用的别名

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator
特别注意

这里有一个小问题。特别重要,在mybatis的配置文件中,所有的属性必须要按照顺序来匹配 在上面的就不能在下面。严格要求,否则报错

元素类型“配置”的内容必须匹配“(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”。

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • objectWrapperFactory
  • reflectorFactory
  • plugins
  • environments
  • databaseldProvider
  • mappers

必须按照以上严格顺序来对标签进行排序。这是必须的,必要的。

typeHandlers

typeHandlers翻译过来,叫做类型处理器。它的作用就是架起我们Java类型和数据库类型之间一一映射的一个桥梁。

举个例子,在Java代码中,有一个String类型的数据,我想把它转到数据库中对应的varchar,应该怎么写。typeHandlers就做了这样的一个事情。

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERICBYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERICSMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERICINTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERICBIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERICFLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERICDOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERICDECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值)
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERICDOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHARLONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE
plugins

plugins翻译为插件,插件也是mybatis本身很强大的功能。这个插件得等到讲完Mybatis运行原理之后,对整个mybatis有了一定的了解。我们才能学的懂插件的工作流程。

这里先做一个简单的介绍。mybatis插件,可以拦截到sql执行的一些核心步骤。这个拦截就是利用插件机制进行拦截的。

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    • 执行器
  • ParameterHandler (getParameterObject, setParameters)
    • 参数处理器
  • ResultSetHandler (handleResultSets, handleOutputParameters)
    • 结果集处理器
  • StatementHandler (prepare, parameterize, batch, update, query)
    • Sql语句处理器

这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

enviroments

环境配置。他也就是对应上了我们核心配置文件的一些环境的配置

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC" />
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>

mybatis可以配置多种环境,我们看上面的xml文件,可以清楚的看到,在environments里有一个environment,这个environment是可以存在多个的。所以说,每一个environment标签,就对应了一个具体的环境配置。若是我们只单写一个标签,如下,他会报错。

<environment id=""></environment>
<!-- 错误的写法-->
<!--
The content of element type "environment" is incomplete, it must match "(transactionManager,dataSource)". 
-->

<!-- 这个标签里,必须存在 transactionManager 和 dataSource -->

也就是说,我们要在这个标签里,加上transactionManager标签和dataSource标签。如下

<environment id="">
    <transactionManager type=""></transactionManager>
    <dataSource type=""></dataSource>
</environment>

可能我们注意到了,environment 标签里有一个对应的属性,id,可以用来作为唯一标识,假设我们需要用到两种环境,第一种是我们的本地环境,第二种是测试环境,呢么我们就可以配置两套方案来使用,通过id命名,如下。

<environments>
    
    <!-- 测试环境-->
	<environment id="test">
    	<transactionManaget type=""></transactionManaget>
    	<dataSource type=""></dataSource>
    </environment>
    
    <!-- 本地环境-->
    <environment id="localHost">
    	<transactionManaget type=""></transactionManaget>
    	<dataSource type=""></dataSource>
    </environment>
    
</environments>

这样,在实际中,就可以根据对应的环境,去进行对应的测试。我们如何去选择对应的开发环境,这个问题也很简单,我们只需要在所有environment的父级标签environments标签上,添加一个元素属性, default,来指定我们的开发环境。

<!-- 这里我们指定本地环境-->
<environments default="localHost">
    
    <!-- 测试环境-->
	<environment id="test">
    	<transactionManaget type=""></transactionManaget>
    	<dataSource type=""></dataSource>
    </environment>
    
    <!-- 本地环境-->
    <environment id="localHost">
    	<transactionManaget type=""></transactionManaget>
    	<dataSource type=""></dataSource>
    </environment>
    
</environments>

这样我们就可以达到快速切换,不需要过多的配置。

transactionManaget 标签

翻译过来也就是我们所说的事务管理器。

而里面的属性,type就是事务管理器的属性。在我们之前,type属性是写的JDBC,呢我们一共有多少种可以去写呢,在官方给出的方法里,我们共有两种可以去写 [JDBC / MANAGED]

  • JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

  • MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

<transactionManager type="MANAGED">
  <property name="closeConnection" value="false"/>
</transactionManager>

dataSource

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性:

  • driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
  • url – 这是数据库的 JDBC URL 地址。
  • username – 登录数据库的用户名。
  • password – 登录数据库的密码。
  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
  • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息。

作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如:

  • driver.encoding=UTF8

这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 UTF8encoding 属性给数据库驱动。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:

  • poolMaximumActiveConnections – 在任意时间可存在的活动(正在使用)连接数量,默认值:10
  • poolMaximumIdleConnections – 任意时间可能存在的空闲连接数。
  • poolMaximumCheckoutTime – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒)
  • poolTimeToWait – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。
  • poolMaximumLocalBadConnectionTolerance – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 poolMaximumIdleConnectionspoolMaximumLocalBadConnectionTolerance 之和。 默认值:3(新增于 3.4.5)
  • poolPingQuery – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。
  • poolPingEnabled – 是否启用侦测查询。若开启,需要设置 poolPingQuery 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。
  • poolPingConnectionsNotUsedFor – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。

JNDI – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
  • data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如:

  • env.encoding=UTF8

这就会在 InitialContext 实例化时往它的构造方法传递值为 UTF8encoding 属性。

你可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}

为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。 下面是一个可以连接至 PostgreSQL 数据库的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>

呢么,在必要的情况下,最简单的配置应该是以下层级的,这些内容是必要的

  • environments
    • environment
      • transactionManager
      • dataSource
mappers

我们在之前用过,就是把我们写好的sql映射文件,使用mapper标签注册到全局配置中,也是非常重要的 必不可少的。

<mappers>
    <!-- mapper注册一个sql映射 -->
	<mapper resource />
</mappers>

mappers对应的一些属性都有哪一些。

  • resource
    • 引用类路径下的sql映射文件
  • url
    • 引用网络路径下的或者磁盘路径下的sql映射文件。
  • class
    • 直接引用接口,但是要想成功绑定,必须要把映射文件和接口放在同一目录下,而且名称相同。

为了更加明显的体现class属性的作用,我们来使用注解标注接口的方式,实现我们的数据库访问查询。

我们也讲了,直接引用接口,也就是我们首先前提要拥有一个接口,这个接口我们自己来写,我把它放在了dao包下,命名为 EmployeeMapperAnnotation 在里面我们拥有一个方法。返回一个Employee对象,有一个Integar类型的参数

package dao;

import model.Employee;

public interface EmployeeMapperAnnotation {

    // 返回一个Employee类型的结果,通过id查询。
    Employee selectById(Integer id);

}

然后我们要做的是,在该抽象方法上,添加一个注解。@Select。 很明显,查询的意思,我们要添加数据就写@Insert 等等类似。在@Select()里有对应的属性值,也就是我们要查询的查询语句

package dao;

import model.Employee;

public interface EmployeeMapperAnnotation {

    // 返回一个Employee类型的结果,通过id查询。
    @Select("select * from tbl_employee where id = #{integer}")
    Employee selectById(Integer id);

}

然后,我们把这个接口,通过mapper的方式,注册到全局配置文件中。

<mappers>
	<mapper class="dao.EmployeeMapperAnnotation" />
</mappers>

接着去书写我们的测试类,测试类跟上面一样大同小异,基本上是完全一致的写法。

@Test
public void test2() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream(resource);

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    try {

        EmployeeMapperAnnotation mapper = sqlSession.getMapper(EmployeeMapperAnnotation.class);

        Employee employee = mapper.selectById(1);

        System.out.println(employee.toString());

    } finally {

        resourceAsStream.close();

        sqlSession.close();

    }


}

这也就是我们这个class的主要用法,但是特别注意的是,我们每一次需要修改sql语句或者优化sql语句的时候,我们必须要到源代码里去更改对应的注解内容,这样违背了我们的原则,耦合度高,所以我们尽可能的把重要的sql语句写入到xml配置文件中,而那些可以简化开发,不是很重要的sql语句,我们就可以使用注解的方式来进行使用。

在mappers中批量注册

这个功能我们用到了 package 标签。

<package name=""></package>

这个name的内容,就是写我们的包名,里面的所有内容会被注册。

6. Mybatis 映射文件

映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义

  • cache
    • 命名空间的二级缓存配置
  • cache-ref
    • 其他命名空间缓存配置的引用
  • resultMap
    • 自定义结果集映射
  • parameterMap
    • 已废弃,老式风格的参数映射
  • sql
    • 抽取可重复使用语句块
  • insert
    • 映射插入语句
  • update
    • 映射更新语句
  • delete
    • 映射删除语句
  • select
    • 映射查询语句

(1) 实现最基本的增删改查功能

首先我们需要完善一下我们的EmployeeMapper接口。让他具备有增删改查的四个功能。

package dao;

import model.Employee;

public interface EmployeeMapper {

    // 返回一个Employee类型的结果,通过id查询。
    Employee selectById(Integer id);

    // 添加一个员工
    void addEmployee(Employee employee);

    // 修改一个员工
    void updateEmployee(Employee employee);

    // 删除一个员工
    void deleteEmployee(Integer id);

}

我们除了查询我们的员工需要返回一个员工对象以外。添加,修改,删除,并不需要什么返回值,如果需要,则可以使用官方默认的返回值,可以直接使用的返回值。 Integer,boolean,Long。都会有对应的默认返回值。

接着,我们就需要去完善编写我们对应的mapper映射文件了。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.EmployeeMapper">
    <!-- 这里命名空间与接口绑定,指定为接口的全路径-->

    <!-- 查询, 这里绑定方法名,不光与接口绑定,还与接口的方法绑定。-->
    <select id="selectById" resultType="model.Employee">
        select * from tbl_employee where id = #{id};
    </select>

    <!-- 添加员工-->
    <insert id="addEmployee" parameterType="model.Employee">
        insert into tbl_employee(last_name, gender, email) values (#{last_name}, #{gender}, #{email})
    </insert>
    
    <!-- 修改一个员工-->
    <update id="updateEmployee">
        update tbl_employee set last_name = #{last_name}, gender = #{gender}, email = #{email}
        where id = #{id}
    </update>

    <!-- 删除一个员工 -->
    <delete id="deleteEmployee">
        delete from tbl_employee where id = #{id}
    </delete>

</mapper>

在这里大家可能注意到,为什么insert属性里有一个parameterType,而update和delete标签里并没有对应的属性呢。因为这个属性是参数类型,也就是我们接口里定义参数的参数类型。比如String类型啊,Integer啊等等。这一属性,可有可无。看自己来写。剩下的则是一些基础的sql语句和sql表达式。

然后我们去书写对应的测试类,在测试类的最后,一定不要忘记关闭流和事务的手动提交。当然,如果把 SqlSession sqlSession = sqlSessionFactory.openSession(); 的值改为 SqlSession sqlSession = sqlSessionFactory.openSession(true); 既可实现自动提交事务。

@Test
public void test3() throws IOException {

    InputStream resourceAsStream = Resources.getResourceAsStream(resource);

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    try {

        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

        // 根据id查询员工信息
        Employee employee = mapper.selectById(1);
        System.out.println(employee.toString());

        // 添加员工信息
        //            mapper.addEmployee(new Employee(null, "李四", "0", "119982098@126.com"));

        // 删除员工信息
        //            mapper.deleteEmployee(4);

        // 更新员工信息
        mapper.updateEmployee(new Employee(1, "王五", "1", "878035748@qq.com"));

        // 提交事务, 只有提交事务后,更新的数据才会有效
        sqlSession.commit();

    } finally {

        sqlSession.close();

        resourceAsStream.close();

    }

}

(2) 在insert标签获取主键自增的主键值

因为我们的主键是一个自增的状态,我们即使是传进去的数字为null,他也会自动在前一个数字上+1,作为当前的id值,我们要做的就是获取到这个id值。只需要在xml文件里添加几个属性即可。

<insert id="insertEmployee" parameterType="model.Employee" useGeneratedKeys="true" keyProperty="id">
    insert into tbl_employee(last_name, gender, email) values(#{last_name}, #{gender}, #{email})
</insert>

parameterType 参数类型。

useGeneratedKeys mysql支持主键自增。mybatis也是利用了statement.getGenreatedKeys();

keyProperty 把获取到的主键,传给我们的id值。

(3)映射文件参数处理

- 单个参数&多个参数&命名

单个参数,mybatis不会做特殊处理

我们就目前看到的,我们如何取出我们的参数, #{name}  就可以取出我们的参数。只有一个参数,这个名字,你随便起,爱写啥就写啥,他会取出呢个唯一的参数。

举个例子。看下方

// 假设在interface里有一个这样的抽象方法, 根据id删除一个信息记录
boolean deleteByIdEmployee(Integer id);

我们按道理,在xml的配置文件参数传递中,我们应该去写 #{id} 对吗,但是注意,有讲过,只要是一个参数,mybatis并不会做特殊处理。也就是说,你这个参数传递的内容,可以随便去写。比如

<delete id="deleteByIdEmployee">
    delete from tbl_employee where id = #{i999waad}
</delete>
<!-- 一样可以-->
- 多个参数

在这里,我们使用查询来实例,我们通过多个信息来查询数据,根据 id 和 name 来查询一个人的所有数据信息。呢么我们在 接口的方法就应该如下

Employee selectByIdAndName(Integer id, String name);

在xml中也需要去书写配置

<select id="selectByIdAndName" resultType="model.Employee">
    select * from tbl_employee where id = #{id} and lase_name = #{name}
</select>

在我们去测试的时候,他会报错

org.apache.ibatis.binding.BindingException:
	Parameter 'id' not found.
        Available paramenters are [1, 0, param1, param2]

呢么,我们执行的方法是

Employee selectByIdAndName(Integer id, String name);
我们取值的方法是
#{id}, #{name}
报错了,说我们可用参数为 10,p1,p2

也就是说,mybatis在我们的多个参数,会做特殊处理。多个参数会被封装成一个map,#{}是从map中获得指定的key值。

遵循一个规则,我们假设有两个参数,呢么我们map对应的key 就是 param1,param2。以此类推,我们的参数有十个,呢么map对应的key就为 param1,param2…param10; 或者我们写0, 1也行,这是参数的索引。

通过param的方式进行参数传递

Employee selectByIdAndName(Integer id, String name);

<select id="selectByIdAndName" resultType="model.Employee">
    select * from tbl_employee where id = #{param1} and last_name = #{param2}
</select>

import dao.EmployeeMapper;
import model.Employee;
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.jupiter.api.Test;

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

public class MyTest {

    String resource = "mybatis-config.xml";

    @Test
    public void test() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream(resource);

        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        try {

            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

            Employee employee = mapper.selectByIdAndName(1, "张三");

            System.out.println(employee.toString());


        } finally {

            sqlSession.commit();

            sqlSession.close();

            resourceAsStream.close();

        }


    }


}


最终结果

Employee{id=1, last_name='张三', gender='1', email='1113334567@126.com'}

- 参数命名

上面也有说,他会把多个参数封装成map集合,我们使用p1,p2的方式来取值, 也就是key来取value,呢么我们可以在封装的时候,就指定map的key值,不使用p1,p2…pn了。

呢么我们只需要用到一个注解。@Param 标注在参数前,如下

// 在接口中,原有的方法
Employee getEmployeeByIdAndName(Integer id, String name);

// 修改后的方法
Employee getEmployeeByIdAndName(@Param("id")Integer id, @Param("name")String name);

然后我们把我们XML文件的配置稍作修改

<select id="selectByIdAndName" resultType="model.Employee">
    select * from tbl_employee where id = #{id} and last_name = #{name}
</select>

在次启动测试类得到结果

Employee{id=1, last_name='张三', gender='1', email='1113334567@126.com'}

可以看到,命名参数已经按照我们指定的名称进行了传入,而这个方法也是可行的。

- POJO

如果多个参数正好的我们业务逻辑的数据模型,我们就可以直接传入pojo。

POJO是Plain Ordinary Java Objects的缩写不错,但是它通指没有使用Entity Beans的普通java对象,可以把POJO作为支持业务逻辑的协助类。

#{属性名}:取出传入的pojo属性值

- Map

如果参数不是业务模型中的数据,没有对应的POJO,为了方便。我们也可以传入map。

#{Key}:就是取出map中对应的值

举例说明,我们现在接口中,把参数类型设为map接口。然后属性为<String, Object>

Employee selectByIdAndNameOnMap(Map<String, Object> map);

然后我们在测试类做,做出更改。新建map接口,并且使用hashmap,最后把他当作参数,传递给方法。

EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);

Map<String, Object> map = new HashMap<>();

map.put("idMap", 1);
map.put("nameMap", "张三");

Employee employee = mapper.selectByIdAndNameOnMap(map);

System.out.println(employee.toString());

然后我们要对我们的xml映射文件进行微调,修改我们的参数,改为map对应的key值。

<select id="selectByIdAndNameOnMap" resultType="model.Employee">
    select * from tbl_employee where id = #{idMap} and last_name = #{nameMap}
</select>
- TO

如果参数不是业务模型中的数据,但是要经常使用的话,推荐编写一个TO(Transfer Object)数据传输对象。而在这个对象里面,比如我们查询分页的时候,经常要传入开始索引以及每页的大小。

Page {
	int index;
	int size;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐