1、ORM思想(Object Relational Mapping)
目前,通过Java语言连接并操作数据库的技术或方式已经有很多了,例如:JDBC,Hibernate,MyBatis,TopLink等等。其中JDBC是Java原生的API,支持连接并操作各种关系型数据库。相信每个程序员都是从JDBC开始学起的,然后才接触到各种持久层框架。
JDBC作为Java原生API,有优点,也有缺点,这里主要说一下缺点:
- 编码繁琐,效率低(指的是编码效率低,重复代码多)。
- 数据库连接的创建和释放比较重复,也造成了系统资源的浪费
- 大量硬编码,缺乏灵活性,不利于后期维护
- 参数的赋值和数据的封装全是手动进行
... ...
可能你还可以再列出一些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),该方法的代码非常多,这里只截取了部分关键代码:
- 第723行代码从Properties中得到配置文件中的数据
- 第653行和第689行分别创建 MetadataBuilder对象并调用该对象的build()方法解析了映射信息,然后存储到 Metadata对象中,下列截图展示了从映射配置文件或实体类中解析出来的映射数据,包含哪个类和哪个表对应,哪个属性和哪个字段对应
- 第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进行了封装,结果如下图所示:
以上内容参考自传智播客的教程