返回介绍

2.2 MyBatis 的基本构成

发布于 2025-04-26 13:08:32 字数 13612 浏览 0 评论 0 收藏

认识往往是从表面现象到内在本质的一个探索过程,所以对于 MyBatis 的掌握,我们从认识 表面现象 - MyBatis 的基本构成开始。我们先了解一下 MyBatis 的核心组件。

  • SqlSessionFactoryBuilder(构造器):它会根据配置信息或者代码来生成 SqlSessionFactory(工厂接口)。

  • SqlSessionFactory:依靠工厂来生成 SqlSession(会话)。

  • SqlSession:是一个既可以发送 SQL 去执行并返回结果,也可以获取 Mapper 的接口。

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

用一张图表达它们之间的关联,如图 2-4 所示。

025_0001

图 2-4 MyBatis 的构成

这里我们不需要马上明白 MyBatis 的组件内容,先了解它们之间的先后顺序、流程和基本功能,后面我们会详细讨论它们的用法。

2.2.1 构建 SqlSessionFactory

每个 MyBatis 的应用都是以 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。但是读者们需要注意 SqlSessionFactory 是一个工厂接口而不是现实类,它的任务是创建 SqlSession。SqlSession 类似于一个 JDBC 的 Connection 对象。MyBatis 提供了两种模式去创建 SqlSessionFactory:一种是 XML 配置的方式,这是笔者推荐的方式;另一种是代码的方式。能够使用配置文件的时候,我们要尽量的使用配置文件,这样一方面可以避免硬编码(hard code),一方面方便日后配置人员的修改,避免重复编译代码。

这里我们的 Configuration 的类全限定名为 org.apache.ibatis.session. Configuration,它在 MyBatis 中将以一个 Configuration 类对象的形式存在,而这个对象将存在于整个 MyBatis 应用的生命期中,以便重复读取和运用。在内存中的数据是计算机系统中读取速度最快的,我们可以解析一次配置的 XML 文件保存到 Configuration 类对象中,方便我们从这个对象中读取配置信息,性能高。单例占用空间小,基本不占用存储空间,而且可以反复使用。Configuration 类对象保存着我们配置在 MyBatis 的信息。在 MyBatis 中提供了两个 SqlSessionFactory 的实现类,DefaultSqlSessionFactory 和 SqlSessionManager。不过 SqlSessionManager 目前还没有使用,MyBatis 中目前使用的是 DefaultSqlSessionFactory。

让我们看看它们的关系图,如图 2-5 所示。

026_0001

图 2-5 两个 SqlSessionFactory 现实类的关系图

2.2.1.1 使用 XML 方式构建

这里我们配置一个简易的 XML,包含获取数据库连接实例的数据源(DataSource)、决定事务范围和控制方式的事务管理器(TransactionManager)和映射器(SQL Mapper)。XML 配置文件的内容后面会详细探讨,这里先给出一个简单的示例,如代码清单 2-1 所示。

代码清单 2-1: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>
        <typeAlias alias="role" type="com.learn.chapter2.po.Role"/>
    </typeAliases>
    <!--定义数据库信息,默认使用 development 数据库构建环境-->
    <environments default="development">
        <environment id="development">
            <!--采用 jdbc 事务管理-->
            <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="learn"/>
            </dataSource>
        </environment>
    </environments>
    <!--定义映射器-->
    <mappers>
        <mapper resource="com/learn/chapter2/mapper/roleMapper.xml"/>
    </mappers>
</configuration>

对上面的配置做一下说明。

  • 这里配置了一个别名 role,它代表 com.learn.chapter2.po.Role,这样我们就可以在 MyBatis 上下文中引用它了。

  • 我们配置了环境内容,它默认使用 id 是 development 的环境配置,包含以下两方面的内容。

(1)采用 JDBC 的事务管理模式。

(2)数据库的连接信息。

  • 配置映射器。

这里引入了一个 XML,它的作用是提供 SQL 和 SQL 对 POJO 的映射规则定义,它包含了映射器里面的信息。MyBatis 将解析这个 XML,来为我们生成映射器。

现在我们用代码实现创建 SqlSessionFactory,如代码清单 2-2 所示。

代码清单 2-2:生成 SqlSessionFactory

   String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = null;
        sqlSessionFactory = new SqlSessionFactoryBuilder().build (inputStream);

这里我们创建了一个 XML 文件输入流,用 SqlSessionFactoryBuilder 读取 XML 的信息来创建 SqlSessionFactory 的对象。

MyBatis 的解析程序会将 mybatis-config.xml 文件配置的信息解析到 Configuration 类对象里面,然后利用 SqlSessionFactoryBuilder 读取这个对象为我们创建 SqlSession Factory。

2.2.1.2 使用代码方式构建

除了使用 XML 配置的方式创建代码外,也可以使用 Java 编码来实现,不过并不推荐这个方式,因为修改环境的时候,我们不得不重新编译代码,这样不利于维护。

不过在本书我们依旧讨论其实现方法。和上面 XML 方式一样我们也要配置别名、数据库环境和映射器。MyBatis 已经为我们提供好了对象的类和方法,我们只要熟悉它们的使用即可。首先,构建 Configuration 类对象。然后,往对象里面注册我们构建 SqlSessionFactory 所需要的信息便可。

让我们看看代码是如何现实的,如代码清单 2-3 所示。

代码清单 2-3:使用代码生成 SqlSessionFactory

//构建数据库连接池
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("learn");
//构建数据库事务方式
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;

让我们说明一下上面的代码做了什么。

  • 初始化了一个数据库连接池。

  • 定义了 JDBC 的数据库事务管理方式。

  • 用数据库连接池和事务管理方式创建了一个数据库运行环境,并命名为 development。

  • 创建了一个 Configuration 类对象,并把数据库运行环境注册给它。

  • 注册一个 role 的别名。

  • 加入一个映射器。

  • 用 SqlSessionFactoryBuilder 通过 Configuration 对象创建 SqlSessionFactory。

显然用代码方式和用 XML 方式只是换个方法实现而已,其本质并无不同。采用代码方式一般是在需要加入自己特性的时候才会用到,例如,数据源配置的信息要求是加密的时候,我们需要把它们转化出来。在大部分的情况下,笔者都不建议你使用这个方式来创建 MyBatis 的 SqlSessionFactory。

2.2.2 创建 SqlSession

SqlSession 是一个接口类,它类似于你们公司前台的美女客服,它扮演着门面的作用,而真正干活的是 Executor 接口,你可以认为它是公司的工程师。假设我是客户找你们公司干活,我只需要告诉前台的美女客服(SqlSession)我要什么信息(参数),要做什么东西,过段时间,她会将结果给我。在这个过程中,作为用户的我所关心的是:

(1)要给美女客服(SqlSession)什么信息(功能和参数)。

(2)美女客服会返回什么结果(Result)。

而我不关心工程师(Executor)是怎么为我工作的,只要前台告诉工程师(Executor),工程师就知道如何为我工作,这个步骤对我而言是个黑箱操作。

在 MyBatis 中 SqlSession 接口的实现类有两个,分别是 DefaultSqlSession 和 SqlSession Manager。这里我们暂时不深入讨论 Executor 接口及其涉及的其他类,只关心 SqlSession 的用法就好。我们构建了 SqlSessionFactory,然后生成 MyBatis 的门面接口 SqlSession。SqlSession 接口类似于一个 JDBC 中的 Connection 接口对象,我们需要保证每次用完正常关闭它,所以正确的做法是把关闭 SqlSession 接口的代码写在 finally 语句中保证每次都会关闭 SqlSession,让连接资源归还给数据库。如果我们不及时关闭资源,数据库的连接资源将很快被耗尽,系统很快因为数据库资源的匮乏而瘫痪。让我们看看实现的伪代码,如代码清单 2-4 所示。

代码清单 2-4:标准 SqlSession 使用方法

//定义 SqlSession
        SqlSession sqlSession = null;
        try {
            //打开 SqlSession 会话
            sqlSession = sqlSessionFactory.openSession();
            //some code ....
            sqlSession.commit();
        }  catch(Exception ex) {
            System.err.println(ex.getMessage());
            sqlSession.rollback();
        }finally {
            //在 finally 语句中确保资源被顺利关闭
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

这样 SqlSession 就被我们创建出来了,在 finally 语句中我们保证了它的合理关闭,让连接资源归还给数据库连接池,以便后续使用。

SqlSession 的用途主要有两种。

(1)获取映射器,让映射器通过命名空间和方法名称找到对应的 SQL,发送给数据库执行后返回结果。

(2)直接通过命名信息去执行 SQL 返回结果,这是 iBatis 版本留下的方式。在 SqlSession 层我们可以通过 update、insert、select、delete 等方法,带上 SQL 的 id 来操作在 XML 中配置好的 SQL,从而完成我们的工作;与此同时它也支持事务,通过 commit、rollback 方法提交或者回滚事务。

关于这两种用途我们会在映射器里面进行讨论,这两种用途的优劣我们也会进行研讨。

2.2.3 映射器

映射器是由 Java 接口和 XML 文件(或注解)共同组成的,它的作用如下。

  • 定义参数类型。

  • 描述缓存。

  • 描述 SQL 语句。

  • 定义查询结果和 POJO 的映射关系。

一个映射器的实现方式有两种,一种是通过 XML 文件方式实现,读者应该记得我们在 mybatis-config.xml 文件中已经描述了一个 XML 文件,它是用来生成 Mapper 的。另外一种就是通过代码方式实现,在 Configuration 里面注册 Mapper 接口(当然里面还需要我们写入 Java 注解)。当然它也是 MyBatis 的核心内容,同时也是最为复杂的。这两种方式都可以实现我们的需求,不过笔者强烈建议使用 XML 文件配置方式,理由如下。

  • Java 注解是受限的,功能较少,而 MyBatis 的 Mapper 内容相当多,而且相当复杂,功能很强大,使用 XML 文件方式可以带来更为灵活的空间,显示出 MyBatis 功能的强大和灵活。

  • 如果你的 SQL 很复杂,条件很多,尤其是存在动态 SQL 的时候,写在 Java 文件里面可读性较差,增加了维护的成本。

所以本书主要介绍 XML 文件方式,而事实上使用注解也是可以完成 SQL 定义的,包括动态 SQL 定义。但是使用 SQL 构造器可读性不佳且工作量巨大、实操性弱,因此本书不介绍这种方式。

2.2.3.1 XML 文件配置方式实现 Mapper

使用 XML 文件配置是 MyBatis 实现 Mapper 的首选方式。它由一个 Java 接口和一个 XML 文件构成。让我们看看它是如何实现的。

第一步,给出 Java 接口,如代码清单 2-5 所示。

代码清单 2-5:RoleMapper.java

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

这里我们定义了一个接口,它有一个方法 getRole,通过角色编号找到角色对象。

第二步,给出一个映射 XML 文件,如代码清单 2-6 所示。

代码清单 2-6:RoleMapper.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.learn.chapter2.mapper.RoleMapper">
    <select id="getRole" parameterType="long" resultType="role">
        select id, role_name as roleName, note from t_role where id = #{id}
    </select>
</mapper>

描述一下上面的 XML 文件做了什么。

  • 这个文件是我们在配置文件 mybatis-config.xml 中配置了的,所以 MyBatis 会读取这个配置文件,生成映射器。

  • 定义了一个命名空间为 com.learn.chapter2.mapper.RoleMapper 的 SQL Mapper,这个命名空间和我们定义的接口的全限定名是一致的。

  • 用一个 select 元素定义了一个查询 SQL,id 为 getRole,和我们接口方法是一致的,而 parameterType 则表示我们传递给这条 SQL 的是一个 java.lang.Long 型参数,而 resultType 则定义我们需要返回的数据类型,这里为 role,而 role 是我们之前注册 com.learn.chapter2.po.Role 的别名。

我们来看看这个 POJO,如代码清单 2-7 所示。

代码清单 2-7:Role.java

package com.learn.chapter2.po;
public class Role {    
    private Long id;
    private String roleName;
    private String note;

    public Long getId() {
        return id;
    }

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

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

这是一个十分简单的 POJO,符合 Java Bean 的规范。下面是 SQL 的代码。

select id, role_name as roleName, note from t_role where role_no = #{id}

#{id}为这条 SQL 的参数。而 SQL 列的别名和 POJO 的属性名称保持一致。那么 MyBatis 就会把这条语句查询的结果自动映射到我们需要的 POJO 属性上,这就是自动映射。我们可以用 SqlSession 来获取这个 Mapper,代码也比较简单,如代码清单 2-8 所示。

代码清单 2-8:用 SqlSession 获取 Mapper

//获取映射器 Mapper
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);//执行方法
System.out.println(role.getRoleName());//打印角色名称

这样就完成了 MyBatis 的一次查询。

2.2.3.2 Java 注解方式实现 Mapper

Java 注解方式实现映射方法不难,只需要在接口中使用 Java 注解,注入 SQL 即可。我们不妨看看这个接口,如代码清单 2-9 所示。

代码清单 2-9:使用注解生成 Mapper 的接口定义

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

这里笔者解释一下,我们使用了 @Select 注解,注入了和 XML 一样的 select 元素,这样 MyBatis 就会读取这条 SQL,并将参数 id 传递进 SQL。同样使用了别名,这样 MyBatis 会为我们自动映射,得到我们需要的 Role 对象。这里看起来比 XML 简单,但是现实中我们遇到的 SQL 远比例子复杂得多。如果多个表的关联、多个查询条件、级联、条件分支等显然这条 SQL 就会复杂得多,所以笔者并不建议读者使用这种方式。比如代码清单 2-10 所示的这条 SQL。

代码清单 2-10:SQL 例子

    select u.id as userid, u.username, u.mobile, u.email, r.id as roleid, r.role_name from 
t_username u left join t_user_role ur on u.id = ur.user_id
left join t_role r on ur.role_id = r.id
where 1=1 
and  u.username like concat('%', #{username}, '%')
and u.mobile like concat('%', #{mobile}, '%')
and u.email like concat('%', #{email}, '%')
and r.role_name like concat('%', #{roleName}, '%')

如果我们只要写这条 SQL 还可以接受,但是如果我们还需要根据上下文判断 where 语句后面的条件语句,那么显然代码会相当的复杂。我们需要判断 username 是否为空,如果为空我们就不以 username 作为条件,这时候 SQL 就不要出现下面的语句。

and  u.username like concat('%', #{username}, '%')

同样的,如果 mobile、email、role_name 都需要这样判断,都写在 Java 文件的注解里面,会十分复杂,造成可读性下降。

当然如果你的系统很简单,使用注释方式也不失为一个好办法,毕竟简易许多。

我们只需要加入这一段代码即可注册这个接口为映射器,如下所示。

configuration. addMapper(RoleMapper2.class);

2.2.3.3 一些疑问

在 MyBatis 中保留着 iBatis,通过 命名空间(namespace)+SQL id 的方式发送 SQL 并返回数据的形式,而不需要去获取映射器,以下面的代码为例。

Role role=sqlSession.selectOne ("com.learn.chapter2.mapper.RoleMapper. getRole", "role_no_1" -- --> 1L;

如果 MyBatis 上下文中只有一个 SQL 的 id 为 getRole,那么我们将代码简写为:

Role role = sqlSession.selectOne ("getRole","role_no_1" -- --> 1L;

注意,当 SQL 的 id 有两个或两个以上 getRole 的时候,第二种省略的办法就会失败。系统异常就会提示你写出 命名空间+SQL id 的全路径模式才可以。其实它们大同小异,都是发送 SQL 并返回需要的结果,而 MyBatis 一样会根据 com.learn.chapter2.mapper.RoleMapper.getRole 找到需要执行的接口和方法,进而找到对应的 SQL,传递参数"role_no_1" -- --> 1L 到 SQL 中,返回数据,完成一次查询。

那么困惑是我们需要 Mapper 吗?答案是肯定的,Mapper 是一个接口,相对而言它可以进一步屏蔽 SqlSession 这个对象,使得它具有更强的业务可读性。因此笔者强烈建议采用映射器方式编写代码,其好处主要有两点。

  • sqlSession.selectOne 是功能性代码,长长的字符串比较晦涩难懂,不包含业务逻辑的含义,不符合面向对象的规范,而对于 roleMapper.getRole 这样的才是符合面向对象规范的编程,也更符合业务的逻辑。

  • 使用 Mapper 方式,IDE 可以检查 Java 语法,避免不必要的错误。

这是 MyBatis 特有的接口编程模式,而 iBatis 只能通过 SqlSession 用 SQL 的 id 过滤 SQL 去执行。

我们使用的仅仅是 Java 接口和一个 XML 文件(或者注解)去实现 Mapper,Java 接口不是实现类,对于 Java 语言不熟悉的读者肯定会十分疑惑,一个没有实现类的接口怎么能够运行呢?其实它需要运用到 Java 语言的动态代理去实现,而实现 Java 语言的动态代理的方式有多种。这里我们还是集中于它的用法,所以可以这样理解:我们会在 MyBatis 上下文中描述这个接口,而 MyBatis 会为这个接口生成代理类对象,代理对象会根据 接口全路径+方法名 去匹配,找到对应的 XML 文件(或注解)去完成它所需要的任务,返回我们需要的结果。

关于 SqlSession 和 Mapper 是 MyBatis 的核心内容和难点,它内部远远没有我们目前看到的那么简单,只是在入门阶段我们暂时不需要讨论它的实现方式,知道它的作用和用法就可以了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。