ORM思想及Hibernate和Mybatis框架实现原理

1、ORM思想(Object Relational Mapping)

目前,通过Java语言连接并操作数据库的技术或方式已经有很多了,例如:JDBC,Hibernate,MyBatis,TopLink等等。其中JDBC是Java原生的API,支持连接并操作各种关系型数据库。相信每个程序员都是从JDBC开始学起的,然后才接触到各种持久层框架。

JDBC作为Java原生API,有优点,也有缺点,这里主要说一下缺点:

  1. 编码繁琐,效率低(指的是编码效率低,重复代码多)。
  2. 数据库连接的创建和释放比较重复,也造成了系统资源的浪费
  3. 大量硬编码,缺乏灵活性,不利于后期维护
  4. 参数的赋值和数据的封装全是手动进行

... ...

可能你还可以再列出一些JDBC的缺点,如果你已经很久没有使用过JDBC了,印象已经不深刻了,那么相信下面的代码能勾引起你的些许回忆。

public List<Book> findAll() {
   Connection connection = null;
   PreparedStatement preparedStatement = null;
   ResultSet resultSet = null;
   List<Book> bookList = null;
   
   try {
      //加载数据库驱动
      Class.forName("com.mysql.jdbc.Driver");
      //通过驱动管理类获取数据库链接
      connection =  DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123");
      //定义sql语句   ?表示占位符
      String sql = "select * from t_book where author = ?";
      //获取预处理statement
      preparedStatement = connection.prepareStatement(sql);
      //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
      preparedStatement.setString(1, "张三");
      //向数据库发出sql执行查询,查询出结果集
      resultSet =  preparedStatement.executeQuery();
      //遍历查询结果集
      bookList = new ArrayList<>();
      while(resultSet.next()){
         Book book=new Book();
         book.setId(resultSet.getInt("id"));
         book.setName(resultSet.getString("bname"));
         book.setAuthor(resultSet.getString("author"));
         book.setPrice(resultSet.getDouble("price"));
         bookList.add(book);
      }
      return bookList;
   } catch (Exception e) {
      e.printStackTrace();
      return null;
   }finally{
      //释放资源
      if(resultSet!=null){
         try {
            resultSet.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(preparedStatement!=null){
         try {
            preparedStatement.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(connection!=null){
         try {
            connection.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
   }
}

正是因为JDBC存在着各种问题,所以才导致很多持久层框架应运而生,例如:Hibernate和MyBatis,这两个都是目前比较流行的持久层框架,都对JDBC进行了更高级的封装和优化,相信大家对这两个框架都比较熟悉。

很多程序员其实都亲自尝试过自己对JDBC进行封装和优化,设计并编写过一些API,每个程序员在做这个事情时,可能设计以及实现的思想都是不一样的,这些思想各有特点,各有千秋,可以分为两大类:

第一类:着重对JDBC进行API层的抽取和封装,以及功能的增强,典型代表是Apache的DbUtils。

程序员在使用DbUtils时仍然需要编写sql语句并手动进行数据封装,但是API的使用比JDBC方便了很多,下面是使用DbUtils的代码片段:

@Test
public void testQuery(){
   //创建queryRunner对象,用来操作sql语句
   QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
   //编写sql语句 
   String sql = "select * from user";
   //执行sql语句
   try {
        List<User> list = qr.query(sql, new BeanListHandler<User>(User.class));
        System.out.println(list);
   } catch (SQLException e) {
        e.printStackTrace();
   }
}

第二类:借鉴面向对象的思想,让程序员以操作对象的方式操作数据库,无需编写sql语句,典型代表是ORM。

ORM(Object Relational Mapping)吸收了面向对象的思想,把对sql的操作转换为对象的操作,从而让程序员使用起来更加方便和易于接受。这种转换是通过对象和表之间的元数据映射实现的,这是实现ORM的关键,如下图所示:

       由于类和表之间以及属性和字段之间建立起了映射关系,所以,通过sql对表的操作就可以转换为对象的操作,程序员从此无需编写sql语句,由框架根据映射关系自动生成,这就是ORM思想。

       目前比较流行的Hibernate和MyBatis都采用了ORM思想,一般我们把Hibernate称之为全自动的ORM框架,把MyBatis称之为半自动的ORM框架。使用过这两个框架的程序员,对于ORM一定不会陌生。同时,ORM也是JPA(SUN推出的持久层规范)的核心内容,如下图所示:

2、ORM的经典应用:Hibernate

       Hibernate就是应用ORM思想建立的一个框架,一般我们把它称之为全自动的ORM框架,程序员在使用Hibernate时几乎不用编写sql语句,而是通过操作对象即可完成对数据库的增删改查。

2.1 Hibernate案例

通过Hibernate框架让大家对ORM思想有一个更加深入的理解,接下来我们从一个案例开始:

2.1.1 pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zdw.orm</groupId>
    <artifactId>HibernateDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.3.6.Final</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource> <!--为了编译时能加载包中的xml文件-->
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource> <!--为了编译时能加载resources中的xml文件-->
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

2.1.2 核心配置文件hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123</property>

        <property name="hibernate.hbm2ddl.auto">update</property><!--自动建表,如果存在就更新-->
        <property name="hibernate.show_sql">true</property><!--控制台打印sql语句-->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property><!--配置方言,因为Hibernate要根据对象生成sql语句,数据库不同,语法有区别,所以要告诉Hibernate框架是什么数据库-->

        <mapping class="com.zdw.orm.entity.Book"/> <!--配置带有映射注解的实体类的路径-->
        <mapping resource="com/zdw/orm/entity/Book.hbm.xml"/><!--配置映射文件-->
    </session-factory>
</hibernate-configuration>

该配置文件主要设置了数据库连接信息和映射配置文件的位置信息

2.1.3 实体类Book

package com.zdw.orm.entity;

/**
 * Create By zdw on 2019/7/16
 */
public class Book {
    private Integer id;
    private String name;
    private String author;
    private Double price;

    //get/set方法和toString方法
}

2.1.4 映射配置文件Book.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.zdw.orm.entity.Book" table="t_book">
        <id name="id" column="bid">
            <generator class="identity"/> <!--主键的值采用自增方式-->
        </id>
        <property name="name" column="bname"></property>
        <property name="author" column="author"></property>
        <property name="price" column="price"></property>
    </class>
</hibernate-mapping>

       该配置文件非常关键,重点体现了ORM思想,类和表之间,属性和字段之间的映射关系清晰明了,当然现在也非常流行注解的方式,就是把映射关系以注解的方式放在实体类中,如下所示:

package com.zdw.orm.entity;


import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;

/**
 * Create By zdw on 2019/7/16
 */
@Entity
@Table(name = "t_book")
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "bid")
    private Integer id;
    @Column(name = "bname")
    private String name;
    @Column(name = "author")
    private String author;
    @Column(name = "price")
    private Double price;
    //get/set方法
}

不管你用xml配置的方式,还是注解的方式,其本质都是一样的,都是为了通过ORM思想把类和表之间,属性和字段之间的映射关系设置好。

2.1.5 测试类

package com.zdw.orm.test;

import com.zdw.orm.entity.Book;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Before;
import org.junit.Test;

/**
 * Create By zdw on 2019/7/16
 */
public class HibernateTest {
    private SessionFactory factory;

    @Before
    public void init(){
        //创建一个Configuration对象,它会读取核心配置文件hibernate.cfg.xml的信息
        Configuration configuration = new Configuration().configure();
        //创建SessionFactory对象,它会解析映射文件Xxx.hbm.xml,或者是解析实体类中的注解,并生成相关的sql语句
        factory=configuration.buildSessionFactory();
    }

    @Test
    public void testSave(){
        Session session = factory.openSession();//得到核心类
        Transaction transaction = session.beginTransaction();//开启事务
        //创建保存的数据对象
        Book book = new Book();
        book.setName("小孩");
        book.setAuthor("大冰");
        book.setPrice(29.8);

        session.save(book);
        transaction.commit();//提交事务
        session.close();//释放资源
    }
}

注意事项:当我们执行上面的测试方法,会发现报错如下:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'type=MyISAM' at line 1
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)

问题的关键是:'type=MyISAM'  这种写法,老版本mysql使用type=MyISAM,mysql5.1以后使用engine=MyISAM,所以我们这里就报错了。

解决办法:

我们在核心配置文件hibernate.cfg.xml中配置数据库方言的时候,配置的值修成如下的就可以了:

<!-- <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>--><!--上面的这种方式是5.1之前的配置-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property><!--配置方言,因为Hibernate要根据对象生成sql语句,数据库不同,语法有区别,所以要告诉Hibernate框架是什么数据库-->
       

然后再次启动,成功不报错,数据库中也创建成功了表t_book,同时插入了一条数据,控制台也打印了建表语句和插入语句:

Hibernate: create table t_book (bid integer not null auto_increment, bname varchar(255), author varchar(255), price double precision, primary key (bid)) engine=MyISAM
Hibernate: insert into t_book (bname, author, price) values (?, ?, ?)

 

2.1.6 测试查询和删除

上面的测试类我们测试了插入的方法,下面测试查询和删除的方法

//测试根据id查询
    @Test
    public void testGet(){
        Session session = factory.openSession();
        Book book = session.get(Book.class, 1);//第一个参数的返回值的类型,第二个参数的主键值
        System.out.println(book);
        session.close();
    }
    //测试删除
    @Test
    public void testDelete(){
        Session session = factory.openSession();
        Transaction transaction = session.beginTransaction();
        Book book = new Book();
        book.setId(1);
        session.delete(book);
        transaction.commit();
        session.close();
    }

该测试类使用Hibernate的API实现了图书的添加,查询和删除功能,程序员无需编写sql语句,只需要像平时一样操作对象即可,然后由Hibernate框架自动生成sql语句。

 

2.2 Hibernate的ORM实现原理

接下来我们通过上述案例来讲解一下Hibernate框架是如何应用ORM思想的,一起剖析一下Hibernate的内部实现原理。

其实不管使用什么框架,最终都需要生成sql语句,因为数据库需要的就是sql语句,而我们在使用Hibernate编码时没有编写sql语句,只是提供了对象,那么Hibernate是如何根据对象生成sql语句的呢?接下来我们一起跟踪并分析一下Hibernate 5.x的源码。

1、Configuration cfg = new Configuration().configure();

1.1 在new Configuration()时,进行了hibernate的环境初始化工作,相关对象和容器被创建了出来,如下图所示:

红框中的两个对象大家要尤为注意,一个是第158行的StandardServiceRegistryBuilder,一个是第161行的Properties对象,后面的源码中会重点用到这两个对象。 

1.2 接下来跟踪调用configure()方法,

如下图所示:

configure(...)方法会默认加载名字为hibernate.cfg.xml的配置文件,然后去解析该配置文件并把解析到的数据存放到一个Properties中。解析出来的数据如下图所示:

通过上图大家能很清晰得看到,hibernate.cfg.xml中的信息被解析出来并存到了一个Properties中。

       总之,第一步Configuration cfg = new Configuration().configure(); 已经把hibernate.cfg.xml中的信息全部解析了出来并进行了存储。

 

2、SessionFactory factory = cfg.buildSessionFactory();

由Configuration对象的buildSessionFactory(...)方法创建会话工厂(SessionFactory),该方法的代码非常多,这里只截取了部分关键代码:

  1. 第723行代码从Properties中得到配置文件中的数据
  2. 第653行和第689行分别创建 MetadataBuilder对象并调用该对象的build()方法解析了映射信息,然后存储到 Metadata对象中,下列截图展示了从映射配置文件或实体类中解析出来的映射数据,包含哪个类和哪个表对应,哪个属性和哪个字段对应

  1. 第708行调用SessionFatctoryBuilder对象的build()方法生成sql语句并返回 SessionFactory 实例,下面截图展示出了生成的sql语句:

       会话工厂(SessionFactory)在Hibernate中实际上起到了一个缓冲区的作用,它缓存了Hibernate自动生成的SQL语句和相关映射数据,只要生成了sql语句,那么后面实现增删改查就不在话下。

3、ORM的经典应用:MyBatis

       MyBatis框架也应用了ORM思想,一般我们把它称之为半自动的ORM框架,跟Hibernate相比,MyBatis更加轻量,更加灵活,为了保证这一点,程序员在使用MyBatis时需要自己编写sql语句,但是API的使用依然像Hibernate一样简单方便。

3.1 MyBatis案例

通过MyBatis框架让大家对ORM思想有一个更加深入的理解,接下来我们从一个案例开始:

准备工作,数据库test创建两张表,员工表和部门表:

CREATE TABLE t_dept(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(100)
)ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE t_emp(
    id INT(11) PRIMARY KEY AUTO_INCREMENT,
    NAME VARCHAR(50),
    age INT(11),
    salary DOUBLE,
    dept_id INT(11),
    CONSTRAINT emp_dept_id FOREIGN KEY (dept_id) REFERENCES t_dept(id) ON DELETE CASCADE ON UPDATE CASCADE 
)ENGINE=INNODB DEFAULT CHARSET=utf8;

insert into t_dept values (null,'电子'),(null,'财务');

insert into t_emp values (null,'小米',23,20000.00,1),(null,'小丽',22,8900.00,2),(null,'小王',23,30000.00,1),(null,'小雨',28,9000.00,2),(null,'小钰',25,12000.00,1),(null,'小周',21,9900.00,2);

3.1.1 pomxml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zdw.orm</groupId>
    <artifactId>MyBatisDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes> <!--为了编译时能加载包中的xml文件-->
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

3.1.2 MyBatis的核心配置文件SqlMapConfig.xml

<?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.zdw.orm.entity"></package>
    </typeAliases>
    <!--配置数据库连接环境-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
    <!--加载mapper文件-->
    <mappers>
        <package name="com.zdw.orm.mapper"/>
    </mappers>
</configuration>

该配置文件主要设置了数据库连接信息和映射配置文件的位置信息

2.1.3 实体类

package com.zdw.orm.entity;

/**
 * Create By zdw on 2019/7/16
 * // 实体类:存储各部门的员工总数
 */
public class DeptEmp {
    private String deptName;  //部门名称
    private int total;  //员工总数

    //get/set方法和toString方法
}

这就是一个普通的实体类,用来封装和存储查询出来的数据

2.1.4 Mapper接口

package com.zdw.orm.mapper;

import com.zdw.orm.entity.DeptEmp;

import java.util.List;

/**
 * Create By zdw on 2019/7/16
 * mapper接口
 */
public interface DeptEmpMapper {
    //查询各部门的员工总数
    List<DeptEmp> getEmpTotalByDept();
}

这是Mapper接口,我们只需要定义方法,由MyBatis框架创建代理类(实现类)并实现功能

2.1.5 映射配置文件DeptEmpMapper.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.zdw.orm.mapper.DeptEmpMapper">
    <!--自定义resultMap配置实体类和结果集之间的映射关系  type="deptEmp" 使用的是实体类别名,不区分大小写-->
    <resultMap id="dept_emp_result_map" type="deptEmp">
        <result property="deptName" column="dname"/>
        <result property="total" column="total"/>
    </resultMap>

    <!--定义查询语句 id="getEmpTotalByDept"要和DeptEmpMapper文件的中定义的方法名相同-->
    <select id="getEmpTotalByDept" resultMap="dept_emp_result_map">
         SELECT t_dept.name dname, COUNT(*) AS total FROM t_dept,t_emp WHERE t_emp.dept_id=t_dept.id GROUP BY dname
    </select>

</mapper>

       MyBatis其实会采用默认规则自动建立表和实体类之间,属性和字段之间的映射关系,但是由于我们这个案例中是要查询各部门的员工总数,无法自动映射,所以我们自定义了一个resultMap来配置实体类和查询结果之间的映射关系,想通过这个案例更加明显的体现一下ORM思想。

2.1.6 测试类

package com.zdw.orm.test;

import com.zdw.orm.entity.DeptEmp;
import com.zdw.orm.mapper.DeptEmpMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import javax.sound.midi.Soundbank;
import java.io.InputStream;
import java.util.List;


/**
 * Create By zdw on 2019/7/16
 */
public class MyBatisTest {

    private SqlSessionFactory sqlSessionFactory = null;

    @Before
    public void init() throws Exception{
        //创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 加载配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory对象
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void test(){
        //得到SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //得到Mapper代理对象
        DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);
        //调用mapper接口的方法查询部门的总人数
        List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept();
        for(DeptEmp deptEmp:list){
            System.out.println(deptEmp);
        }
        sqlSession.close();
    }

}

该测试类通过MyBatis的API实现了查询功能,如下图所示:

3.2 MyBatis的ORM实现原理

接下来我们通过上述案例来讲解一下MyBatis框架是如何应用ORM思想的,一起剖析一下MyBatis 3.x的内部实现原理。

3.2.1 解析MyBatis核心配置文件并创建SqlSessionFactory对象

//创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory对象
sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

这三行代码先是创建了SqlSessionFactoryBuilder 对象,然后获得了MyBatis核心配置文件的输入流,最后调用了build方法,我们接下来跟踪一下该方法的源码。

第69行代码创建了一个xml解析器对象,并在第70行对MyBatis核心配置文件进行了解析,拿到了数据库连接数据以及映射配置文件中的数据(包括我们编写的sql语句和自定义的resultMap),如下图所示:

第88行代码创建了DefaultSqlSessionFactory对象,并把上图中展示的解析后拿到的数据传给了它,我们继续跟踪。

       在DefaultSqlSessionFactory的构造方法中,我们发现,解析后拿到的数据最终被全部存入到了一个名字为Configuration的对象中,后续很多操作都会从该对象中获取数据。

3.2.2 SqlSession sqlSession = sqlSessionFactory.openSession();

图中红框部分代码,主要通过读取Configuration对象中的数据分别创建了Environment对象,事务对象,Executor对象等,并最终直接new了一个DefaultSalSession对象(SqlSession接口的实现类),该对象是MyBatis API的核心对象。

 

3.2.3 DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);

第269行代码调用了Configuration对象的getMapper方法,把实体类和sqlSession对象传了过去。

第693行代码调用了MapperRegistry对象的getMapper方法,把实体类和sqlSession对象传了过去。

第34行代码通过代理工厂最终把我们自定义的Mapper接口的代理对象创建了出来。

3.2.4 List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept();

当我们调用代理对象的getEmpTotalByDept()方法时,框架内部会调用MapperProxy的invoke方法, 我们可以观察一下这个方法三个参数的值,如下图所示:

在invoke方法内锁定第36行代码,该行代码调用MapperMethod的execute方法实现查询功能。

       在execute方法内先进行增删改查判断,本案例进行的是查询,并且可能会查询出多条记录,所以最后锁定第60行代码,该行代码调用executeForMany方法进行查询。

第124行代码通过hasRowBounds()方法判断是否进行分页查询,本案例不需要,所以执行else中的代码,调用sqlSession的selectList方法(第128行), 参数的值如下图所示:

第123行代码从Configuration对象中获得MappedStatement对象,该对象存储了映射配置文件中的所有信息(包括我们编写的sql语句和自定义的resultMap),如下图所示:

第124行代码调用了CachingExecutor的query(...)方法, 继续往下跟踪。

第61行通过ms对象从mapper映射配置文件中获得了sql语句,如下图所示:

第63行代码调用了下面的query方法,该方法内部先从缓存中取数据,如果缓存中没数据,就重新查询(第87行),继续往下跟踪。

第134行代码通过调用queryFromDatabase方法最终执行了sql语句并将查询结果按照我们自定义的resultMap进行了封装,结果如下图所示:

 

以上内容参考自传智播客的教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值