Mybatis实现及原理

简介: 什么是Mybatis MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

什么是Mybatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

Mybatis优势

  • 与JDBC相比,减少了50%以上的代码量。
  • MyBatis实现了接口绑定,无需自己写DAO实现类.
  • MyBatis是最简单的持久化框架,小巧并且简单易学。
  • MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统一管理和优化,并可重用

Mybatis简单实现

  • Mybatis主要是为了简化我们操纵数据库的过程,那么首先应该在本地的数据库建立一张表。
CREATE TABLE STUDENT (
  id          INT,
  name        VARCHAR(20),
  PRIMARY KEY (id)
);
  • 向表中插入记录。
INSERT INTO student (id,name) VALUES (1,'A');
INSERT INTO student (id,name) VALUES (2,'B');
INSERT INTO student (id,name) VALUES (3,'C');
INSERT INTO student (id,name) VALUES (4,'D');
INSERT INTO student (id,name) VALUES (5,'E');
INSERT INTO student (id,name) VALUES (6,'F');
INSERT INTO student (id,name) VALUES (7,'F');
INSERT INTO student (id,name) VALUES (8,'G');
INSERT INTO student (id,name) VALUES (9,'H');
INSERT INTO student (id,name) VALUES (10,'I');
  • 在IDEA中新建一个maven工程,并加入使用Mybatis相关依赖。
    <!-- 与连接数据库相关的依赖 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.20</version>
    </dependency>
    <!-- 与Mybatis相关的依赖 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.0</version>
    </dependency>
  • 编写domain类,此类中的属性与数据库中的列名一一对应。
package com.alibaba.mybatis.domain;

public class Student {
    private Integer id;
    
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return "[" + "id:" + id + " name:" + name + "]";
    }
}
  • 编写dao层接口,不需要编写该接口的实现类。具体的原因会在下面进行具体的解释。这也是使用Mybatis后最大的困惑。
package com.alibaba.mybatis.mapper;

import com.alibaba.mybatis.domain.Student;

import java.util.List;

public interface StudentMapper {
    List<Student> findAll();
}
  • 在这里我们要实现的是查询数据库的功能,因此,我们需要编写dao层的mapper映射文件。该文件的具体内容为:
<?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="com.alibaba.mybatis.mapper.StudentMapper">
    <select id="findAll" resultType="Student">
        select * from student
    </select>
</mapper>
  • 配置mybatis-config文件,该文件的具体的路径为:src/main/resources。
<?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>
    <!-- 类型的别名 -->
    <typeAliases>
        <package name="com.alibaba.mybatis.domain"/>
    </typeAliases>
    <!-- 配置Mybatis运行环境 -->
    <environments default="development">
        <environment id="development">
            <!-- type="JDBC" 代表使用JDBC的提交和回滚来管理事务 -->
            <transactionManager type="JDBC"/>
            <!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
            <!-- POOLED 表示支持JDBC数据源连接池 -->
            <!-- UNPOOLED 表示不支持数据源连接池 -->
            <!-- JNDI 表示支持外部数据源连接池 -->
            <dataSource type="POOLED">
                <!-- 数据库的具体配置 -->
                <!-- url表示你数据库的地址
                     username表示的是数据库的用户
                     password表示的是数据库的密码-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/hongchen"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <!-- 注册StudentMapper.xml映射文件 -->
    <mappers>
        <package name="com.alibaba.mybatis.mapper"/>
    </mappers>
</configuration>
  • 编写测试类
package com.alibaba.mybatis;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class DBTools {
    public static SqlSessionFactory sessionFactory;

    static{
        try {
            //使用MyBatis提供的Resources类加载mybatis的配置文件
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            //构建sqlSession的工厂
            sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    //创建能执行映射文件中sql的sqlSession
    public static SqlSession getSession(){
        return sessionFactory.openSession();
    }

}
package com.alibaba.mybatis;

import com.alibaba.mybatis.domain.Student;
import com.alibaba.mybatis.mapper.StudentMapper;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Demo {

    public static void main(String[] args) {
        selectAllUser();
    }

    /**
     * 查询所有的用户
     */
    private static void selectAllUser(){
        SqlSession session=DBTools.getSession();
        StudentMapper mapper=session.getMapper(StudentMapper.class);
        try {
            List<Student> user=mapper.findAll();
            System.out.println(user.toString());
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
            session.close();
        }
    }

}

执行Demo类中的main函数,得到如下的结果:
mybatis.png

Mybatis技术内幕

在学习Mybatis过程中遇到一个一直困扰我很久的问题,在Mybatis中声明一个interface接口,没有编写任何实现类,Mybatis就能返回接口实例,并调用接口方法返回数据库中的数据。实现这个功能的底层原理主要是底层采用了jdk动态代理。

动态代理

动态代理的主要功能是:通过拦截器方法回调,对目标target方法进行增强。言外之意就是为了增强目标target方法,这句话确实是讲的没有错误,但是在动态代理的过程中我们也可以做到连目标tagert都不要的霸权。

自定义自动映射器Mapper

首先我们先不研究源码,通过仿照源码的格式来自定义我们的自动映射器。其中我们的domain类还是采用的上面的Student类,不过在原来的基础上加上一个带参构造器。

public class Student {
    private Integer id;

    private String name;

    public Student(Integer id,String name) {
        this.id=id;
        this.name=name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "[" + "id:" + id + " name:" + name + "]";
    }
}

dao层接口中的方法改为getStudentById()。

public interface StudentMapper {

    public Student getStudentById(Integer id);
}

实现一个InvocationHandler。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MapperProxy implements InvocationHandler {

    public <T> T newInstance(Class<T> clz) {
        return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                // 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this
                return method.invoke(this, args);
            } catch (Throwable t) {
            }
        }
        
        return new Student((Integer) args[0], "hongchen.zhx");
    }
}

在MapperProxy类中,我们可以看到在执行Object对象的方法时,target被指向了this,此时target已经可以说是成为一个傀儡了。已经没有target什么事了。
编写测试代码进行测试。

public class Demo {
    public static void main(String[] args) {
        MapperProxy proxy = new MapperProxy();

        StudentMapper mapper = proxy.newInstance(StudentMapper.class);
        Student student= mapper.getStudentById(1001);

        System.out.println("ID:" + student.getId());
        System.out.println("Name:" + student.getName());

        System.out.println(mapper.toString());
    }
}

那么测试代码运行后的结果为:
121345.png

真正的MapperProxy

通过以上自定义的Mapper映射器再来看相对应的源码就会轻松很多。

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
      //在执行诸如Object的方式时,target指向当前对象this。
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //调用MapperMethod.execute()方法执行sql语句。
    return mapperMethod.execute(sqlSession, args);
  }
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
291 1
|
SQL Java 数据库连接
mybatis常见分页技术和自定义分页原理实战
mybatis常见分页技术和自定义分页原理实战
994 0
|
9月前
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
SQL Java 数据库连接
Mybatis架构原理和机制,图文详解版,超详细!
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,在一线互联网大厂中应用广泛,Mybatis已经成为了一个必会框架。本文详细解析了MyBatis的架构原理与机制,帮助读者全面提升对MyBatis的理解和应用能力。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Mybatis架构原理和机制,图文详解版,超详细!
|
SQL 缓存 Java
MyBatis原理分析之获取SqlSessionFactory
MyBatis原理分析之获取SqlSessionFactory
620 0
|
SQL XML Java
Mybatis的原理和MybaitsPlus
这篇文章对比分析了Mybatis和Mybatis Plus的特点与底层实现机制,探讨了两者之间的差异及各自的优势。
399 0
|
SQL Java 数据库连接
springboot~mybatis-pagehelper原理与使用
【7月更文挑战第15天】MyBatis-PageHelper是用于MyBatis的分页插件,基于MyBatis的拦截器机制实现。它通过在SQL执行前动态修改SQL语句添加LIMIT子句以支持分页。使用时需在`pom.xml`添加依赖并配置方言等参数。示例代码: PageHelper.startPage(2, 10); List&lt;User&gt; users = userMapper.getAllUsers(); PageInfo&lt;User&gt; pageInfo = new PageInfo&lt;&gt;(users); 这使得分页查询变得简单且能获取总记录数等信息。
545 2
|
SQL Java 数据库连接
深入探索MyBatis Dynamic SQL:发展、原理与应用
深入探索MyBatis Dynamic SQL:发展、原理与应用
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
261 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析