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 提供的针对不同数据库特异性语法的支持。
针对大多数应用场景,不需要配置这里列到的配置。