Mybatis框架入门教程

在这里插入图片描述

MyBatis是什么?它和hibernate的区别有哪些?

MyBatis 的前身是 Apache 的开源项目 iBatis。MyBatis 几乎可以代替 JDBC,是一个支持普通 SQL 查询,存储过程和高级映射的基于 Java 的优秀持久层框架。

MyBatis 与 JDBC 相比,减少了 50% 以上的代码量。并且满足高并发和高响应的要求,所以它成为最流行的 Java 互联网持久框架。
在移动互联网时代,MyBatis 成为了目前互联网 Java 持久框架的首选。

MyBatis 本是 Apache 的一个开源项目——iBatis,2010 年这个项目由 Apache Software Foundation 迁移到了 Google Code,并且改名为 MyBatis。

MyBatis 是一个基于 Java 的持久层框架。MyBatis 提供的持久层框架包括 SQL Maps 和 Data Access Objects(DAO),它消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。

MyBatis 使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

目前,Java 的持久层框架产品有许多,常见的有 Hibernate 和 MyBatis。
Hibernate 和 MyBatis 的区别
1)sql 优化方面

  • Hibernate 不需要编写大量的 SQL,就可以完全映射,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作。但会多消耗性能。
  • MyBatis 手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。工作量相对大些。

2)开发方面

  • MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系。
  • Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可。

3)Hibernate 优势

  • Hibernate 的 DAO 层开发比 MyBatis 简单,Mybatis 需要维护 SQL 和结果映射。
  • Hibernate 对对象的维护和缓存要比 MyBatis 好,对增删改查的对象的维护要方便。
  • Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL。
  • Hibernate 有更好的二级缓存机制,可以使用第三方缓存。MyBatis 本身提供的缓存机制不佳。

4)Mybatis优势

  • MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段。
  • MyBatis 容易掌握,而 Hibernate 门槛较高。

总的来说,MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。

所以对于性能要求不太苛刻的系统,比如管理系统、ERP 等推荐使用 Hibernate,而对于性能要求高、响应快、灵活的系统则推荐使用 MyBatis。

MyBatis的工作原理

MyBatis 的版本可以通过“https://github.com/mybatis/mybatis-3/releases”网址下载。

MyBatis 的工作原理如图 2 所示。下面对图 2 中的每步流程进行说明。
在这里插入图片描述
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

*MyBatis的核心组件

MyBatis 的核心组件分为 4 个部分。

1)SqlSessionFactoryBuilder(构造器):它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。

2)SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。

3)SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。

4)SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。

用一张图来展示 MyBatis 核心组件之间的关系,如图 1 所示。
在这里插入图片描述
注意,无论是映射器还是 SqlSession 都可以发送 SQL 到数据库执行,下面学习这些组件的用法。

1.SqlSessionFactory及其常见创建方式

使用 MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,而 MyBatis 提供了构造器 SqlSessionFactoryBuilder。

它提供了一个类 org.apache.ibatis.session.Configuration 作为引导,采用的是 Builder 模式。具体的分步则是在 Configuration 类里面完成的,当然会有很多内容,包括你很感兴趣的插件。

在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory。

笔者强烈推荐采用 XML 的形式,因为代码的方式在需要修改的时候会比较麻烦。当配置了 XML 或者提供代码后,MyBatis 会读取配置文件,通过 Configuration 类对象构建整个 MyBatis 的上下文。

注意,SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。

一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,它们之间的关系如图 1 所示。

在这里插入图片描述
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。我们往往会采用单例模式处理它,下面讨论使用配置文件和 Java 代码两种形式去生成 SqlSessionFactory 的方法。

使用 XML 构建 SqlSessionFactory(推荐使用的方式)
首先,在 MyBatis 中的 XML 分为两类,一类是基础配置文件,通常只有一个,主要是配置一些最基本的上下文参数和运行环境;另一类是映射文件,它可以配置映射关系、SQL、参数等信息。

先看一份简易的基础配置文件,我们把它命名为 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>
    <typeAliases><!--别名-->
        <typeAliases alias="user" type="com.mybatis.po.User"/>
    </typeAliases>
    <!-- 数据库环境 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC的事务管理 -->
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <!-- MySQL数据库驱动 -->
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <!-- 连接数据库的URL -->
                <property name="url"
                    value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="1128" />
            </dataSource>
        </environment>
    </environments>
    <!-- 将mapper文件加入到配置文件中 -->
    <mappers>
        <mapper resource="com/mybatis/mapper/UserMapper.xml" />
    </mappers>
</configuration>

我们描述一下 MyBatis 的基础配置文件:

  • 元素定义了一个别名 user,它代表着 com.mybatis.po.User 这个类。这样定义后,在 MyBatis 上下文中就可以使用别名去代替全限定名了。
  • 元素的定义,这里描述的是数据库。它里面的 元素是配置事务管理器,这里采用的是 MyBatis 的 JDBC 管理器方式。
  • 元素配置数据库,其中属性 type=“POOLED” 代表采用 MyBatis 内部提供的连接池方式,最后定义一些关于 JDBC 的属性信息。
  • 元素代表引入的那些映射器,在谈到映射器时会详细讨论它。

有了基础配置文件,就可以用一段很简短的代码来生成 SqlSessionFactory 了,如下所示。

SqlSessionFactory factory = null;
String resource = "mybatis-config.xml";
InputStream is;
try {
    InputStream is = Resources.getResourceAsStream(resource);
    factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
    e.printStackTrace();
}

首先读取 mybatis-config.xml,然后通过 SqlSessionFactoryBuilder 的 Builder 方法去创建 SqlSessionFactory。整个过程比较简单,而里面的步骤还是比较烦琐的,只是 MyBatis 采用了 Builder 模式为开发者隐藏了这些细节。这样一个 SqlSessionFactory 就被创建出来了。

采用 XML 创建的形式,信息在配置文件中,有利于我们日后的维护和修改,避免了重新编译代码,因此笔者推荐这种方式。

使用代码创建 SqlSessionFactory
虽然笔者不推荐使用这种方式,但是我们还是谈谈如何使用它。通过代码来实现与使用 XML 构建 SqlSessionFactory 一样的功能——创建 SqlSessionFactory,代码如下所示。

// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("1128");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;

注意代码中的注释,它和 XML 方式实现的功能是一致的,只是方式不太一样而已。但是代码冗长,如果发生系统修改,那么有可能需要重新编译代码才能继续,所以这不是一个很好的方式。

除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用这样的方式。

2.SqlSession简介

在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。

DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:

  • 获取 Mapper 接口。
  • 发送 SQL 给数据库。
  • 控制数据库事务。

先来掌握它的创建方法,有了 SqlSessionFactory 创建的 SqlSession 就十分简单了,如下所示。

SqlSession sqlSession = SqlSessionFactory.openSession();

注意,SqlSession 只是一个门面接口,它有很多方法,可以直接发送 SQL。它就好像一家软件公司的商务人员,是一个门面,而实际干活的是软件工程师。在 MyBatis 中,真正干活的是 Executor,我们会在底层看到它。

SqlSession 控制数据库事务的方法,如下所示。

//定义 SqlSession
SqlSession sqlSession = null;
try {
    // 打开 SqlSession 会话
    sqlSession = SqlSessionFactory.openSession();
    // some code...
    sqlSession.commit();    // 提交事务
} catch (IOException e) {
    sqlSession.rollback();  // 回滚事务
}finally{
    // 在 finally 语句中确保资源被顺利关闭
    if(sqlSession != null){
        sqlSession.close();
    }
}

这里使用 commit 方法提交事务,或者使用 rollback 方法回滚事务。因为它代表着一个数据库的连接资源,使用后要及时关闭它,如果不关闭,那么数据库的连接资源就会很快被耗费光,整个系统就会陷入瘫痪状态,所以用 finally 语句保证其顺利关闭。

3.MyBatis实现映射器的2种方式:XML文件形式和注解形式

映射器是 MyBatis 中最重要、最复杂的组件,它由一个接口和对应的 XML 文件(或注解)组成。它可以配置以下内容:

  • 描述映射规则。
  • 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存-刷新等信息。
  • 配置缓存。
  • 提供动态 SQL。
    本节阐述两种实现映射器的方式,XML 文件形式和注解形式。不过在此之前,先用以下 SQL 语句创建 role 表。
CREATE TABLE `role` (
    `id` bigint(20) NOT NULL,
    `role_name` varchar(20) DEFAULT NULL,
    `note` varchar(20) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

并且定义一个 POJO,它十分简单,如下所示。

package com.mybatis.po;
public class Role {
    private Long id;
    private String roleName;
    private String note;
/**setter and getter**/
}

映射器的主要作用就是将 SQL 查询到的结果映射为一个 POJO,或者将 POJO 的数据插入到数据库中,并定义一些关于缓存等的重要内容。

注意,开发只是一个接口,而不是一个实现类。初学者可能会产生一个很大的疑问,那就是接口不是不能运行吗?

是的,接口不能直接运行。MyBatis 运用了动态代理技术使得接口能运行起来,入门阶段只要懂得 MyBatis 会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑即可。

用 XML 实现映射器(推荐使用)
用 XML 定义映射器分为两个部分:接口和 XML。先定义一个映射器接口,如下所示。

package com.mybatis.mapper;
import com.mybatis.po.Role;
public interface RoleMapper {
    public Role getRole(Long id);
}

在用 XML 方式创建 SqlSession 的配置文件中有这样一段代码:

它的作用就是引入一个 XML 文件。用 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.mybatis.mapper.RoleMapper">
    <select id="getRole" parameterType="long" resultType="role">
        SELECT id,role_name as roleName,note FROM role WHERE id =#{id}
    </select>
</mapper>

有了这两个文件,就完成了一个映射器的定义。XML 文件还算比较简单,我们稍微讲解一下:

元素中的属性 namespace 所对应的是一个接口的全限定名,于是 MyBatis 上下文就可以通过它找到对应的接口。

元素表明这是一条查询语句,而属性 id 标识了这条 SQL,属性 parameterType=“long” 说明传递给 SQL 的是一个 long 型的参数,而 resultType=“role” 表示返回的是一个 role 类型的返回值。而 role 是之前配置文件 mybatis-config.xml 配置的别名,指代的是 com.mybatis.po.Role。

这条 SQL 中的 #{id} 表示传递进去的参数。

注意,我们并没有配置 SQL 执行后和 role 的对应关系,它是如何映射的呢?

其实这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。

这里 SQL 返回的列名 id 和 note 是可以和之前定义的 POJO 的属性对应起来的,而表里的列 role_name 通过 SQL 别名的改写,使其成为 roleName,也是和 POJO 对应起来的,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。

注解实现映射器
除 XML 方式定义映射器外,还可以采用注解方式定义映射器,它只需要一个接口就可以通过 MyBatis 的注解来注入 SQL,如下所示。

package com.mybatis.mapper;
import org.apache.ibatis.annotations.Select;
import com.mybatis.po.Role;
public interface RoleMapper2 {
    @Select("select id,role_name as roleName,note from t_role where id=#{id}")
    public Role getRole(Long id);
}

这完全等同于 XML 方式创建映射器。也许你会觉得使用注解的方式比 XML 方式要简单得多。如果它和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,所以 MyBatis 官方推荐使用的是 XML 方式,因此本教程以 XML 方式为主讨论 MyBatis 的应用。

在工作和学习中,SQL 的复杂度远远超过我们现在看到的 SQL,比如下面这条 SQL。

select * from t_user u
left join t_user_role ur on u.id = ur.user_id
left join t_role r on ur.role_id = r.id
left join t_user_info ui on u.id = ui.user_id
left join t_female_health fh on u.id = fh.user_id
left join t_male_health mh on u.id = mh.user_id
where u.user_name like concat('%', ${userName},'%')
and r.role_name like concat('%', ${roleName},'%')
and u.sex = 1
and ui.head_image is not null;

显然这条 SQL 比较复杂,如果放入 @Select 中会明显增加注解的内容。如果把大量的 SQL 放入 Java 代码中,显然代码的可读性也会下降。

如果同时还要考虑使用动态 SQL,比如当参数 userName 为空,则不使用 u.user_name like concat(’%’, u s e r N a m e , {userName},'%')作为查询条件;当 roleName 为空,则不使用 r.role_name like concat('%', {roleName},’%’)作为查询条件,但是还需要加入其他的逻辑,这样就使得这个注解更加复杂了,不利于日后的维护和修改。

此外,XML 可以相互引入,而注解是不可以的,所以在一些比较复杂的场景下,使用 XML 方式会更加灵活和方便。所以大部分的企业都是以 XML 为主,本教程也会保持一致,以 XML 方式来创建映射器。当然在一些简单的表和应用中使用注解方式也会比较简单。

这个接口可以在 XML 中定义,我们仿造在 mybatis-config.xml 中配置 XML 语句:

<mapper resource="com/mybatis/mapper/RoleMapper.xml" />

把它修改为下面的形式即可。

<mapper resource="com/mybatis/mapper/RoleMapper2" />

也可以使用 configuration 对象注册这个接口,比如:

configuration.addMapper(RoleMapper2.class);

4.MyBatis执行SQL的两种方式:SqlSession和Mapper接口

本节主要介绍 MyBatis 执行 SQL 语句的两种方式和它们的区别。
SqlSession 发送 SQL
有了映射器就可以通过 SqlSession 发送 SQL 了。我们以 getRole 这条 SQL 为例看看如何发送 SQL。

Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);

selectOne 方法表示使用查询并且只返回一个对象,而参数则是一个 String 对象和一个 Object 对象。这里是一个 long 参数,long 参数是它的主键。

String 对象是由一个命名空间加上 SQL id 组合而成的,它完全定位了一条 SQL,这样 MyBatis 就会找到对应的 SQL。如果在 MyBatis 中只有一个 id 为 getRole 的 SQL,那么也可以简写为:

Role role = (Role)sqlSession.selectOne("getRole",1L);

这是 MyBatis 前身 iBatis 所留下的方式。

用 Mapper 接口发送 SQL(推荐使用)
SqlSession 还可以获取 Mapper 接口,通过 Mapper 接口发送 SQL,如下所示。

RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);

通过 SqlSession 的 getMapper 方法来获取一个 Mapper 接口,就可以调用它的方法了。因为 XML 文件或者接口注解定义的 SQL 都可以通过“类的全限定名+方法名”查找,所以 MyBatis 会启用对应的 SQL 进行运行,并返回结果。

对比两种发送 SQL 方式
上面分别展示了 MyBatis 存在的两种发送 SQL 的方式,一种用 SqlSession 直接发送,另外一种通过 SqlSession 获取 Mapper 接口再发送。笔者建议采用 SqlSession 获取 Mapper 的方式,理由如下:

使用 Mapper 接口编程可以消除 SqlSession 带来的功能性代码,提高可读性,而 SqlSession 发送 SQL,需要一个 SQL id 去匹配 SQL,比较晦涩难懂。使用 Mapper 接口,类似 roleMapper.getRole(1L)则是完全面向对象的语言,更能体现业务的逻辑。

使用 Mapper.getRole(1L)方式,IDE 会提示错误和校验,而使用 sqlSession.selectOne(“getRole”,1L)语法,只有在运行中才能知道是否会产生错误。

目前使用 Mapper 接口编程已成为主流,尤其在 Spring 中运用 MyBatis 时,Mapper 接口的使用就更为简单,所以本教程使用 Mapper 接口的方式讨论 MyBatis。

SqlSessionFactoryBuilder、SqlSessionFactory和SqlSession的作用域以及生命周期

所谓生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被 Java 虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命周期。

SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)

SqlSessionFactory
SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。

由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域

SqlSession
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。

所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。

所以 SqlSession 的最佳的作用域是请求或方法作用域

Mapper
Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它

以上,我们讨论了 MyBatis 组件的生命周期,如图 1 所示。
在这里插入图片描述

Mybatis框架的简单搭建

Mybatis框架的简单搭建:
https://blog.csdn.net/Bonport/article/details/104968969

MyBatis配置文件详解

MyBatis 配置文件并不复杂,它所有的元素如下所示。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 配置 -->
    <properties /><!-- 属性 -->
    <settings /><!-- 设置 -->
    <typeAliases /><!-- 类型命名 -->
    <typeHandlers /><!-- 类型处理器 -->
    <objectFactory /><!-- 对象工厂 -->
    <plugins /><!-- 插件 -->
    <environments><!-- 配置环境 -->
        <environment><!-- 环境变量 -->
            <transactionManager /><!-- 事务管理器 -->
            <dataSource /><!-- 数据源 -->
        </environment>
    </environments>
    <databaseIdProvider /><!-- 数据库厂商标识 -->
    <mappers /><!-- 映射器 -->
</configuration>

但是需要注意的是,MyBatis 配置项的顺序不能颠倒。如果颠倒了它们的顺序,那么在 MyBatis 启动阶段就会发生异常,导致程序无法运行。

本节的任务是了解 MyBatis 配置项的作用,其中 properties、settings、typeAliases、typeHandler、plugin、environments、mappers 是常用的内容。

先不讨论 plugin(插件)元素的使用,在进一步学习 MyBatis 的许多底层内容和设计后我们才会学习它。MyBatis 中 objectFactory 和 databaseIdProvider 不常用。

properties元素

properties 属性可以给系统配置一些运行参数,可以放在 XML 文件或者 properties 文件中,而不是放在 Java 编码中,这样的好处在于方便参数修改,而不会引起代码的重新编译。一般而言,MyBatis 提供了 3 种方式让我们使用 properties,它们是:

  • property 子元素。
  • properties 文件。
  • 程序代码传递。

property 子元素
以下面代码为基础,使用 property 子元素将数据库连接的相关配置进行改写,如下所示。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties>
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" />
        <property name="username" value="root" />
        <property name="password" value="1128" />
    </properties>
    <typeAliases>
        <typeAlias alias="role" type="com.mybatis.po.Role"/>
    </typeAliases>
    <!--数据库环境-->
    <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?characterEncoding=utf8" />
                <property name="username" value="root" />
                <property name="password" value="1128" />
            </dataSource>
        </environment>
    </environments>
    <!-- 映射文件 -->
    <mappers>
        <mapper resource="com/mybatis/mapper/RoleMapper.xml" />
    </mappers>
</configuration>

这里使用了元素 下的子元素 定义,用字符串 database.username 定义数据库用户名,然后就可以在数据库定义中引入这个已经定义好的属性参数,如 ${database.username},这样定义一次就可以到处引用了。但是如果属性参数有成百上千个,显然使用这样的方式不是一个很好的选择,这个时候可以使用 properties 文件。

使用 properties 文件
使用 properties 文件是比较普遍的方法,一方面这个文件十分简单,其逻辑就是键值对应,我们可以配置多个键值放在一个 properties 文件中,也可以把多个键值放到多个 properties 文件中,这些都是允许的,它方便日后维护和修改。

我们创建一个文件 jdbc.properties 放到 classpath 的路径下,如下所示。

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

在 MyBatis 中通过 的属性 resource 来引入 properties 文件。

<properties resource="jdbc.properties"/>

也可以按 ${database.username} 的方法引入 properties 文件的属性参数到 MyBatis 配置文件中。这个时候通过维护 properties 文件就可以维护我们的配置内容了。

使用程序传递方式传递参数
在真实的生产环境中,数据库的用户密码是对开发人员和其他人员保密的。运维人员为了保密,一般都需要把用户和密码经过加密成为密文后,配置到 properties 文件中。

对于开发人员及其他人员而言,就不知道其真实的用户密码了,数据库也不可能使用已经加密的字符串去连接,此时往往需要通过解密才能得到真实的用户和密码了。

现在假设系统已经为提供了这样的一个 CodeUtils.decode(str)进行解密,那么我们在创建 SqlSessionFactory 前,就需要把用户名和密码解密,然后把解密后的字符串重置到 properties 属性中,如下所示。

String resource = "mybatis-config.xml";
InputStream inputStream;
Inputstream in = Resources.getResourceAsStream("jdbc.properties");
Properties props = new Properties();
props.load(in);
String username = props.getProperty("database.username");
String password = props.getProperty("database.password");
//解密用户和密码,并在属性中重置
props.put("database.username", CodeUtils.decode(username));
props.put ("database.password", CodeUtils.decode(password)); 
inputstream = Resources.getResourceAsStream(resource);
//使用程序传递的方式覆盖原有的properties属性参数
SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputstream, props);

首先使用 Resources 对象读取了一个 jdbc.properties 配置文件,然后获取了它原来配置的用户和密码,进行解密并重置,最后使用 SqlSessionFactoryBuilder 的 build 方法,传递多个 properties 参数来完成。

这将覆盖之前配置的密文,这样就能连接数据库了,同时也满足了运维人员对数据库用户和密码安全的要求。

settings属性配置详解

在 MyBatis 中 settings 是最复杂的配置,它能深刻影响 MyBatis 底层的运行,但是在大部分情况下使用默认值便可以运行,所以在大部分情况下不需要大量配置它,只需要修改一些常用的规则即可,比如自动映射、驼峰命名映射、级联规则、是否启动缓存、执行器(Executor)类型等。
settings属性具体配置项:
http://c.biancheng.net/view/4324.html

settings 的配置项很多,但是真正用到的不会太多,我们把常用的配置项研究清楚就可以了,比如关于缓存的 cacheEnabled,关于级联的 lazyLoadingEnabled 和 aggressiveLazy Loading,关于自动映射的 autoMappingBehavior 和 mapUnderscoreToCamelCase,关于执行器类型的 defaultExecutorType 等。
这里给出一个全量的配置样例,如下所示。

<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>

typeAliases(别名)详解

由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。

在 MyBatis 中别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。注意,在 MyBatis 中别名不区分大小写。

在 MyBatis 中别名由类 TypeAliasRegistry(org.apache.ibatis.type.TypeAliasRegistry)去定义。注意,在 MyBatis 中别名不区分大小写。

自定义别名
由于现实中,特别是大型互联网系统中存在许多对象,比如用户(User)这个对象有时候需要大量重复地使用,因此 MyBatis 也提供了用户自定义别名的规则。我们可以通过 TypeAliasRegistry 类的 registerAlias 方法注册,也可以采用配置文件或者扫描方式来自定义它。

使用配置文件定义很简单:

<typeAliases><!--别名-->
    <typeAlias alias="role" type="com.mybatis.po.Role"/>
    <typeAlias alias="role" type="com.mybatis.po.User"/>
</typeAliases>

这样就可以定义一个别名了。如果有很多类需要定义别名,那么用这样的方式进行配置可就不那么轻松了。MyBatis 还支持扫描别名。比如上面的两个类都在包 com.mybatis.po 之下,那么就可以定义为:

<typeAliases><!--别名-->
    <package name="com.mybatis.po"/>
</typeAliases>

这样 MyBatis 将扫描这个包里面的类,将其第一个字母变为小写作为其别名,比如类 Role 的别名会变为 role,而 User 的别名会变为 user。使用这样的规则,有时候会出现重名。

比如 com.mybatis.po.User 这个类,MyBatis 还增加了对包 com.mybatis.po 的扫描,那么就会出现异常,这个时候可以使用 MyBatis 提供的注解 @Alias(“user3”)进行区分,如下所示。

package com.mybatis.po;
@Alias("user3")
public Class User {
    ......
}

这样就能够避免因为别名重名导致的扫描失败的问题。

系统定义别名
在 MyBatis 的初始化过程中,系统自动初始化了一些别名,如下表所示。
在这里插入图片描述
如果需要使用对应类型的数组型,要看其是否能支持数据,如果支持只需要使用别名加[]即可,比如 _int 数组的别名就是 _int[]。而类似 list 这样不支持数组的别名,则不能那么写。

有时候要通过代码来实现注册别名,让我们看看 MyBatis 是如何初始化这些别名的,如下所示。

public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    ......
    registerAlias("byte[]",Byte[].class); registerAlias("long[]",Long[].class);
    ......
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class); registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);
    registerAlias("ResultSet", ResultSet.class);
}

所以使用 TypeAliasRegistry 的 registerAlias 方法就可以注册别名了。一般是通过 Configuration 获取 TypeAliasRegistry 类对象,其中有一个 getTypeAliasRegistry 方法可以获得别名,如 configuration.getTypeAliasRegistry()。

然后就可以通过 registerAlias 方法对别名注册了。而事实上 Configuration 对象也对一些常用的配置项配置了别名,如下所示。

//事务方式别名
typeAliasRegistry.registerAlias("JDBC",JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED",ManagedTransactionFactory.class);
//数据源类型别名
typeAliasRegistry.registerAlias("JNDI",JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED",
PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED",UnpooledDataSourceFactory.class);
//缓存策略别名
typeAliasRegistry.registerAlias("PERPETUAL",PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO",FifoCache.class);
typeAliasRegistry.registerAlias("LRU",LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
//数据库标识别名
typeAliasRegistry.registerAlias("DB_VENDOR",
VendorDatabaseIdProvider.class);
//语言驱动类别名
typeAliasRegistry.registerAlias("XML",XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW",RawLanguageDriver.class);
//日志类别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGTNG",JakartmCommonsLogginglmpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING",NoLoggingImpl.class);
//动态代理别名
typeAliasRegistry.registerAlias("CGLIB",CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST",JavassistProxyFactory.class);

这些配置为的是让我们更容易配置 MyBatis 的相关信息。以上就是 MyBatis 系统定义的别名,我们在使用的时候,不要重复命名,导致出现其他问题。

TypeHandler类型转换器

在 JDBC 中,需要在 PreparedStatement 对象中设置那些已经预编译过的 SQL 语句的参数。执行 SQL 后,会通过 ResultSet 对象获取得到数据库的数据,而这些 MyBatis 是根据数据的类型通过 typeHandler 来实现的。

在 typeHandler 中,分为 jdbcType 和 javaType,其中 jdbcType 用于定义数据库类型,而 javaType 用于定义 Java 类型,那么 typeHandler 的作用就是承担 jdbcType 和 javaType 之间的相互转换。

如图 1 所示。在很多情况下我们并不需要去配置 typeHandler、jdbcType、javaType,因为 MyBatis 会探测应该使用什么类型的 typeHandler 进行处理,但是有些场景无法探测到。

对于那些需要使用自定义枚举的场景,或者数据库使用特殊数据类型的场景,可以使用自定义的 typeHandler 去处理类型之间的转换问题。
在这里插入图片描述
和别名一样,在 MyBatis 中存在系统定义 typeHandler 和自定义 typeHandler。MyBatis 会根据 javaType 和数据库的 jdbcType 来决定采用哪个 typeHandler 处理这些转换规则。系统提供的 typeHandler 能覆盖大部分场景的要求,但是有些情况下是不够的,比如我们有特殊的转换规则,枚举类就是这样。

讲解这 4 种 typeHandler:
MyBatis系统定义的TypeHandler
MyBatis自定义TypeHandler
MyBatis自定义TypeHandler处理枚举
BlobTypeHandler读取Blob类型字段

MyBatis ObjectFactory(对象工厂)

当创建结果集时,MyBatis 会使用一个对象工厂来完成创建这个结果集实例。在默认的情况下,MyBatis 会使用其定义的对象工厂——DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)来完成对应的工作。

MyBatis 允许注册自定义的 ObjectFactory。如果自定义,则需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory,并给予配置。
在大部分的情况下,我们都不需要自定义返回规则,因为这些比较复杂而且容易出错,在更多的情况下,都会考虑继承系统已经实现好的 DefaultObjectFactory ,通过一定的改写来完成我们所需要的工作。

配置文件environments

在 MyBatis 中,运行环境主要的作用是配置数据库信息,它可以配置多个数据库,一般而言只需要配置其中的一个就可以了。

它下面又分为两个可配置的元素:事务管理器(transactionManager)、数据源(dataSource)。
http://c.biancheng.net/view/4352.html
在实际的工作中,大部分情况下会采用 Spring 对数据源和数据库的事务进行管理。

Mybatis-Spring的简单整合

下面通过一个实例实现 MyBatis 与 Spring 的整合,具体实现过程如下:
https://blog.csdn.net/Bonport/article/details/104986335

select标签以及使用Map接口和Java Bean传递多个参数

在 SQL 映射文件中 元素用于映射 SQL 的 select 语句,其示例代码如下:

<!--根据uid查询一个用户信息 -->
<select id="selectUserById" parameterType="Integer" resultType="com.mybatis.po.MyUser">
    select * from user where uid = #{uid}
</select>

在上述示例代码中,id 的值是唯一标识符,它接收一个 Integer 类型的参数,返回一个 MyUser 类型的对象,结果集自动映射到 MyUser 属性。

元素除了有上述示例代码中的几个属性以外,还有一些常用的属性,如表 所示。
在这里插入图片描述
使用 Map 接口传递多个参数
在实际开发中,查询 SQL 语句经常需要多个参数,例如多条件查询。当传递多个参数时, 元素的 parameterType 属性值的类型是什么呢?在 MyBatis 中允许 Map 接口通过键值对传递多个参数。

假设数据操作接口中有个实现查询陈姓男性用户信息功能的方法:

public List<MyUser> selectAllUser(Map<String,Object> param);

此时,传递给映射器的是一个 Map 对象,使用它在 SQL 文件中设置对应的参数,对应 SQL 文件的代码如下:

<!-- 查询陈姓男性用户信息 -->
<select id="selectAllUser" resultType="com.mybatis.po.MyUser">
    select * from user
    where uname like concat('%',#{u_name},'%')
    and usex = #{u_sex}
</select>

在上述 SQL 文件中,参数名 u_name 和 u_sex 是 Map 的 key。

为了测试该示例,首先创建一个 Web 应用 mybatisDemo02,将 mybatisDemo01 应用的所有 JAR 包复制到 /WEB-INF/lib 下,同时将 mybatisDemo01 应用的 src 目录下的所有包和文件复制到 mybatisDemo02 应用的 src 目录下。

然后将 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中的“查询所有用户信息”的代码片段修改为上述“查询陈姓男性用户信息”的代码片段,最后将 com.controller 包中 UserController 的代码简单修改即可运行测试类了。

com.controller 包中 UserController 的代码片段如下:

@Controller("UserController")
public class UserController {
    private UserDao userDao;
    public void test(){
        ...
        //查询多个用户
        Map<String,Object> map = new HashMap<>();
        map.put("u_name","陈");
        map.put("u_sex","男");
        List<MyUser> list = userDao.seleceAllUser(map);
        for(MyUser myUser : list) {
            System.out.println(myUser);
        }
        ...
    }
}

Map 是一个键值对应的集合,使用者要通过阅读它的键才能了解其作用。另外,使用 Map 不能限定其传递的数据类型,所以业务性不强,可读性较差。如果 SQL 语句很复杂,参数很多,使用 Map 将很不方便。幸运的是,MyBatis 还提供了使用 Java Bean 传递多个参数的形式。

使用 Java Bean 传递多个参数
首先在 myBatisSemo02 应用的 src 目录下创建一个名为 com.pojo 的包,在包中创建一个 POJO 类 SeletUserParam,代码如下:

package com.pojo;
public class SeletUserParam {
    private String u_name;
    private String u_sex;
    // 此处省略setter和getter方法
}

接着将 Dao 接口中的 selectAllUser 方法修改为如下:

public List<MyUser> selectAllUser(SelectUserParam param);

然后将 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中的“查询陈姓男性用户信息”的代码修改为如下:

<select id="selectAllUser" resultType="com.po.MyUser" parameterType="com.pojo.SeletUserParam">
    select * from user
    where uname like concat('%',#{u_name},'%')
    and usex=#{u_sex}
</select>

最后将 com.controller 包中 UserController 的“查询多个用户”的代码片段做如下修改:

SeletUserParam su = new SelectUserParam();
su.setU_name("陈");
su.setU_sex("男");
List<MyUser> list = userDao.selectAllUser(su);
for (MyUser myUser : list) {
    System.out.println(myUser);
}

在实际应用中是选择 Map 还是选择 Java Bean 传递多个参数应根据实际情况而定,如果参数较少,建议选择 Map;如果参数较多,建议选择 Java Bean。

insert、update、delete和sql标签

insert元素
insert元素用于映射插入语句,MyBatis 执行完一条插入语句后将返回一个整数表示其影响的行数。它的属性与select元素的属性大部分相同,在本节讲解它的几个特有属性。

  • keyProperty:该属性的作用是将插入或更新操作时的返回值赋给 PO 类的某个属性,通常会设置为主键对应的属性。如果是联合主键,可以将多个值用逗号隔开。
  • keyColumn:该属性用于设置第几列是主键,当主键列不是表中的第 1 列时需要设置。如果是联合主键,可以将多个值用逗号隔开。
  • useGeneratedKeys:该属性将使 MyBatis 使用 JDBC 的 getGeneratedKeys()方法获取由数据库内部产生的主键,例如 MySQL、SQL Server 等自动递增的字段,其默认值为 false。

1)主键(自动递增)回填
MySQL、SQL Server 等数据库的表格可以采用自动递增的字段作为主键,有时可能需要使用这个刚刚产生的主键,用于关联其他业务。

首先为 com.mybatis 包中的 SQL 映射文件 UserMapper.xml 中 id 为 addUser 的 insert元素添加 keyProperty 和 useGeneratedKeys 属性,具体代码如下:

<!--添加一个用户,成功后将主键值返回填给uid(po的属性)-->
<insert id="addUser" parameterType="com.po.MyUser" keyProperty="uid" useGeneratedKeys="true">
    insert into user (uname,usex) values(#{uname},#{usex})
</insert>

然后在 com.controller 包的 UserController 类中进行调用,具体代码如下:

// 添加一个用户
MyUser addmu = new MyUser();
addmu.setUname("陈恒");
addmu.setUsex("男");
int add = userDao.addUser(addmu);
System.out.println("添加了" + add + "条记录");
System.out.println("添加记录的主键是" + addmu.getUid());

2)自定义主键
如果在实际工程中使用的数据库不支持主键自动递增(例如 Oracle),或者取消了主键自动递增的规则,可以使用 MyBatis 的 selectKey元素来自定义生成主键。具体配置示例代码如下:

<!-- 添加一个用户,#{uname}为 com.mybatis.po.MyUser 的属性值 -->
<insert id="insertUser" parameterType="com.po.MyUser">
    <!-- 先使用selectKey元素定义主键,然后再定义SQL语句 -->
    <selectKey keyProperty="uid" resultType="Integer" order="BEFORE">
    select if(max(uid) is null,1,max(uid)+1) as newUid from user)
    </selectKey>
    insert into user (uid,uname,usex) values(#{uid},#{uname},#{usex})
</insert>

在执行上述示例代码时,selectKey 元素首先被执行,该元素通过自定义的语句设置数据表的主键,然后执行插入语句。

selectKey 元素的 keyProperty 属性指定了新生主键值返回给 PO 类(com.po.MyUser)的哪个属性。

  • order 属性可以设置为 BEFORE 或 AFTER。
  • BEFORE 表示先执行 selectKey 元素然后执行插入语句。
  • AFTER 表示先执行插入语句再执行 selectKey 元素。

update与delete元素
update 和 delete 元素比较简单,它们的属性和 insert元素、select 元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。配置示例代码如下:

<!-- 修改一个用户 -->
<update id="updateUser" parameterType="com.po.MyUser">
    update user set uname = #{uname},usex = #{usex} where uid = #{uid}
</update>
<!-- 删除一个用户 -->
<delete id="deleteUser" parameterType="Integer">
    delete from user where uid = #{uid}
</delete>

sql 元素
sql 元素的作用在于可以定义 SQL 语句的一部分(代码片段),以方便后面的 SQL 语句引用它,例如反复使用的列名。

在 MyBatis 中只需使用 元素编写一次便能在其他元素中引用它。配置示例代码如下:

<sql id="comColumns">id,uname,usex</sql>
<select id="selectUser" resultType="com.po.MyUser">
    select <include refid="comColumns"> from user
</select>

在上述代码中使用 include元素的 refid 属性引用了自定义的代码片段。

resultMap元素的结构及使用

< resultMap > 元素表示结果映射集,是 MyBatis 中最重要也是最强大的元素,主要用来定义映射规则、级联的更新以及定义类型转化器等。
resultMap 元素的结构
resultMap元素包含了一些子元素,结构如下:

<resultMap id="" type="">
    <constructor><!-- 类再实例化时用来注入结果到构造方法 -->
        <idArg/><!-- ID参数,结果为ID -->
        <arg/><!-- 注入到构造方法的一个普通结果 -->  
    </constructor>
    <id/><!-- 用于表示哪个列是主键 -->
    <result/><!-- 注入到字段或JavaBean属性的普通结果 -->
    <association property=""/><!-- 用于一对一关联 -->
    <collection property=""/><!-- 用于一对多、多对多关联 -->
    <discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 -->
        <case value=""/><!-- 基于某些值的结果映射 -->
    </discriminator>
</resultMap>
  • < resultMap> 元素的 type 属性表示需要的 POJO,id 属性是 resultMap 的唯一标识。
  • 子元素 < constructor> 用于配置构造方法(当 POJO 未定义无参数的构造方法时使用)。
  • 子元素 < id> 用于表示哪个列是主键。
  • 子元素 < result> 用于表示POJO和数据表普通列的映射关系。
  • 子元素 < association>、< collection> 和 用在级联的情况下。关于级联的问题比较复杂,后面教程会详细讲解。

一条查询 SQL 语句执行后将返回结果,而结果可以使用 Map 存储,也可以使用 POJO 存储

使用 Map 存储结果集

任何 select 语句都可以使用 Map 存储结果,示例代码如下:

<!-- 查询所有用户信息存到Map中 -->
<select id="selectAllUserMap" resultType="map">
    select * from user
</select>

测试上述 SQL 配置文件的过程如下:

首先在 com.dao.UserDao 接口中添加以下接口方法。

public List<Map<String,Object>> selectAllUserMap();

然后在 com.controller 包的 UserController 类中调用接口方法,具体代码如下。

// 查询所有用户信息存到Map中
List<Map<String, Object>> lmp = userDao.selectAllUserMap();
for (Map<String, Object> map : lmp) {
    System.out.println(map);
}

上述 Map 的 key 是 select 语句查询的字段名(必须完全一样),而 Map 的 value 是查询返回结果中字段对应的值,一条记录映射到一个 Map 对象中。Map 用起来很方便,但可读性稍差,有的开发者不太喜欢使用 Map,更多时候喜欢使用 POJO 的方式。

使用POJO存储结果集

有的开发者喜欢使用 POJO 的方式存储结果集,一方面可以使用自动映射,例如使用 resultType 属性,但有时候需要更为复杂的映射或级联,这时候就需要使用 < select> 元素的 resultMap 属性配置映射集合。具体步骤如下:

1)创建 POJO 类
在 myBatisDemo02 应用的 com.pojo 包中创建 POJO 类 MapUser。MapUser 类的代码如下:

package com.pojo;
public class MapUser {
    private Integer m_uid;
    private String m_uname;
    private String m_usex;
    // 此处省略setter和getter方法
    @Override
    public String toString() {
        return "User[uid=" + m_uid + ",uname=" + m_uname + ",usex=" + m_usex
                + "]";
    }
}

2)配置 < resultMap> 元素
在 SQL 映射文件 UserMapper.xml 中配置 < resultMap> 元素,其属性 type 引用 POJO 类。具体配置如下:

<!--使用自定义结果集类型-->
< resultMap type="com.pojo.MapUser" id="myResult">
    <!-- property 是 com.pojo.MapUser 类中的属性-->
    <!-- column是查询结果的列名,可以来自不同的表-->
    <id property="m_uid" column="uid"/>
    <result property="m_uname" column="uname"/>
    <result property="m_usex" column="usex"/>
</resultMap>

3)配置< select>元素
在 SQL 映射文件 UserMapper.xml 中配置 元素,其属性 resultMap 引用了 元素的 id。具体配置如下:

<!-- 使用自定义结果集类型查询所有用户 -->
<select id="selectResultMap" resultMap="myResult">
    select * from user
</select>

4)添加接口方法
在 com.dao.UserDao 接口中添加以下接口方法:

public List< MapUser> selectResultMap();

5)调用接口方法
在 com.controller 包的 UserController 类中调用接口方法,具体代码如下:

// 使用resultMap映射结果集
List<MapUser> listResultMap = userDao.selectResultMap();
for (MapUser myUser : listResultMap) {
    System.out.println(myUser);
}

MyBatis关联查询(级联查询)

级联关系是一个数据库实体的概念,有 3 种级联关系,分别是一对一级联、一对多级联以及多对多级联。

级联的优点是获取关联数据十分方便,但是级联过多会增加数据库系统的复杂度,同时降低系统的性能。

在实际开发中要根据实际情况判断是否需要使用级联。更新和删除的级联关系很简单,由数据库内在机制即可完成。本节只讲述级联查询的相关实现。

如果表 A 中有一个外键引用了表 B 的主键,A 表就是子表,B 表就是父表。当查询表 A 的数据时,通过表 A 的外键将表 B 的相关记录返回,这就是级联查询。例如,当查询一个人的信息时,同时根据外键(身份证号)将他的身份证信息返回。

一对一关联查询

MyBatis 如何处理一对一级联查询呢?

https://blog.csdn.net/Bonport/article/details/105006633

在 MyBatis 中,通过 元素的子元素 处理这种一对一级联关系。

在 元素中通常使用以下属性。

  • property:指定映射到实体类的对象属性。
  • column:指定表中对应的字段(即查询返回的列名)。
  • javaType:指定映射到实体对象属性的类型。
  • select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。

一对多关联查询

实现mybatis一对多的关联

解决一个Person有多张IdCard

点击查看》》》一对多关联查询《《《

重点:

	在IdCard类中添加 person.id 属性
	在person类中添加List< IdCard> cardList 属性
	在PersonMapper.xml中
	< resultMap>< collection ofType="…IdCArd">
	< /collection>
	</ resultMap>

一对多关联查询

其实,MyBatis 没有实现多对多级联,这是因为多对多级联可以通过两个一对多级联进行替换。

例如,一个订单可以有多种商品,一种商品可以对应多个订单,订单与商品就是多对多的级联关系,使用一个中间表(订单记录表)就可以将多对多级联转换成两个一对多的关系。

如在,product类中添加属性

// 多对多中的一个一对多
    private List<Orders> orders;

在Orders类中添加属性:

// 多对多中的另一个一对多
    private List<Product> products;

MyBatis动态sql

MyBatis 的动态 SQL 元素与 JSTL 或 XML 文本处理器相似,常用 < if>、< choose>、< when>、< otherwise>、< trim>、< where>、< set>、< foreach> 和 等元素。

if标签:条件判断

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分,所以在 MyBatis 中 元素是最常用的元素,它类似于 Java 中的 if 语句。

<!--使用 if 元素根据条件动态查询用户信息-->
<select id="selectUserByIf" resultType="com.po.MyUser" parameterType="com.po.MyUser">
    select * from user where 1=1
    <if test="uname!=null and uname!=''">
        and uname like concat('%',#{uname},'%')
    </if >
    <if test="usex !=null and usex !=''">
        and usex=#{usex}
    </if >
</select>

choose、when、otherwise标签

有些时候不想用到所有的条件语句,而只想从中择取一二,针对这种情况,MyBatis 提供了 < choose> 元素,它有点像 Java 中的 switch 语句。

<!--使用choose、when、otherwise元素根据条件动态查询用户信息-->
<select id="selectUserByChoose" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
    select * from user where 1=1
    <choose>
        <when test="uname!=null and uname!=''">
            and uname like concat('%',#{uname},'%')
        </when>
        <when test="usex!=null and usex!=''">
            and usex=#{usex}
        </when>
        <otherwise>
            and uid > 10
        </otherwise>
    </choose>
</select>

trim、where、set标签

< trim>元素

< trim> 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix。

可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides。正因为 < trim> 元素有这样的功能,所以也可以非常简单地利用 < trim> 来代替 < where> 元素的功能。

<!--使用trim元素根据条件动态查询用户信息-->
<select id="selectUserByTrim" resultType="com.po.MyUser"parameterType="com.po.MyUser">
    select * from user
    <trim prefix="where" prefixOverrides = "and | or">
        <if test="uname!=null and uname!=''">
            and uname like concat('%',#{uname},'%')
        </if>
        <if test="usex!=null and usex!=''">
            and usex=#{usex}
        </if>
    </trim>
</select>

< where> 元素

< where> 元素的作用是会在写入 < where> 元素的地方输出一个 where 语句,另外一个好处是不需要考虑 < where> 元素里面的条件输出是什么样子的,MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。

当然如果是以 or 开头的,MyBatis 也会把它忽略;此外,在 < where> 元素中不需要考虑空格的问题,MyBatis 将智能加上。

<!--使用where元素根据条件动态查询用户信息-->
<select id="selectUserByWhere" resultType="com.po.MyUser" parameterType="com.po.MyUser">
    select * from user
    <where>
        <if test="uname != null and uname ! = ''">
            and uname like concat('%',#{uname},'%')
        </if>
        <if test="usex != null and usex != '' ">
            and usex=#{usex}
        </if >
    </where>
</select>

< set>元素

在动态 update 语句中可以使用 < set> 元素动态更新列。

<!--使用set元素动态修改一个用户-->
<update id="updateUserBySet" parameterType="com.po.MyUser">     
    update user
    <set>
        <if test="uname!=null">uname=#{uname}</if>
        <if test="usex!=null">usex=#{usex}</if>
    </set>
    where uid=#{uid}
</update>

foreach标签

< foreach> 元素主要用在构建 in 条件中,它可以在 SQL 语句中迭代一个集合。

< foreach> 元素的属性主要有 item、index、collection、open、separator、close。

  • item 表示集合中每一个元素进行迭代时的别名。
  • index 指定一个名字,用于表示在迭代过程中每次迭代到的位置。
  • open 表示该语句以什么开始。
  • separator 表示在每次进行迭代之间以什么符号作为分隔符。
  • close 表示以什么结束。

在使用 < foreach> 元素时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:

  • 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
  • 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
  • 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 -key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
<!--使用foreach元素查询用户信息-->
<select id="selectUserByForeach" resultType="com.po.MyUser" parameterType=
"List">
    select * from user where uid in
    <foreach item="item" index="index" collection="list"
    open="(" separator="," close=")">
        # {item}
    </foreach>
</select>

bind标签

在进行模糊查询时,如果使用“${}”拼接字符串,则无法防止 SQL 注入问题。如果使用字符串拼接函数或连接符号,但不同数据库的拼接函数或连接符号不同。

例如 MySQL 的 concat 函数、Oracle 的连接符号“||”,这样 SQL 映射文件就需要根据不同的数据库提供不同的实现,显然比较麻烦,且不利于代码的移植。幸运的是,MyBatis 提供了 元素来解决这一问题。

<!--使用bind元素进行模糊查询-->
<select id="selectUserByBind" resultType="com.po.MyUser" parameterType= "com.po.MyUser">
    <!-- bind 中的 uname 是 com.po.MyUser 的属性名-->
    <bind name="paran_uname" value="'%' + uname + '%'"/>
        select * from user where uname like #{paran_uname}
</select>

参考内容:

从C语言中文网学到了很多,网址http://c.biancheng.net/mybatis/

发布了63 篇原创文章 · 获赞 17 · 访问量 4068

猜你喜欢

转载自blog.csdn.net/Bonport/article/details/104964066