Mybatis实现及原理

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 什么是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);
  }
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
2月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
40 1
|
4月前
|
SQL Java 数据库连接
mybatis常见分页技术和自定义分页原理实战
mybatis常见分页技术和自定义分页原理实战
|
7月前
|
SQL XML Java
Mybatis的mapper接口实现原理
Mybatis的mapper接口实现原理
130 0
|
7月前
|
SQL Java 数据库连接
MyBatis原理分析手写持久层框架1
MyBatis原理分析手写持久层框架1
64 0
|
4月前
|
SQL 缓存 Java
MyBatis原理分析之获取SqlSessionFactory
MyBatis原理分析之获取SqlSessionFactory
101 0
|
1月前
|
XML Java 数据库连接
【MyBatis】 框架原理
【MyBatis】 框架原理
17 0
|
1月前
|
缓存 Java 数据库连接
mybatis 数据库缓存的原理
MyBatis 是一个流行的 Java 持久层框架,它封装了 JDBC,使数据库交互变得更简单、直观。MyBatis 支持两级缓存:一级缓存(Local Cache)和二级缓存(Global Cache),通过这两级缓存可以有效地减少数据库的访问次数,提高应用性能。
282 1
|
2月前
|
SQL Java 数据库连接
一篇看懂Mybatis的SqlSession运行原理
SqlSession是Mybatis最重要的构建之一,可以简单的认为Mybatis一系列的配置目的是生成类似 JDBC生成的Connection对象的SqlSession对象,这样才能与数据库开启“沟通”,通过SqlSession可以实现增删改查(当然现在更加推荐是使用Mapper接口形式),那么它是如何执行实现的,这就是本篇博文所介绍的东西,其中会涉及到简单的源码讲解。
32 1
|
3月前
|
SQL 缓存 Java
mybatis工作原理
mybatis工作原理
82 0