Mybatis源码分析(自己动手造轮子)
Posted 程序dunk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析(自己动手造轮子)相关的知识,希望对你有一定的参考价值。
个人博客欢迎访问
总结不易,如果对你有帮助,请点赞关注支持一下
Mybatis基础
概念
Mybatis是什么
Mybatis是一款优秀的持久层框架,一个半ORM(对象关系映射)框架,它支持定制化SQL、存储过程以及高级映射。Mybatis避免了所有JDBC代码和手动设置参数以及获取结果集。Mybatis可以使用简单的XML或注解来配置和映射原生类型、接口和Java的POJO(普通老式Java对象)为数据库中的记录
ORM是什么
ORM(Object Relational Mapping)对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单来说,ORM是通过使用描述对象和数据库之间的映射关系的元数据,将程序中的对象自动持久化到关系型数据库中
为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以他是全自动的
而Mybatis在查询关联对象或者关联集合对象时,需要手动编写SQL来完成,所以称之为半自动ORM映射工具
传统JDBC开发存在的问题
/**
* @author :zsy
* @date :Created 2021/5/16 20:42
* @description:JDBC
*/
public class JDBCTest {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
PreparedStatement preparedStatement = null;
try {
//注册驱动
Class.forName(driver);
//获取连接
conn = DriverManager.getConnection(url, user, password);
//获取数据库操作对象
//stmt = conn.createStatement();
//执行sql语句
String sql = "select empno,ename,sal from emp where sal > ?";
//int count=executUpdate(insert/delete/update)
//ResultSet rs=executeQuery(select)
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, 3000);
rs = preparedStatement.executeQuery();
//处理数据查询集
//boolean flag1=rs.next();
while (rs.next()) {
String empno = rs.getString("empno");//JDBC中所有下标从1开始。不是从0开始。
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "\\t" + ename + "\\t" + sal);
}
/*while(rs.next()){
String empno=rs.getString(1);//JDBC中所有下标从1开始。不是从0开始。
String ename=rs.getString(2);
String sal=rs.getString(3);
System.out.println(empno+"\\t"+ename+"\\t"+sal);
}*/
//释放资源
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (preparedStatement != null) {
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池
- sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护
- 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护
- 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便
针对JDBC编程的不足,Mybatis是如何解决这些问题
-
Mybatis-config.xml中配置数据连接池,使用连接池管理数据库连接
- POOLED:由Mybatis创建传统的javax.sql.DataSource连接池用于数据库操作,操作完成后Mybatis会将连接返回给连接池,此配置常见于开发或测试环境中。
- UNPOOLED:由Mybatis为每一次数据库操作创建一个新的连接,并在操作完成后关闭连接,此配置未践行池化思想且仅适用于规模较小的并发应用程序中。
- JNDI:采用服务器提供的JNDI技术获取DataSource对象,不同服务器中获取的DataSource对象不一致,例如在Tomcat服务器中采用DBCP连接池,此配置不适用于非Web或Maven的war工程。
-
将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
-
Mybatis自动将java对象映射至sql语句
-
Mybatis自动将sql执行结果映射至java对象
Mybatis优缺点
优点
与传统的数据库访问技术相比,ORM有以下优点:
- 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
- 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
- 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
- 能够与Spring很好的集成
缺点
- SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
Hibernate和Mybatis的区别
相同点
都是对jdbc的封装,都是持久层的框架,都用于dao层的开发。
映射关系
- MyBatis 是一个半自动映射的框架,配置Java对象与sql语句执行结果的对应关系,多表关联关系配置简单
- Hibernate 是一个全表映射的框架,配置Java对象与数据库表的对应关系,多表关联关系配置复杂
SQL优化和移植性
- Hibernate 对SQL语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但SQL语句优化困难。
- MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用SQL语句操作数据库,不支持数据库无关性,但sql语句优化容易。
开发难易程度和学习成本
- Hibernate 是重量级框架,学习使用门槛高,适合于需求相对稳定,中小型的项目,比如:办公自动化系统
- MyBatis 是轻量级框架,学习使用门槛低,适合于需求变化频繁,大型的项目,比如:互联网电子商务系统
总结
MyBatis 是一个小巧、方便、高效、简单、直接、半自动化的持久层框架,
Hibernate 是一个强大、方便、高效、复杂、间接、全自动化的持久层框架。
缓存
简介
Mybatis的一级、二级缓存
一级缓存:基于PerpetualCache 的HashMap本地缓存,其存储作用域为Session,当Session flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存
二级缓存与一级缓存其机制相同,默认也是采用PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。默认不打开耳机缓存,要开启二级缓存,使用二级缓存属性类要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置
对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear
一级缓存
一级缓存也叫本地缓存:SQLSession
基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当Session flush或者close之后,该Session中的所有Cache都会被清空,默认打开一级缓存
二级缓存
- 二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
- 默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache
- 工作机制
- 一个会话查询一条数据,这个数据会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这分会话对应的一级缓存就没有了,但是我们想要的是会话关闭了,一级缓存中数据被保存到二级缓存中
- 新的会话查询信息,就会直接从二级缓存中获取数据
- 不同的mapper查出的数据会放在自己对应的缓存中
对于缓存数据的更新机制,当某一个作用域(一级缓存或者二级缓存namespaces)进行了CUD操作后,默认该作用域下的所有select中的缓存将被clear
开启二级缓存的步骤
全局配置参数
<setting name="cacheEnabled" value="true"/>
开启二级缓存
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 按照先进先出的淘汰策略缓存项
- 缓存容量为512个对象引用
- 缓存每隔60s刷新一次
- 缓存返回的对象是写安全的,即在外部修改对象不会影响到缓存内部存储对象
测试二级缓存
/**
* @author :zsy
* @date :Created 2021/5/13 22:36
* @description:测试缓存
*/
public class Test {
@org.junit.Test
public void test() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
EmpMapper empMapper1 = sqlSession1.getMapper(EmpMapper.class);
EmpMapper empMapper2 = sqlSession2.getMapper(EmpMapper.class);
Employee user1 = empMapper1.select(1);
Employee user2 = empMapper2.select(1);
sqlSession1.close();
sqlSession2.close();
System.out.println(user1 == user2);
}
}
高级查询
建表
根据数据库设计的第三范式,设计两张表,t_class(班级表)和t_stu(学生表),学生与班级之间是一对一的关系,班级与学生之间是多对一的关系(加外键),具体实现:
班级表
create table t_class(
id int(10) primary key auto_increment,
cname varchar(20) not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
学生表
create table t_stu (
id int(10) primary key auto_increment,
name varchar(20) not null,
cid int(10) not null,
constraint fk_stu_class foreign key(cid) references t_class(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
创建实体类
/**
* @author :zsy
* @date :Created 2021/5/22 10:51
* @description:班级类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Clazz {
private int id;
private String cname;
private List<Student> students;
}
/**
* @author :zsy
* @date :Created 2021/5/22 10:50
* @description:学生类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Student {
private int id;
private String cname;
private Clazz clazz;
}
一对一查询
Mybatis实现一对一查询的方式:通过在里面配置association节点完成一对一类的查询
- 联合查询:几个表联合查询,只查询一次
- 嵌套查询:先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据
联合查询
两种写法
<mapper namespace="school.xauat.mapper.StudentMapper">
<resultMap id="studentResultMap" type="student">
<id property="id" column="id>"></id>
<result property="name" column="name"></result>
<association property="clazz" column="cid" javaType="clazz">
<id property="id" column="id"></id>
<result property="cname" column="cname"></result>
</association>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select s.id, s.name, c.id as cid, c.cname from t_stu s left join t_class c on s.cid = c.id where s.id = #{id}
</select>
</mapper>
区别于上面,class对象需要关联
<mapper namespace="school.xauat.mapper.StudentMapper">
<resultMap id="studentResultMap" type="student">
<id property="id" column="id>"></id>
<result property="name" column="name"></result>
<association property="clazz" column="cid" resultMap="class">
</association>
</resultMap>
<resultMap id="class" type="clazz">
<id property="id" column="id"></id>
<result property="cname" column="cname"></result>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select s.id, s.name, c.id as cid, c.cname from t_stu s left join t_class c on s.cid = c.id where s.id = #{id}
</select>
</mapper>
嵌套查询
<mapper namespace="school.xauat.mapper.StudentMapper">
<resultMap id="studentResultMap" type="student">
<id property="id" column="id>"></id>
<result property="name" column="name"></result>
<association property="clazz" column="cid" javaType="clazz" select="selectClass">
<id property="id" column="id"></id>
<result property="name" column="cname"></result>
</association>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
select * from t_stu where id = #{id}
</select>
<select id="selectClass" resultType="clazz">
select * from t_class where id = #{id}
</select>
</mapper>
一对多查询
<mapper namespace="school.xauat.mapper.ClassMapper">
<resultMap id="ClassResultMap" type="clazz">
<id property="id" column="classId"></id>
<result property="cname" column="cname"></result>
<collection property="students" column="classId" javaType="ArrayList" select="getStudentById"></collection>
</resultMap>
<resultMap id="studentResultMap" type="student">
<id property="id" column="id"></id>
<result property="name" column="name动手造轮子:写一个日志框架