MyBatis 全解析系列(01)使用 MyBatis 框架原生 API

01-使用 MyBatis 框架原生 API

Mybatis 是一套持久化层框架,灵活易用,比较流行,官网

为应用层提供的核心 API 包括:

  • SqlSession 和 SqlSessionFactory
  • Configuration
  • Mapper 和 Mapped SQL Statement

SqlSession 和 SqlSessionFactory

SqlSession 是 MyBatis 框架对数据库连接的接口封装。

它提供了诸如增删改查、事务提交回滚等操作接口。

例如:

java 复制代码
try (SqlSession session = sqlSessionFactory.openSession()) {  
    // 直接使用 SqlSession 上的查询接口  
    Student s = session.selectOne("org.mybatis.example.StudentMapper.selectByNo", 101);  
    // 使用 Mapper 中的方法(更推荐,类型安全)  
    StudentMapper mapper = session.getMapper(StudentMapper.class);  
    s = mapper.selectByNo(101);  
}  

SqlSessionFactory 接口,顾名思义,是创建 SqlSession 的工厂类。

它只有两类 API openSession 和 getConfiguration。

与 SqlSessionFactory 相关的,还有一个 SqlSessionFactoryBuilder 类,是用来创建并设置 SqlSessionFactory 对象的。

Configuration

Configuration 是 MyBatis 中的核心类,包含所有的配置信息。

Configuration 可以基于 XML 文件创建,即通过 XMLConfigBuilder#parse 接口创建 Configuration 对象。

XML 格式的配置文件类似于:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE configuration  
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  
"https://mybatis.org/dtd/mybatis-3-config.dtd">  
<configuration>  
<environments default="development">  
<environment id="development">  
<transactionManager type="JDBC"/>  
<dataSource type="POOLED">  
<property name="driver" value="${driver}"/>  
<property name="url" value="${url}"/>  
<property name="username" value="${username}"/>  
<property name="password" value="${password}"/>  
</dataSource>  
</environment>  
</environments>  
<mappers>  
<mapper resource="org/mybatis/example/StudentMapper.xml"/>  
<mapper url="org/mybatis/example/TeacherMapper.xml"/>  
<mapper class="org.mybatis.example.ScoreMapper"/>  
</mappers>  
</configuration>  

注:其中的核心元素是 <environment><mapper>

前者声明使用的事务管理器和数据源,后者定义所有的 Mapper 配置。

<environments> 设计用来应用于不同的数据库,例如区别开发、测试、生产环境等;

虽然可以配置多个环境,但是每个 SqlSessionFactory 仅可对应一个 <environment> 元素。

要配置多数据源,需要创建多个 SqlSessionFactory 对象,并通过多个 <environment> 标签,配置多个数据库;

每个 <environment> 中主要定义两部分内容:

  • <transactionManager type=[JDBC|MANAGED]>
  • <dataSource type=[UNPOOLED|POOLED|JNDI]>

事务管理器,分为两类:

  • JDBC,指直接使用 JDBC 中的提交、回滚功能,它依赖于数据库连接来管理事务的范围;
  • MANAGED,不管理连接的提交、回滚,而是交由容器负责事务的全生命周期管理;

Configuration 也可以通过 Java API 创建。

java 复制代码
Environment environment = new Environment("development", new JdbcTransactionFactory(), dataSource);  
Configuration configuration = new Configuration(environment);  

Mapper 和 Mapped SQL Statement

Mapper 是用户定义的查询接口。

Mybatis 中不要求 Mapper 实现任何特定的接口,即任何合法的接口,都可以作为 Mapper。

Mapped SQL Statement 是根据 XML 文件,通常是 **Mapper.xml 文件,并且通过 namespace 与 Mapper 关联起来。

它与 Mapper 的关系是:

  • Mapper 定义了接口,供应用层使用。
  • Mapped SQL Statement 提供了接口的具体实现(SQL语句)。

如下是一个典型的 XML 文件,它定义了 org.mybatis.example.StudentMapper#selectByNo 的实现:

xml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>  
<!DOCTYPE mapper  
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="org.mybatis.example.StudentMapper">  
<select id="selectByNo" resultType="org.mybatis.example.Student">  
select * from students where sno = #{sno}  
</select>  
</mapper>  

除了 XML 文件外,MyBatis 还支持从注解中创建 Mapped SQL Statement。

例如,下面的等价于上面的 XML 文件:

java 复制代码
public interface StudentMapper {  
@Select("select * from students where sno = #{sno}")  
List<Student> selectByNo(@Param("sno") String sno);  
}  

这里进一步说明一下 Mapper 和 MappedStatement 在 MyBatis 框架中的存储。

在 Configuration 中有两个数据结构:

  • MapperRegistry mapperRegistry;,存储了所有已知的 Mapper 类
  • Map<String, MappedStatement> mappedStatements;,存储的是"接口.方法 -> MappedStatement" 的映射关系。

MappedStatement 有两种创建方式,对应上面描述的两种定义 Mapped SQL Statement 的方式:

  • 基于 Mapper 接口上的注解,由 org.apache.ibatis.builder.annotation.MapperAnnotationBuilder 解析并创建。
  • 基于 XML 文件,由 org.apache.ibatis.builder.xml.XMLStatementBuilder 解析并创建 MappedStatement;

向 MapperRegistry 中添加 Mapper 类时,会经过 MapperAnnotationBuilder 解析。

java 复制代码
// config 即 Configuration 对象  
// type 即 Mapper 类型,例如 StudentMapper 接口代表的 Mapper  
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);  
parser.parse();  
  
public void parse() {  
    String resource = type.toString();  
    if (!configuration.isResourceLoaded(resource)) {  
        loadXmlResource(); // (1) 如果有的话,加载对应的 xml 文件  
        configuration.addLoadedResource(resource);  
        assistant.setCurrentNamespace(type.getName());  
        parseCache(); // 解析类上的 @CacheNamespace  
        parseCacheRef(); // 解析类上的 @CacheNamespaceRef  
        for (Method method : type.getMethods()) {  
            if (!canHaveStatement(method)) { // 忽略桥方法、default 方法  
                continue;  
            }  
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()  
                && method.getAnnotation(ResultMap.class) == null) {  
                parseResultMap(method); // 如果是 Select 或 SelectProvider,且类上没有 @ResultMap 方法,所以需要初始化一个 ResultMap  
            }  
            try {  
                parseStatement(method); // 解析生成 Statement  
            } catch (IncompleteElementException e) {  
                configuration.addIncompleteMethod(new MethodResolver(this, method));  
            }  
        }  
    }  
    parsePendingMethods();  
}  

注:(1) 尝试加载同名的 XML 文件,例如 org.mybatis.example.StudentMapper Mapper 类,会尝试加载 org/mybatis/example/StudentMapper.xml 文件。

如果加载到了,由 XMLMapperBuilder 解析;如果没加载到,则跳过。

使用 XML 配置文件时,配置中的 <Mapper> 由 XMLMapperBuilder 来解析处理。

java 复制代码
private void configurationElement(XNode context) {  
    try {  
        String namespace = context.getStringAttribute("namespace");  
        if (namespace == null || namespace.isEmpty()) {  
            throw new BuilderException("Mapper's namespace cannot be empty");  
        }  
        builderAssistant.setCurrentNamespace(namespace);  
        cacheRefElement(context.evalNode("cache-ref"));  
        cacheElement(context.evalNode("cache"));  
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));  
        resultMapElements(context.evalNodes("/mapper/resultMap"));  
        sqlElement(context.evalNodes("/mapper/sql"));  
        buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // (1) 对 XML 文件中的 Mapped SQL statement 进行解析  
    } catch (Exception e) {  
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);  
    }  
}  

注:(1) 解析工作由 XMLStatementBuilder 完成。

Configuration 中的其他常用配置

除了前面介绍的最核心的 API,Configuration 中其他常用的配置包括:

  • typeHandlers,使用场景主要有两个:(1) 根据参数生成 PreparedStatement 时,(2)从 ResultSet 中取值,生成对应的 Java 对象时;
    这个接口,允许用户自定义处理特殊的参数转换规则。
  • plugins,MyBatis 提供的切片机制。允许用户编写代码,对 Executor、ParameterHandler、ResultSetHandler、StatementHandler 四种类型的对象的方法调用时进行切片。
  • databaseIdProvider,MyBatis 提供的针对不同数据库特异性语法的支持。

针对大多数应用场景,不需要配置这里列到的配置。

相关推荐
2402_8575893623 分钟前
Spring Boot新闻推荐系统设计与实现
java·spring boot·后端
繁依Fanyi26 分钟前
旅游心动盲盒:开启个性化旅行新体验
java·服务器·python·算法·eclipse·tomcat·旅游
J老熊31 分钟前
Spring Cloud Netflix Eureka 注册中心讲解和案例示范
java·后端·spring·spring cloud·面试·eureka·系统架构
蜜桃小阿雯33 分钟前
JAVA开源项目 旅游管理系统 计算机毕业设计
java·开发语言·jvm·spring cloud·开源·intellij-idea·旅游
CoderJia程序员甲34 分钟前
重学SpringBoot3-集成Redis(四)之Redisson
java·spring boot·redis·缓存
Benaso34 分钟前
Rust 快速入门(一)
开发语言·后端·rust
sco528235 分钟前
SpringBoot 集成 Ehcache 实现本地缓存
java·spring boot·后端
OLDERHARD1 小时前
Java - LeetCode面试经典150题 - 矩阵 (四)
java·leetcode·面试
原机小子1 小时前
在线教育的未来:SpringBoot技术实现
java·spring boot·后端
吾日三省吾码1 小时前
详解JVM类加载机制
后端