zoukankan      html  css  js  c++  java
  • MyBatis源码解析

    在讲解MyBatis之前,先说下传统JDBC连接数据库的弊端:
    1.JDBC底层没有实现连接池,从而导致操作数据库需要频繁的创建和释放,影响性能;
    2.JDBC的代码散落在Java代码中,如果需要修改SQL语句,需要重新编译Java类;
    3.使用PreparedStatement设置参数繁,占位符和参数需要一一对应;
    4.处理返回的结果集解析也很麻烦。

    所以,在实际开发中,基本不会使用原生的JDBC来操作数据库。

    然后,说一下Hibernate和MyBatis的区别,现在互联网公司基本上都是以MyBatis为主,因MyBatis能够书写比较复杂的SQL语句,比较灵活。

    MyBatis的核心概念

    类名 描述
    Configuration 描述mybatis-config.xml全局配置关系类
    SqlSessionFactory Session管理工厂接口
    SqlSession SqlSession接口。 SqlSession中提供了操作数据库的方法,完成一次数据库的访问和结果的映射.不是线程安全
    Executor 执行器。SqlSession通过执行器操作数据库,调用StatementHandler房屋数据库,并缓存查询结果
    MappedStatement 底层封装对象。对操作数据库存储封装,包括sql语句、输入输出参数
    StatementHandler 具体操作数据库相关的handler接口
    ResultSetHandler 具体操作数据库返回结果的handler接口

    下面我们新建一个项目,
    首先创建一个表,并插入数据:

    CREATE TABLE STUDENTS
    (
        stud_id int(11)     NOT NULL AUTO_INCREMENT,
        name    varchar(50) NOT NULL,
        email   varchar(50) NOT NULL,
        dob     date DEFAULT NULL,
        PRIMARY KEY (stud_id)
    ) ENGINE = InnoDB
      AUTO_INCREMENT = 1
      DEFAULT CHARSET = UTF8;
    
    insert into students(stud_id, name, email, dob)
    values (1, 'Student1', 'student1@gmail.com', '1990-06-25');
    insert into students(stud_id, name, email, dob)
    values (2, 'Student2', 'student2@gmail.com', '1990-06-25');
    

    新建一个Student类:

    package com.mybatis3.domain;
    
    import java.util.Date;
    
    public class Student {
        private Integer studId;
        private String name;
        private String email;
        private Date dob;
    
        public Student() {
    
        }
    
        public Student(Integer studId) {
            this.studId = studId;
        }
    
        public Student(Integer studId, String name, String email, Date dob) {
            this.studId = studId;
            this.name = name;
            this.email = email;
            this.dob = dob;
        }
    
        @Override
        public String toString() {
            return "Student [studId=" + studId + ", name=" + name + ", email="
                    + email + ", dob=" + dob + "]";
        }
        // todo getter and setter
    }
    
    

    新建一个mapper类:

    import java.util.List;
    
    import com.mybatis3.domain.Student;
    
    public interface StudentMapper {
        Student findStudentById(Integer id);
    }
    

    新建Service类:

    public class StudentService {
        private Logger logger = LoggerFactory.getLogger(getClass());
    
        public Student findStudentById(Integer studId) {
            logger.debug("Select Student By ID :{}", studId);
            SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
            try {
                StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
                return studentMapper.findStudentById(studId);
                //return sqlSession.selectOne("com.mybatis3.StudentMapper.findStudentById", studId);
            } finally {
                sqlSession.close();
            }
        }
    }
    

    新建一个SqlSessionFactory工具类:

    package com.mybatis3.util;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.util.Properties;
    
    import org.apache.ibatis.datasource.DataSourceFactory;
    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 MyBatisSqlSessionFactory {
        private static SqlSessionFactory sqlSessionFactory;
    
        private static final Properties PROPERTIES = new Properties();
    
        static {
            try {
                InputStream is = DataSourceFactory.class.getResourceAsStream("/application.properties");
                PROPERTIES.load(is);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static SqlSessionFactory getSqlSessionFactory() {
            if (sqlSessionFactory == null) {
                try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
                    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
                } catch (IOException e) {
                    throw new RuntimeException(e.getCause());
                }
            }
            return sqlSessionFactory;
        }
    
        public static SqlSession getSqlSession() {
            return getSqlSessionFactory().openSession();
        }
    
        public static Connection getConnection() {
            String driver = PROPERTIES.getProperty("jdbc.driverClassName");
            String url = PROPERTIES.getProperty("jdbc.url");
            String username = PROPERTIES.getProperty("jdbc.username");
            String password = PROPERTIES.getProperty("jdbc.password");
            Connection connection = null;
            try {
                Class.forName(driver);
                connection = DriverManager.getConnection(url, username, password);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return connection;
        }
    }
    

    编写StudentMapper.xml配置文件

    <?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.mybatis3.mappers.StudentMapper">
        <resultMap type="Student" id="StudentResult">
            <id property="studId" column="stud_id"/>
            <result property="name" column="name"/>
            <result property="email" column="email"/>
            <result property="dob" column="dob"/>
        </resultMap>
    
        <select id="findStudentById" parameterType="int" resultType="Student">
            select stud_id as studId, name, email, dob
            from Students
            where stud_id = #{studId}
        </select>
    </mapper>
    

    最后编写test类:

    package com.mybatis3.services;
    
    import java.util.Date;
    import java.util.List;
    
    import org.junit.AfterClass;
    
    import static org.junit.Assert.*;
    
    import org.junit.BeforeClass;
    import org.junit.Test;
    
    import com.mybatis3.domain.Student;
    
    public class StudentServiceTest {
        private static StudentService studentService;
    
        @Test
        public void testFindStudentById() {
            Student student = studentService.findStudentById(1);
            assertNotNull(student);
        }
    }
    
    

    整个代码结构如下:

    现在正式调试testFindStudentById方法,调用到 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)方法,
    在这里设置断点,进入MyBatis的源码里,

    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    

    这行代码没有必要看,接着往下看:

    return build(parser.parse());
    

    parse方法点进去:

    parseConfiguration(parser.evalNode("/configuration"));
    

    进入到parseConfiguration方法里面:

    这些属性就是对应mybatis-config.xml里面的参数,这个方法就是解析xml里面所有的内容。
    点击进入mapperElement方法,这个方法,就是规定了mapper标签的加载优先级顺序:

    如果配置重复,会抛出异常。

    回到上面的bulid方法:

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    

    可以看到就是返回我们SqlSessionFactory(是个接口)的实例对象,其参数就是我们的Configuration全局配置类。

    上面到此为止告一段落,接下来看下sqlSessionFactory.openSession()方法,点进去:

     public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
    

    上面的getDefaultExecutorType方法就是返回执行器类型,是个枚举,默认是SIMPLE:

    public enum ExecutorType {
      SIMPLE, REUSE, BATCH
    }
    

    点进去openSessionFromDataSource方法,这个方法就是获取环境信息,创建一个事务,再获取一个执行器:

     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;
        try {
          final Environment environment = configuration.getEnvironment();
          final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
          tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
          final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
          return new DefaultSqlSession(configuration, executor);
        } catch (Exception e) {
          closeTransaction(tx); // may have fetched a connection so lets call close()
          throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    

    点进newExecutor方法:根据条件创建不同的执行器。
    执行器的层级关系如下:

    此方法里有个非常重要的一段代码:

     if (cacheEnabled) {
          executor = new CachingExecutor(executor, autoCommit);
        }
    

    CachingExecutor是创建一级缓存的执行器。

    到目前为止,已经拿到SqlSession,并对SimpleExecutor执行器进行初始化。

    第三步:执行我们编写的return sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);方法。

    点进去selectOne,selectOne会调用DefaultSqlSession.selectList方法,点进去:

    看下statement的参数值,就是StudentMapper.xml里 mapper标签名称 + select标签id的名称,所以这里的id是要唯一的。

    这里出现了一个新的核心类MappedStatement,对应的StudentMapper.xml里的select,update,insert,delete语句。
    下面代码:

          List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    

    进入query方法:

     public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    

    getBoundSql就是绑定sql语句的参数,具体值如下:

    createCacheKey方法创建缓存key,点进去,一直找到createCacheKey方法,包括缓存参数的更新,给sql创建了一个缓存,缓存可以是由id + sql + limit + offset组成,具有相同的key会做缓存。

    query方法会先拿到缓存,缓存如果不为空的话,判断是否要清空缓存,如果使用缓存,且不是脏数据,则使用读写锁进行加锁,从不同的缓存策略中获取缓存列表;
    缓存为空的话,会查询数据库,
    进入 List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);方法,
    在真正查询数据库之前,会再次判断已经存在一级缓存,如果没有,执行queryFromDatabase方法,执行里面的doQuery方法:

     public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    

    这里又出现了一个核心类StatementHandler,这个接口是设计具体数据库的操作。 进入 return handler.<E>query(stmt, resultHandler);方法:

     public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.<E> handleResultSets(ps);
      }
    

    这里出现的JDBC的PreparedStatement 类,不用解释了吧,已经调到Java JDBC的这层API了。
    进入到handleResultSets方法,执行此方法,就已经返回数据集的结果了。如下图:

    现在总结一下:

    至此,基本上就是整个MyBatis查询数据的一个过程。

  • 相关阅读:
    maven springMVC SSM框架中 出现的406 (Not Acceptable)
    eclipse中maven项目部署到tomcat
    @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
    springMvc注解之@ResponseBody和@RequestBody
    springmvc后台接前端的参数,数组,集合,复杂对象等
    Spring项目JUnit测试报错ClassNotFoundException解决
    后台给前端返回图片
    前端js实现 blob转base64位 和 base64位转blob
    tomcat中实现特定路径下的图片的url访问Tomcat配置图片保存路径,图片不保存在项目路径下
    data:image/png;base64 上传图像将图片转换成base64格式
  • 原文地址:https://www.cnblogs.com/IcanFixIt/p/13975648.html
Copyright © 2011-2022 走看看