MyBatis-执行流程
MyBatis-执行流程
学习核心
- MyBatis框架核心
- 执行流程
学习资料
MyBatis框架核心
MyBatis执行流程
MyBatis 框架
MyBatis是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。 MyBatis消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索。 MyBatis可以使用简单的XML 或注解用于配置和原始映射,将接口和 Java 的 POJO( Plain Old Java Objects,普通的Java 对象)映射成数据库中的记录。
MyBatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,可以自由灵活的生成满足条件的sql(半自动化的orm框架)
框架流程
【1】读取MyBatis的核心配置文件(例如myabtis-config.xml全局配置文件,配置数据库基础属性和其他映射配置)
【2】加载映射文件(多个mapper.xml,封装操作数据库的SQL语句):通过package扫描或者通过mapper指定
【3】构建会话工厂获取SqlSessionFactory(通过SqlSessionFactoryBuilder对象进行构建)
【4】创建会话对象SqlSession(每个线程拥有自己的SqlSession实例)
【5】Executor执行器(MyBatis核心)负责SQL语句的生成和查询缓存的维护(根据SqlSession传递的参数动态生成需要执行的SQL语句并维护查询缓存)
- SimpleExecutor -- 普通的执行器
- ReuseExecutor -- 执行器会重用预处理语句(PreparedStatements)
- BatchExecutor -- 批处理执行器
【6】MappedStatement(底层封装对象),由StatementHandler对解析的SQL语句进行封装(一个MappedStatement对应sql语句标签)
- 输入参数类型(输入参数映射):parameterType
- 输出参数类型(封装结果集):resultType
<!--一个动态sql标签就是一个`MappedStatement`对象-->
<select id="findUserById" parameterType="int" resultType="com.noob.mybatis.model.User">
select * from user where id=#{id}
</select>
综上,可以对比JDBC的操作流程理解,其核心流程可以总结为:
Configuration =》SqlSessionFactory =》SqlSession =》Executor =》StatementHandler(ParameterHandler、ResultHandler)
- 加载解析配置文件(核心配置文件和映射文件)
- 处理参数、解析SQL
- 执行操作(CRUD)
- 封装操作结果集
工作原理(可以结合两个点进行扩展)
【1】MyBatis启动加载
SqlSessionFactory:完成全局配置文件和映射文件的加载解析操作,然后将相关信息保存在Configuration对象中
SqlSession:通过SqlSessionFactory获取SqlSession会话对象
【2】MyBatis是如何处理请求的?(SQL的执行过程:参数映射、SQL解析、执行和结果处理)
SqlSession中提供了处理请求的方法:例如select、insert、update、save等
通过解析SQL语句,调用相应的Executor执行:如果有配置缓存处理则先走二级缓存再走一级缓存,如果缓存中都没有命中数据则继续走数据库操作,由单个StatementHandler进行处理。
通过StatementHandler处理SQL中的占位符,生成完整的SQL语句
最终执行SQL并通过ResultHander处理结果集的映射
【3】SQL的执行流程
Mapper代理方法方式:寻找SQL语句(根据namespace、方法名定位解析后的SQL语句)、执行SQL语句(执行SQL)
概念详解
可结合普通的mybatis项目、spring整合mybatis、springboot整合mybatis 不同案例,主要掌握其构建核心。此处以普通的mybatis项目案例为参考,解析mybatis执行流程
1.核心配置文件:mybatis-config.xml
可查阅相关的官方doc文档,内部有详细的配置关于mybatis的运行环境、数据源、事务等配置说明
加载核心配置文件mybatis-config.xml(核心配置、mapper映射配置)
<?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>
<!-- 加载映射文件的多种方式 -->
<mappers>
<!--
方式1:通过resource一次加载一个文件
<mapper resource=""/>
-->
<mapper resource="sqlmap/user.xml"/>
<!--
方式2:遵循mapper定义规范
通过mapper接口加载单个配置文件
此种方式加载数据的前提是:映射文件与接口同名且必须在同一个包下
通过指定的包与接口名结合进行加载
<mapper class=""></mapper>
-->
<mapper class="com.noob.mybatis.b_mapper.UserMapper"></mapper>
<!--
方式3:通过package进行批量加载
<package name="指定包名"></package>
从而实现批量加载hiding包下的所有映射文件
-->
<!-- <package name="com.noob.mybatis.b_mapper"></package> -->
</mappers>
</configuration>
针对一些核心配置简单理解,后续项目开发中通过spring、springboot整合mybatis能帮助开发者简化很多繁杂的配置项,此处主要理解其框架核心,然后结合实际场景案例掌握其应用技巧
2.原始DAO操作 VS Mapper代理方法
- 原始DAO操作的CRUD是根据mapper.xml中定义的
namespace.方法名
的形式来执行
public class UserDAOTest {
// 定义SqlSessionFactory
private SqlSessionFactory sqlSessionFactory;
@Before // 在所有测试代码执行之前执行该方法
public void before() throws Exception {
// a.定义mybatis配置文件,并得到配置文件的流
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test() {
// 创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 指定sql语句
User findUser = sqlSession.selectOne("test.findUserById", 25);
System.out.println(findUser);
sqlSession.close();
}
}
// 此处的test.findUserById为对应映射文件的namespace.方法名来进行定位
- Mapper代理方法:通过定义Mapper接口和Mapper.xml 将两者进行绑定
开发步骤:
Mapper代理方法开发:程序员只需要定义mapper接口(相当于dao接口)
程序员需要编写mapper.xml映射文件,且在编写mapper接口(dao接口)的时候需要遵循一些开发规范,mapper可以自动生成mapper接口实现类代理对象
遵循以下定义规范:
a.在mapper.xml中namespace 等于mapper接口地址
b.mapper.java接口中的方法名和mapper.xml中的statement的id必须一致
c.mapper.java接口的方法返回值类型和mapper.xml的resultType的类型一致
d.mapper.java接口的方法的形参和mapper.xml中的parameterType的类型一致
总结:namespace有特殊含义等价于mapper接口地址,且mapper.java接口中的方法名、返回值类型、形参必须与mapper.xml中定义的内容一一对应
主要步骤分析:
a.编写mapper.java文件
b.配置映射文件mapper.xml遵循上述规范
c.通过sqlMapConfig.xml加载映射配置文件
d.编写测试代码进行测试
// 1.构建Mapper接口
public interface Mapper {
// 此处简单定义两个方法进行测试
public void addUser(User user);
public User findUserById(int id);
}
// 2.构建Mapper.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:代表命名空间,此处mapper标识访问地址
-->
<mapper namespace="com.noob.mybatis.b_mapper.UserMapper">
<!-- 定义添加用户相关的信息 -->
<insert id="addUser" parameterType="com.noob.mybatis.model.User">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
select LAST_INSERT_ID()
</selectKey>
insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
<!-- 定义根据id查找用户信息 -->
<select id="findUserById" parameterType="java.lang.Integer" resultType="User">
select * from user where id=#{id}
</select>
</mapper>
// 3.测试
public class UserMapperTest {
// 优化测试,将公共代码提取出来
SqlSessionFactory sqlSessionFactory;
@Before // 在所有测试代码执行之前执行该方法
public void before() throws Exception {
// a.定义mybatis配置文件,并得到配置文件的流
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// b.根据流创建会话工厂,传入mybatis配置信息到会话工厂
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void testAddUser() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 创建要添加的用户信息(通过指定的用户id修改数据)
User user = new User();
user.setUsername("小傻子");
user.setAddress("王者峡谷");
user.setBirthday(new Date());
user.setSex("2");
// 通过UserMapper对象执行操作
userMapper.addUser(user);
// 提交事务、释放连接
sqlSession.commit();
sqlSession.close();
}
@Test
public void testFindUserById() {
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession对象获取UserMapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 根据id查找用户信息
User findUser = userMapper.findUserById(25);
System.out.println("查找到的用户:"+findUser);
// 关闭连接
sqlSession.close();
}
}
3.输入映射和输出映射
映射文件构建的核心是一条SQL语句的封装,要理解XML配置和对应Mapper、SQL的映射。
public interface UserCustomMapper {
// 定义查询方法
public List<UserCustom> findUserByUnion(UserQueryVO userQueryVO)throws Exception;
}
<?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:代表命名空间,此处mapper标识访问地址
-->
<mapper namespace="com.noob.mybatis.c_mapper.UserCustomMapper">
<!-- 定义根据综合条件查找用户信息 -->
<select id="findUserByUnion" parameterType="UserQueryVO" resultType="UserCustom">
select * from user where user.sex=#{userCustom.sex} and user.username like '%${userCustom.username}%'
<!-- 分析:#{userCustom.sex}是指获取UserQueryVO中userCustom属性的sex属性值 -->
</select>
</mapper>
以最基础的查询语句分析,可以看到上述<select>
标签定义:
id
用于唯一标识方法名(和接口中的方法名对照)parameterType
请求参数类型(此处为UserQueryVO,是一个封装的查询对象)resultType
、resultMap
返回参数类型(此处为UserCustom,是一个封装的返回结果对象)
其中针对返回类型处理有多种不同的场景:
- ResultType:可以是自定义的返回实体、也可以是MyBatis支持的简单类型(例如int、string等,参考官方文档进行配置)
- POJO对象或POJO列表:通过
resultType
指定POJO的类型,MyBatis会自动根据指定类型封装返回的实体、列表数据 - ResultMap:为了适配一对一、一对多、多对多的业务场景
4.动态SQL
动态SQL概念核心
MyBatis提供了强大的动态SQL功能,通过相应的标签配置,可以根据不同的条件拼接相应的SQL语句。
例如SQL片段、循环遍历、if条件查询、集合过滤等
SQL片段
<!-- a.定义sql片段 -->
<sql id="query_user_where">
<if test="userCustom!=null">
<if test="userCustom.sex!=null">
and user.sex=#{userCustom.sex}
</if>
<if test="userCustom.username!=null">
and user.username like '%${userCustom.username}%'
</if>
</if>
</sql>
<!-- b.在指定的位置引用sql片段 -->
<!-- 定义根据综合条件查找用户信息(动态sql完成拼接) -->
<select id="findUserByUnion" parameterType="UserQueryVO" resultType="UserCustom">
select * from user
<where>
<include refid="query_user_where"></include>
</where>
</select>
for循环遍历
<!-- a.定义sql片段 -->
<sql id="query_user_foreach">
<if test="ids!=null">
<!--
说明:
foreach表示对collection数据进行迭代
collection:要迭代的集合属性
item:在迭代遍历的过程中生成的对象名称
open:开始遍历id是拼接的字符串
close:遍历结束后拼接的字符串
separator:每次遍历两个对象中间需要拼接的内容
-->
<!-- 方式1:and (id=1 or id=10 or id=16) -->
<!--
<foreach collection="ids" item="user_id" open="and (" close=")" separator="or">
id=#{user_id}
</foreach>
-->
<!-- 方式2:id in (1,10,16) -->
<foreach collection="ids" item="user_id" open="id in (" close=")" separator=",">
#{user_id}
</foreach>
</if>
</sql>
<!-- b.通过指定的sql片段遍历用户id数据 -->
<select id="findUserListByIds" parameterType="UserQueryVO" resultType="UserCustom">
select * from user
<where>
<include refid="query_user_foreach"></include>
</where>
</select>