- 第一部分 自定义持久层框架
- 1.1 原生 jdbc 存在问题
- 1.2 自定义持久层框架
- 1.3 具体实现
- 第二部分:Mybatis 相关概念
- 2.1 对象/关系数据库映射(ORM)
- 2.2 Mybatis 简介
- 2.3 Mybatis 历史
- 2.4 Mybatis 优势
- 第三部分:Mybatis 基本应用
- 3.1 快速入门
- 3.1.1 开发步骤:
- 3.1.2 环境搭建:
- 3.2 核心配置文件分析
- 3.2,1 MyBatis 的映射文件概述
- 3.2.2 代理开发方式
- 3.2.3 动态 sql 语句
- 3.2.4 延迟加载
- 第四部分:Mybatis 复杂映射开发
- 1.1 一对一查询
- 1.2 一对多查询
- 1.3 多对多查询
- 第五部分:Mybatis 注解开发
- 1.1 MyBatis 的常用注解
- 1.2 MyBatis 的常用注解实现
- 第六部分:Mybatis 缓存
- 1.1 一级缓存
- 1.2 二级缓存
- 1.3 二级缓存整合 redis
- 第七部分:Mybatis 插件
- 1.1 插件简介
- 1.2 Mybatis 插件介绍
- 1.3 Mybatis 插件原理
- 1.4 自定义插件
- 1.5 pageHelper 分页插件
- 1.6 通用 mapper
- 第八部分:Mybatis 架构原理
- 1.1 架构设计
- 1.2 主要构件及其相互关系
- 1.3 总体流程
- 1.4 重点介绍
- 总结
- 频繁的连接释放数据库
- sql 语句、参数赋值、结果集参数存在硬编码问题
- 手动封装返回结果集
设计思路
- 由数据库配置文件解析获取数据库对象通过连接池获取连接池对象 datasource 再根据 datasource 获取连接 connection,根据 connection 获取 prepareStatement 进而执行 SQL 封装返回结果
- 由 sqlMapper.xml 获取每一个执行的的 SQL 语句、ID、参数、返回值、namespace,根据 namespace.id 作为封装的 mappedStatement 对象的集合 key
- 通过 configuration 对象封装 mappedStatement 的 map 集合,通过数据库信息封装数据源 datasource
- 开始 JDBC 六大步 第一步根据 configuration 封装的 数据源 datasource 获取连接 connection
- 获取 prepareStatement 对象
- 根据 mappedStatement 获取 SQL 语句 参数等
- 处理获取得到的 SQL 语句占位符 #{id} 则把 ID 存放到一个 list 里面之后替换成?
- 遍历 SQL 占位符得到的 list ,根据反射获取参数中该属性的值进行对应赋值
- 执行处理过的 SQL 语句,获取结果集
- 根据 mappedStatement 获取返回值类型反射获取对象,根据 metadata 进行内省(反射)赋予对应列名和属性值赋予对象封装集合
- 进行测试执行,发现代码重复和 statementid 的硬编码问题
- 通过 jdk 动态代理来执行,因为无法得到 statementid 需要 namespace 的值改为 dao 接口名,方法名和 SQL 的 ID 名保持一致
设计步骤
1.3 具体实现sql 修改
- 通过 boundSql 对 sql 进行转化,把参数#{}转变为?
- 把#{}中的参数以对象的形式封装在 List 当中
设置参数
- 通过 mappedStatement 获取参数类型的反射对象 paramtertypeClass
- 遍历 sql 转化的时候存储的参数值的 parameterMappingList
- 获取 declaredField =paramtertypeClass.getDeclaredField(content);
- 获取参数对应值 o = declaredField.get(params[0]);
- 顺序赋值给 preparedStatement.setObject(i + 1, o);
// 4. 设置参数 //获取到了参数的全路径 String paramterType = mappedStatement.getParamterType(); Class> paramtertypeClass = getClassType(paramterType); ListparameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //反射 Field declaredField = paramtertypeClass.getDeclaredField(content); //暴力访问 declaredField.setAccessible(true); Object o = declaredField.get(params[0]); preparedStatement.setObject(i + 1, o); }
封装结果集
- 获取数据库表列名的集合 resultSet.getmetaData();
- 遍历获取每个列名和其对应的值
// 字段名 String columnName = metaData.getColumnName(i); // 字段的值 Object value = resultSet.getObject(columnName);
- 使用反射或内省将值写入到返回的对象中
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o,value);
- 将返回对象添加到结果集 list 当中
dao 的实现类存在问题
- dao 的实现类代码重复,每次都需获取 sqlSession
- statementId 存在硬编码问题
使用 dao 层接口的代理类
- 实现接口的任意方法都会调用代理类里面的 invoke()方法
- 需要获取 statementId 才能调用方法,需要将其定义为接口全限定名.方法名。
- 根据返回类型来执行不同的方法
@Override
public T getMapper(Class> mapperClass) {
// 使用JDK动态代理来为Dao接口生成代理对象,并返回
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
// 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
// 获取方法名:findAll
String methodName = method.getName();
//获取接口全限定名
String className = method.getDeclaringClass().getName();
//拼接ststementid
String statementId = className+"."+methodName;
// 准备参数2:params:args
// 获取被调用方法的返回值类型
Type genericReturnType = method.getGenericReturnType();
// 判断是否进行了 泛型类型参数化
if(genericReturnType instanceof ParameterizedType){
List
第二部分:Mybatis 相关概念
2.1 对象/关系数据库映射(ORM)
ORM 全称 Object/Relation Mapping:表示对象-关系映射的缩写
ORM 完成面向对象的编程语言到关系数据库的映射。当 ORM 框架完成映射后,程序员既可以利用面向 对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。ORM 把关系数据库包装成面向对 象的模型。ORM 框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。
采用 ORM 框架 后,应用程序不再直接访问底层数据库,而是以面向对象的放松来操作持久化对象,而 ORM 框架则将这 些面向对象的操作转换成底层 SQL 操作。
ORM 框架实现的效果:把对持久化对象的保存、修改、删除 等 操作,转换为对数据库的操作
2.2 Mybatis 简介MyBatis 是一款优秀的基于 ORM 的半自动轻量级持久层框架,它支持定制化 SQL、存储过程以及高级映 射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO (Plain Old Java Objects,普通老式 Java 对 象) 为数据库中的记录。
2.3 Mybatis 历史原是 apache 的一个开源项目 iBatis, 2010 年 6 月这个项目由 apache software foundation 迁移到了 google code,随着开发团队转投 Google Code 旗下,ibatis3.x 正式更名为 Mybatis ,代码于 2013 年 11 月迁移到 Github。
iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框 架包括 SQL Maps 和 Data Access Objects(DAO)
2.4 Mybatis 优势Mybatis 是一个半自动化的持久层框架,对开发人员开说,核心 sql 还是需要自己进行优化,sql 和 java 编 码进行分离,功能边界清晰,一个专注业务,一个专注数据。
第三部分:Mybatis 基本应用 3.1 快速入门MyBatis 官网地址:http://www.mybatis.org/mybatis-3/
3.1.1 开发步骤:开发步骤
① 添加 MyBatis 的坐标
② 创建 user 数据表
③ 编写 User 实体类
④ 编写映射文件 UserMapper.xml
⑤ 编写核心文件 SqlMapConfig.xml
⑥ 编写测试类
代码结构
3.1.2 环境搭建:UTF-8 UTF-8 1.8 1.8 1.8 org.mybatis mybatis 3.4.5 mysql mysql-connector-java 5.1.6 runtime junit junit 4.12
创建 user 数据表
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`birthday` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'lucy', '123', '2019-12-12');
INSERT INTO `user` VALUES ('2', 'tom','123', '2019-12-12');
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ordertime` varchar(255) DEFAULT NULL,
`total` double DEFAULT NULL,
`uid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `uid` (`uid`),
CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('1', '2019-12-12', '3000', '1');
INSERT INTO `orders` VALUES ('2', '2019-12-12', '4000', '1');
INSERT INTO `orders` VALUES ('3', '2019-12-12', '5000', '2');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rolename` varchar(255) DEFAULT NULL,
`roleDesc` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'CTO', 'CTO');
INSERT INTO `sys_role` VALUES ('2', 'CEO', 'CEO');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`userid` int(11) NOT NULL,
`roleid` int(11) NOT NULL,
PRIMARY KEY (`userid`,`roleid`),
KEY `roleid` (`roleid`),
CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `user` (`id`),
CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
INSERT INTO `sys_user_role` VALUES ('2', '2');
编写 User 实体
public class User {
private Integer id;
private String username;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
编写 UserMapper 映射文件
select * from user insert into user values(#{id},#{username}) update user set username = #{username} where id = #{id} delete from user where id = #{abc}
编写 MyBatis 核心文件 sqlMapConfig.xml
JDBC 数据信息
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///zdy_mybatis?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root
Dao mapper 代理
接口名称和方法名称对应于 sql mapper 里面的 namespace 和 sql id
public interface IUserDao {
//查询所有用户
public List findAll() throws IOException;
//多条件组合查询:演示if
public List findByCondition(User user);
//多值查询:演示foreach
public List findByIds(int[] ids);
}
编写测试代码
public class MybatisTest {
@Test
public void test1() throws IOException {
//1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
//2.解析了配置文件,并创建了sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.生产sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
//在进行增删改操作时,要手动提交事务
//4.sqlSession调用方法:查询所有selectList 查询单个:selectOne 添加:insert 修改:update 删除:delete
List users = sqlSession.selectList("user.findAll");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
@Test
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);//事务自动提交
User user = new User();
user.setId(6);
user.setUsername("tom");
sqlSession.insert("user.saveUser",user);
sqlSession.close();
}
@Test
public void test3() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(4);
user.setUsername("lucy");
sqlSession.update("user.updateUser",user);
sqlSession.commit();
sqlSession.close();
}
@Test
public void test4() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.delete("com.lagou.dao.IUserDao.deleteUser",6);
sqlSession.commit();
sqlSession.close();
}
@Test
public void test5() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
List all = mapper.findAll();
for (User user : all) {
System.out.println(user);
}
}
@Test
public void test6() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
User user1 = new User();
user1.setId(4);
user1.setUsername("lucy");
List all = mapper.findByCondition(user1);
for (User user : all) {
System.out.println(user);
}
}
@Test
public void test7() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
int[] arr = {1, 2};
List all = mapper.findByIds(arr);
for (User user : all) {
System.out.println(user);
}
}
}
3.2 核心配置文件分析
3.2,1 MyBatis 的映射文件概述
数据库环境的配置,支持多环境配置
其中,事务管理器(transactionManager)类型有两种:
JDBC:这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 用域。
MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生 命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。 其中,数据源(dataSource)类型有三种:
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。
JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置 数据源,然后放置一个 JNDI 上下文的引用。
mapper 标签
使用相对于类路径的资源引用,例如:•使用完全限定资源定位符(URL),例如: •使用映射器接口实现类的完全限定类名,例如: •将包内的映射器接口实现全部注册为映射器,必须同包同名例如:
typeAliases 标签
上面我们是自定义的别名,mybatis 框架已经为我们设置好的一些常用的类型的别名
SqlSession 工厂构建器 SqlSessionFactoryBuilder
常用 API:SqlSessionFactory build(InputStream inputStream)
通过加载 mybatis 的核心文件的输入流的形式构建一个 SqlSessionFactory 对象
String resource = "org/mybatis/builder/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream);
SqlSession 工厂对象 SqlSessionFactory
SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个
SqlSession 会话对象 SqlSession 实例在 MyBatis 中是非常强大的一个类。
在这里你会看到所有执行语句、提交或回滚事务和 获取映射器实例的方法。
执行语句的方法主要有:
3.2.2 代理开发方式T selectOne(String statement, Object parameter) List selectList(String statement, Object parameter) int insert(String statement, Object parameter) int update(String statement, Object parameter) int delete(String statement, Object parameter) void commit() void rollback()
代理开发方式介绍
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口 定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。
Mapper 接口开发需要遵循以下规范:
- Mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
- Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
测试代理方式
@Test
public void test5() throws IOException {
// IUserDao userDao = new UserDaoImpl();
// List users = userDao.findAll();
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
List users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
}
3.2.3 动态 sql 语句
where 和 if
where 会根据情况去掉第一个 and
循环执行 sql 的拼接操作 foreach
@Test
public void test7() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
int ids[] ={1,2};
List users = mapper.findByIds(ids );
for (User user : users) {
System.out.println(user);
}
}
foreach 标签的属性含义如下:
用于遍历集合,它的属性:
collection:代表要遍历的集合元素,注意编写时不要写#{}
open:代表语句的开始部分
close:代表结束部分
item:代表遍历集合的每个元素,生成的变量名
sperator:代表分隔符
3.2.4 延迟加载延迟加载
就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。
局部延迟加载
在 association 和 collection 标签中都有一个 fetchType 属性,通过修改它的值,可以修改局部的加载策 略。
全局延迟加载
在 Mybatis 的核心配置文件中可以使用 setting 标签修改全局的加载策略。
延迟加载原理实现
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属 性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的 invoke(…) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对 象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。
第四部分:Mybatis 复杂映射开发建表信息、依赖、数据库核心配置同上
1.1 一对一查询用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
对应的 sql 语句:select * from orders o,user u where o.uid=u.id;
创建 Order 和 User 实体
public class Order {
private Integer id;
private String orderTime;
private Double total;
//代表当前订单从属于哪一个客户
private User user;
}
public class User {
private Integer id;
private String username;
}
创建 OrderMapper 接口
public interface OrderMapper {
List findOrderAndUser();
}
配置 OrderMapper.xml
select * from orders o,user u where o.uid = u.id
sqlMapConfig.xml 添加对应到 mapper
添加引入映射配置文件路径相对于 sqlMapConfig.xml
测试
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
List all = mapper.findOrderAndUser();
for(Order order : all){ System.out.println(order); }
1.2 一对多查询
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
对应的 sql 语句:select *,o.id oid from user u left join orders o on u.id=o.uid;
查询的结果如下:
MyBatis 核心文件 sqlMapConfig.xml 改为包扫描
注意在 resources 目录下建立与接口同包同名的 mapper.xml
修改 User 实体 添加如下
//代表当前用户具备哪些订单 private ListorderList;
创建 UserMapper 接口
public interface UserMapper {
List findAll();
}
配置 UserMapper.xml
select * from user u left join orders o on u.id = o.uid
Test
private IUserMapper userMapper;
private IOrderMapper orderMapper;
@Before
public void befor() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
userMapper = sqlSession.getMapper(IUserMapper.class);
orderMapper = sqlSession.getMapper(IOrderMapper.class);
}
@Test
public void oneToMany(){
List all = userMapper.findAll();
for (User user : all) {
System.out.println(user);
}
}
1.3 多对多查询
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
-- 对应的sql语句: select u.,r.,r.id rid from user u left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;
创建 Role 实体,修改 User 实体
public class User implements Serializable {
private Integer id;
private String username;
private List orderList;
private List roleList;
}
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
}
添加 UserMapper 接口方法
ListfindAllUserAndRole();
修改 UserMapper.xml
select * from user u left join orders o on u.id = o.uid
测试
private IUserMapper userMapper;
private IOrderMapper orderMapper;
@Before
public void befor() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
userMapper = sqlSession.getMapper(IUserMapper.class);
orderMapper = sqlSession.getMapper(IOrderMapper.class);
}
@Test
public void ManyToMany(){
List all = userMapper.findAllUserAndRole();
for (User user : all) {
System.out.println(user);
}
}
第五部分:Mybatis 注解开发
1.1 MyBatis 的常用注解
这几年来注解开发越来越流行,Mybatis 也可以使用注解开发方式,这样我们就可以减少编写 Mapper 映射文件了。我们先围绕一些基本的 CRUD 来学习,再学习复杂映射多表操作。
@Insert:实现新增
@Update:实现更新
@Delete:实现删除
@Select:实现查询
@Result:实现结果集封装
@Results:可以与 @Result 一起使用,封装多个结果集
@One:实现一对一结果集封装
@Many:实现一对多结果集封装
MyBatis 的注解实现复杂映射开发
1.2 MyBatis 的常用注解实现在原有的基础上去掉 sqlMapper.xml 来实现
IUserMapper
package com.lagou.mapper;
import com.lagou.pojo.Order;
import com.lagou.pojo.Role;
import com.lagou.pojo.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface IUserMapper {
public List findAll();
public List findAllUserAndRole();
@Insert("insert into user values(#{id},#{username})")
public void addUser(User user);
@Update("update user set username=#{username} where id =#{id}")
public void update (User user);
@Select("select * from user")
public List selectUser ();
@Delete("delete from user where id =#{id}")
public void deleteUser(Integer id);
@Select("select * from user where id = #{id}")
public User selectUserById(Integer id);
@Select("select * from user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "orderList",column = "id",javaType = List.class,
many=@Many(select = "com.lagou.mapper.IOrderMapper.findOrderByUid"))
})
public List findAlls();
public User findUserById(Integer id);
}
IOrderMapper
package com.lagou.mapper;
import com.lagou.pojo.Order;
import com.lagou.pojo.User;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface IOrderMapper {
public List findOrderAndUser();
@Results({
@Result(property = "id",column = "id"),
@Result(property = "orderTime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",javaType = User.class,
one = @One(select = "com.lagou.mapper.IUserMapper.selectUserById")),
})
@Select("select * from orders ")
public List findOrderAndUsers();
@Select("select * from orders where uid = #{uid}")
public List findOrderByUid(Integer uid);
}
第六部分:Mybatis 缓存
缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存,我们可以避免频繁的与数据库 进行交互,进而提高响应速度
Mybatis 也提供了对缓存的支持,分为一级缓存和二级缓存,可以通过下图来理解:
①、一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个数 据结构(HashMap)用于存储缓存数据。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相不影响的
②、二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的
1.1 一级缓存代码测试缓存
public class CacheTest {
private IUserMapper userMapper;
private SqlSession sqlSession;
private SqlSessionFactory sqlSessionFactory;
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
userMapper = sqlSession.getMapper(IUserMapper.class);
}
@Test
public void firstLevelCache(){
// 第一次查询id为1的用户
User user1 = userMapper.findUserById(1);
//更新用户会刷新一级缓存
User user = new User();
user.setId(1);
user.setUsername("tom");
userMapper.updateUser(user);
sqlSession.commit();
sqlSession.clearCache();
// 第二次查询id为1的用户
User user2 = userMapper.findUserById(1);
//未更新为true只查询一次 ,更新后为flase查询两次
System.out.println(user1==user2);
}
}
同一个 sqlsession 第一次查询 与第二次查询获取用户一致,只执行一次 sql,第二次查询是从缓存获取
中间添加更新用户,则是查询两次获取的用户地址不同,更新用户刷新缓存
总结
1、第一次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,如果没有,从 数据 库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、 如果中间 sqlSession 去执行 commit 操作(执行插入、更新、删除),则会清空 SqlSession 中的 一级 缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3、 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直 接从 缓存中获取用户信息
一级缓存原理探究与源码分析
clearCache() 信息
流程走到 Perpetualcache 中的 clear()方法之后,会调用其 cache.clear()方法,那 么这个 cache 是什么东西呢?
点进去发现,cache 其实就是 private Map cache = new HashMap();
缓存其实就是本地存放的一个 map 对象,每一个 SqISession 都会存放一个 map 对象的引用
查询时候的一级缓存
缓存如果没有数据,则从数据库查询之后再存入缓存当中
1.2 二级缓存二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去 缓存中取。
一级缓存是基于 sqlSession 的,而二级缓存是基于 mapper 文件的 namespace 的,也 就 是说多个 sqlSession 可以共享一个 mapper 中的二级缓存区域,并且如果两个 mapper 的 namespace 相 同,即使是两个 mapper,那么这两个 mapper 中执行 sql 查询到的数据也将存在相同的二级缓存区域 中
如何使用二级缓存
① 开启二级缓存
一级缓存默认开启,二级缓存需要手动开启
首先在全局配置文件 sqlMapConfig.xml 文件中加入如下代码:
其次在 UserMapper.xml 文件中开启缓存
(注解则是在 mapper 添加注解 @CacheNamespace)
//@CacheNamespace(implementation = RedisCache.class)//开启二级缓存
@CacheNamespace//开启二级缓存
public interface IUserMapper {}
最后需要 pojo 实现 Serializable 进行缓存序列化
public class User implements Serializable {}
代码测试
@Test
public void SecondLevelCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
User user1 = mapper1.findUserById(1);
sqlSession1.close(); //清空一级缓存
User user = new User();
user.setId(1);
user.setUsername("lisi");
mapper3.updateUser(user);
sqlSession3.commit();
User user2 = mapper2.findUserById(1);
System.out.println(user1==user2);
}
执行结果
二级缓存只是缓存的数据而不是地址故而不等
添加 update 方法进行 commit 则缓存会刷新
useCache 和 flushCache
mybatis 中还可以配置 userCache 和 flushCache 等配置项,userCache 是用来设置是否禁用二级缓 存 的,在 statement 中设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询,默认情况是 true,即该 sql 使用二级缓存
设置 statement 配置中的 flushCache="true”属性,默认情况下为 true,即刷新缓存,如果改成 false 则 不 会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。
一般下执行完 commit 操作都需要刷新缓存,flushCache=true 表示刷新缓存,这样可以避免数据库脏 读。所以我们不用设置,默认即可
1.3 二级缓存整合 redis上面我们介绍了 mybatis 自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。 那么 什么是分布式缓存呢?假设现在有两个服务器 1 和 2,用户访问的时候访问了 1 服务器,查询后的缓 存就会 放在 1 服务器上,假设现在有个用户访问的是 2 服务器,那么他在 2 服务器上就无法获取刚刚那个 缓存, 如下图所示:
如 下图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中, 然 后无论有多少台服务器,我们都能从缓存中获取数据。
mybatis 与 redis 的整合
mybatis 提供了一个针对 cache 接口的 redis 实现类,该类存在 mybatis-redis 包 中
添加依赖
org.mybatis.caches mybatis-redis 1.0.0-beta2
对于配置文件 Mapper.xml
对于注解则是添加注解
@CacheNamespace(implementation = RedisCache.class)//开启二级缓存
public interface IUserMapper {}
redis.properties
下面的内容和默认的一致可以删除,如果要修改则必须添加且名称为 redis.properties 不能修改
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0
全局配置文件 sqlMapConfig.xml 文件中加入如下代码
最后需要 pojo 实现 Serializable 进行缓存序列化
public class User implements Serializable {}
代码测试
@Test
public void SecondLevelCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);
User user1 = mapper1.findUserById(1);
sqlSession1.close(); //清空一级缓存
User user = new User();
user.setId(1);
user.setUsername("lisi");
mapper3.updateUser(user);
sqlSession3.commit();
User user2 = mapper2.findUserById(1);
System.out.println(user1==user2);
}
源码分析
RedisConfig 类
public class RedisConfig extends JedisPoolConfig {
private String host = "localhost";
private int port = 6379;
private int connectionTimeout = 2000;
private int soTimeout = 2000;
private String password;
private int database = 0;
private String clientName;
public RedisConfig() {
}
RedisCache 类
public final class RedisCache implements Cache {
public void putObject(final Object key, final Object value) {
this.execute(new RedisCallback() {
public Object doWithRedis(Jedis jedis) {
jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
return null;
}
});
}
public Object getObject(final Object key) {
return this.execute(new RedisCallback() {
public Object doWithRedis(Jedis jedis) {
return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), key.toString().getBytes()));
}
});
}
public Object removeObject(final Object key) {
return this.execute(new RedisCallback() {
public Object doWithRedis(Jedis jedis) {
return jedis.hdel(RedisCache.this.id.toString(), new String[]{key.toString()});
}
});
}
public void clear() {
this.execute(new RedisCallback() {
public Object doWithRedis(Jedis jedis) {
jedis.del(RedisCache.this.id.toString());
return null;
}
});
}
mybatis-redis 在存储数据的时候,是使用的 hash 结构,把 cache 的 id 作为这个 hash 的 key (cache 的 id 在 mybatis 中就是 mapper 的 namespace);这个 mapper 中的查询缓存数据作为 hash 的 field,需要缓存的内容直接使用 SerializeUtil 存储,SerializeUtil 和其他的序列化类差不多,负责 对象的 序列化和反序列化;
第七部分:Mybatis 插件 1.1 插件简介一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见 的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工 作。以 MyBatis 为例,我们可基于 MyBati s 插件机制实现分页、分表,监控等功能。由于插件和业务 无 关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能
1.2 Mybatis 插件介绍Mybati s 作为一个应用广泛的优秀的 ORM 开源框架,这个框架具有强大的灵活性,在四大组件
(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩 展机制。Mybatis 对持久层的操作就是借助于四大核心对象。
MyBatis 支持用插件对四大核心对象进 行拦 截,对 mybatis 来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis 中的四大对象都是代理对象
MyBatis 所允许拦截的方法如下
执行器 Executor (update、query、commit、rollback 等方法);
SQL 语法构建器 StatementHandler (prepare、parameterize、batch、updates query 等方 法);
参数处理器 ParameterHandler (getParameterObject、setParameters 方法);
结果集处理器 ResultSetHandler (handleResultSets、handleOutputParameters 等方法);
1.3 Mybatis 插件原理在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler);
2、获取到所有的 Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返 回 target 包装后的对象
3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以 为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;
插件具体是如何拦截
interceptorChain 保存了所有的拦截器(interceptors),是 mybatis 初始化的时候创建的。调用拦截器链 中的拦截器依次的对目标进行拦截或增强。interceptor.plugin(target)中的 target 就可以理解为 mybatis 中的四大对象。返回的 target 是被重重代理后的对象
拦截 Executor 的 query 方法,那么可以这样定义插件
定义拦截器指定拦截内容
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) })
public class ExeunplePlugin implements Interceptor { //省略逻辑 }
将插件配置到 sqlMapConfig.xm l 中。
这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待 准备工作做完后,MyBatis 处于就绪状态。
我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创 建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor 实例创建完毕后, MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执 行。
1.4 自定义插件步骤
Mybatis 插件接口-Interceptor
intercept 方法,插件的核心方法
plugin 方法,生成 target 的代理对象
setProperties 方法,传递插件所需参数
实现自定义插件
添加拦截器 MyPlugin 实现 Interceptor 方法
@Intercepts({
@Signature(type= StatementHandler.class,
method = "prepare",
args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("对方法进行了增强....");
return invocation.proceed(); //原方法执行
}
@Override
public Object plugin(Object target) {
Object wrap = Plugin.wrap(target, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("获取到的配置文件的参数是:"+properties);
}
}
sqlMapConfig.xml 配置插件信息
mapper 接口
public interface UserMapper { List selectUser(); }
mapper.xml
测试类
private UserMapper userMapper;
private SqlSession sqlSession;
@Before
public void beforTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = build.openSession();
userMapper = sqlSession.getMapper(IUserMapper.class);
@Test
public void selectUser(){
List users = userMapper.selectUser();
for (User user : users) {
System.out.println(user);
}
}
1.5 pageHelper 分页插件
MyBati s 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper 是将分页的复杂操作进行封 装,使用简单的方式即可获得分页的相关数据
开发步骤
① 导入通用 PageHelper 的坐标
② 在 mybatis 核心配置文件中配置 PageHelper 插件
③ 测试分页数据获取
具体实现
① 导入通用 PageHelper 坐标
com.github.pagehelper
pagehelper
3.7.5
com.github.jsqlparser
jsqlparser
0.9.1
② 在 mybatis 核心配置文件中配置 PageHelper 插件
声明插件,指定方言是 mysql 而不是 oracle
③ 测试分页代码实现
只需要添加 PageHelper.startPage(1,1); 指定开始页和每页显示数据
@Test
public void pageHelperTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = build.openSession();
PageHelper.startPage(1,1);
mapper = sqlSession.getMapper(IUserMapper.class);
List alls = mapper.findAlls();
for (User user : alls
) {
System.out.println(user);
}
PageInfo pageInfo = new PageInfo<>(alls);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页数:"+pageInfo.getPageNum());
System.out.println("每页显示条数:"+pageInfo.getPageSize());
}
测试
1.6 通用 mapper什么是通用 Mapper
通用 Mapper 就是为了解决单表增删改查,基于 Mybatis 的插件机制。开发人员不需要编写 SQL,不需要 在 DAO 中增加方法,只要写好实体类,就能支持相应的增删改查方法
一个基本表的增删改查方法的集成,基于插件机制,只需要继承该接口即可不需要写 SQL 语句
通用 Mapper 的实现
在 pom.xml 中引入 mapper 的依赖
tk.mybatis mapper 3.1.2
Mybatis 配置文件中完成配置 插件
实体类设置 表主键
如果不对应则需要添加 @Column
@Table(name = "user")
public class User implements Serializable {
@Id //对应的是注解id
@GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键的生成策略
private Integer id;
private String username;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
'}';
}
定义通用 mapper
import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper{ }
测试
@Test
public void mapperTest() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setId(1);
User user1 = mapper.selectOne(user);
System.out.println(user1);
//2.example方法
Example example = new Example(User.class);
//条件查询
example.createCriteria().andEqualTo("id",1);
List users = mapper.selectByExample(example);
for (User user2 : users) {
System.out.println(user2);
}
mapper 基础接口
第八部分:Mybatis 架构原理 1.1 架构设计我们把 Mybatis 的功能架构分为三层:
(1) API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到 调用请求就会调用数据处理层来完成具体的数据处理。
MyBatis 和数据库的交互有两种方式:
a. 使用传统的 MyBatis 提供的 API ;
b. 使用 Mapper 代理的方式
(2) 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根 据调用的请求完成一次数据库操作。
(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑
1.2 主要构件及其相互关系| 构件 | 描述 |
|---|---|
| SqlSession | 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数 据库增删改查功能 |
| Executor | MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓 存 的维护 |
| StatementHandler | 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参 数、将 Statement 结果集转换成 List 集合。 |
| ParameterHandler | 负责对用户传递的参数转换成 JDBC Statement 所需要的参数, |
| ResultSetHandler | 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合; |
| TypeHandler | 负责 java 数据类型和 jdbc 数据类型之间的映射和转换 |
| MappedStatement | MappedStatement 维护了一条 <select | update | delete | insert> 节点 的封 装 |
| SqlSource | 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封 装到 BoundSql 对象中,并返回 |
| BoundSql | 表示动态生成的 SQL 语句以及相应的参数信息 |
(1) 加载配置并初始化
触发条件:加载配置文件 配置来源于两个地方,一个是配置文件(主配置文件 conf.xml,mapper 文件*.xml),—个是 java 代码中的 注 解,将主配置文件内容解析封装到 Configuration,将 sql 的配置信息加载成为一个 mappedstatement 对 象,存储在内存之中
(2) 接收调用请求
触发条件:调用 Mybatis 提供的 API
传入参数:为 SQL 的 ID 和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
(3) 处理操作请求
触发条件:API 接口层传递请求过来
传入参数:为 SQL 的 ID 和传入参数对象
处理过程:
(A) 根据 SQL 的 ID 查找对应的 MappedStatement 对象。
(B) 根据传入参数对象解析 MappedStatement 对象,得到最终要执行的 SQL 和执行传入参数。
© 获取数据库连接,根据得到的最终 SQL 语句和执行传入参数到数据库执行,并得到执行结果。
(D) 根据 MappedStatement 对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理 结果。
(E) 释放连接资源。
(4) 返回处理结果
将最终的处理结果返回。
1.4 重点介绍本质是对 JDBC 的封装
执行步骤
根据配置文件 sqlMapConfig.xml 获取配置文件信息流
根据流解析 XML 获取 Configuration 对象封装了配置信息及 mapper 信息 List
继续封装成 DefaultSqlSession 并用 SqlSession 接口进行接收
通过 SqlSession 的 API 根据具体的 namespace.id 获取到具体的 SQL 进行执行
SqlSession
SqlSession 是一个接口,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃用,不做介绍)
SqlSession 是 MyBatis 中用于和数据库交互的顶层类,通常将它与 ThreadLocal 绑定,一个会话使用一 个 SqlSession,并且在使用完毕后需要 close
SqlSession 中的两个最重要的参数,configuration 与初始化时的相同,Executor 为执行器
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor; j
Executor
Executor 也是一个接口,他有三个常用的实现类:
BatchExecutor (重用语句并执行批量更新)
ReuseExecutor (重用预处理语句 prepared statements)
SimpleExecutor (普通的执行器,默认)
//此方法在SimpleExecutor的父类baseExecutor中实现 public总结List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示 BoundSql boundSql = ms.getBoundSql(parameter); //为本次查询创建缓存的Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
完整代码参考 https://github.com/LaneDu/lane-mybatis-11,感觉懂了,又感觉不怎么懂,或许是因为一般使用的时候都是结合 spring 系列吧!
强烈建议接着看看 MyBatis Plus 笔记 我学完MyBatis Plus 感觉对MyBatis、Sprin、Spring Boot的理解加深了很多



