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();
            }
        }

    }
}

  1. 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池
  2. sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布。不好维护
  3. 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护
  4. 结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便

针对JDBC编程的不足,Mybatis是如何解决这些问题

  1. Mybatis-config.xml中配置数据连接池,使用连接池管理数据库连接

    • POOLED:由Mybatis创建传统的javax.sql.DataSource连接池用于数据库操作,操作完成后Mybatis会将连接返回给连接池,此配置常见于开发或测试环境中。
    • UNPOOLED:由Mybatis为每一次数据库操作创建一个新的连接,并在操作完成后关闭连接,此配置未践行池化思想且仅适用于规模较小的并发应用程序中。
    • JNDI:采用服务器提供的JNDI技术获取DataSource对象,不同服务器中获取的DataSource对象不一致,例如在Tomcat服务器中采用DBCP连接池,此配置不适用于非Web或Maven的war工程。
  2. 将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

  3. Mybatis自动将java对象映射至sql语句

  4. 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;

插入数据

image-20210522104958868

创建实体类

/**
 * @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动手造轮子:写一个日志框架

自己动手造“轮子”---python常用的几个方法

动手造轮子:实现一个简单的依赖注入

自己动手写Java String类

爬虫框架概述

Python之爬虫框架概述