龙空技术网

ORM 思想及相关框架(Hibernate以及MyBatis)实现原理

IT技术资源共享 456

前言:

眼前你们对“orm框架的原理是什么”大概比较着重,朋友们都需要剖析一些“orm框架的原理是什么”的相关资讯。那么小编也在网上汇集了一些有关“orm框架的原理是什么””的相关知识,希望各位老铁们能喜欢,看官们快快来了解一下吧!

该课程要求程序员必须已经基本掌握 xml 解析,反射,JDBC,Hibernate,MyBatis,Maven 等相关技术

课程大纲

ORM 思想

ORM 的经典应用:Hibernate 案例及实现原理

ORM 的经典应用:MyBatis 案例及实现原理

自定义一个 ORM 框架:MiniORM

一.ORM 思想

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

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

1.编码繁琐,效率低

2.数据库连接的创建和释放比较重复,也造成了系统资源的浪费

3.大量硬编码,缺乏灵活性,不利于后期维护

4.参数的赋值和数据的封装全是手动进行

… …

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

ublic 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 = ?";//获取预处理 statementpreparedStatement = 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 的代码片段:

@Testpublic 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 推出的持久层规范)的核心内容,如下图所示:

二.ORM 的经典应用:Hibernate

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

2.1Hibernate 案例

本次课程不是为了讲解 Hibernate 框架是如何使用的,而是想通过 Hibernate 框架让大家对 ORM 思想有一个更加深入的理解,接下来我们从一个案例开始:

1.pom.xml

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

2.Hibernate 核心配置文件 hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?><!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD//EN" ""><hibernate-configuration><session-factory><property name="connection.url">jdbc:mysql://localhost:3306/test</property><property name="connection.driver_class">com.mysql.jdbc.Driver</property><property name="connection.username">root</property><property name="connection.password">123</property><property name="hbm2ddl.auto">update</property><property name="dialect">org.hibernate.dialect.MySQLDialect</property><property name="show_sql">true</property><mapping class="cn.itcast.orm.entity.Book"/> <!--带有映射注解的实体类--><mapping resource="cn/itcast/orm/entity/Book.hbm.xml"/> <!--映射配置文件--></session-factory></hibernate-configuration>

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

3.实体类 Book.java

public class Book {private int id; //主键private String name; //图书名字private String author; //图书作者private double price; //图书价格... ...}

这就是一个普通的实体类,描述和存储图书信息

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

<hibernate-mapping><class name="cn.itcast.orm.entity.Book" table="t_book"><id name="id"><column name="bid"/><generator class="identity"/> <!--主键的值采用自增方式--></id><property name="name"><column name="bname"/></property><property name="author"><column name="author"/></property><property name="price"><column name="price"/></property></class></hibernate-mapping>

该配置文件非常关键,重点体现了 ORM 思想,类和表之间,属性和字段之间的映射关

系清晰明了,当然现在也非常流行注解的方式,就是把映射关系以注解的方式放在实体类中, 如下所示:

@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; //图书价格... ...}

不管你用 xml 配置的方式,还是注解的方式,其本质都是一样的,都是为了通过 ORM

思想把类和表之间,属性和字段之间的映射关系设置好。

5.测试类

public class BookTest {private SessionFactory factory;@Beforepublic void init(){//1. 创建一个 Configuration 对象,解析 hibernate 的核心配置文件Configuration cfg=new Configuration().configure();//2. 创建 SessinFactory 对象,解析映射信息并生成基本的 sql factory=cfg.buildSessionFactory();}@Testpublic void testSave(){//3. 得到 Session 对象,该对象具有增删改查的方法Session session=factory.openSession();//4. 开启事务Transaction tx=session.beginTransaction();//5. 保存数据Book book=new Book(); book.setName("java 从入门到精通"); book.setAuthor(" 传 智 播 客 "); book.setPrice(9.9); session.save(book);//6. 提交事务tx.commit();//7. 释放资源session.close();}@Testpublic void testGet(){//3. 得到 Session 对象,该对象具有增删改查的方法Session session=factory.openSession();//4. 通过 Session 对象进行查询Book book=session.get(Book.class,2); System.out.println(book);//5. 释放资源session.close();}@Testpublic void testDelete(){//3. 得到 Session 对象,该对象具有增删改查的方法Session session=factory.openSession();//4. 开启事务管理Transaction tx=session.beginTransaction();//5. 删除数据Book book=new Book(); book.setId(2); session.delete(book);//6. 提交事务tx.commit();;//7. 释放资源session.close();}}

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

2.2Hibernate 的 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()方法,如下图所示:

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

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

Properties 中。

1.3我们再跟踪一下第 258 行的代码,这行代码调用了 StandardServiceRegistryBuilder

对象的 config 方法,如下图所示:

第 165 行从 hibernate.cfg.xml 中解析出来映射配置文件和实体类的信息,并在第 178 行进行了合并汇总,最终存储到了 aggregatedCfgXml 中,如下图所示:

总之,第一步 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 语句,那么后面实现增删改查就不在话下。

三.ORM 的经典应用:MyBatis

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

3.1MyBatis 案例

本次课程不是为了讲解 MyBatis 框架是如何使用的,而是想通过 MyBatis 框架让大家对

ORM 思想有一个更加深入的理解,接下来我们从一个案例开始:

1.pom.xml

<groupId>cn.itcast.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>

2.MyBatis 核心配置文件 SqlMapConfig.xml

<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN" ""><configuration><typeAliases> <!-- 配置实体类别名 --><package name="cn.itcast.orm.entity"/></typeAliases><environments default="development"><environment id="development"><!-- 使用 jdbc 事务管理--><transactionManager type="JDBC" /><!-- 数据库连接信息--><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><mappers> <!-- 加载 mapper 文件 --><package name="cn.itcast.orm.mapper"/></mappers>

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

3.实体类 DeptEmp.java

// 实体类:存储各部门的员工总数public class DeptEmp {private String deptName; //部门名称private int total; //员工总数... ...}

// 实体类:存储各部门的员工总数

public class DeptEmp {

private String deptName; //部门名称private int total; //员工总数

… …

}

//Mapper 接口public interface DeptEmpMapper {//查询各部门的员工总数List<DeptEmp> getEmpTotalByDept();}

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

4.映射配置文件 DeptEmpMapper.xml

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ""><mapper namespace="cn.itcast.orm.mapper.DeptEmpMapper"><!--自定义 resultMap 配置实体类和结果集之间的映射关系--><resultMap type="DeptEmp" id="dept_emp_result_map"><result property="deptName" column="dname"/><result property="total" column="total"/></resultMap><!--定义查询语句--><select id="getEmpTotalByDept" resultMap="dept_emp_result_map">select dname, count(*) as total from dept,emp where emp.dept_id=dept.did group by dname</select></mapper>

MyBatis 其实会采用默认规则自动建立表和实体类之间,属性和字段之间的映射关系,但是

由于我们这个案例中是要查询各部门的员工总数,无法自动映射,所以我们自定义了一个resultMap 来配置实体类和查询结果之间的映射关系,想通过这个案例更加明显的体现一下ORM 思想。

5.测试类

public class DeptEmpTest {private SqlSessionFactory sqlSessionFactory = null; @Beforepublic void init() throws Exception {// 创建 SQLSessionFactoryBuilder 对象SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();// 加载配置文件InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");// 创建 SQLSessionFactory 对象sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);}@Testpublic void testGetEmpTotalByDept() {//得到 sqlSession 对象SqlSession sqlSession = sqlSessionFactory.openSession();//得到 Mapper 代理对象DeptEmpMapper deptEmpMapper = sqlSession.getMapper(DeptEmpMapper.class);//调用方法实现查询各部门员工总数List<DeptEmp> list = deptEmpMapper.getEmpTotalByDept(); for (DeptEmp de : list) {System.out.println(de);}//关闭 sqlSession sqlSession.close();}}

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

3.2MyBatis 的 ORM 实现原理

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

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

InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);

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

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

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

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

2.SqlSession sqlSession = sqlSessionFactory.openSession();

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

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

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

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

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

4.List 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 进行了封装,结果如下图所示:

标签: #orm框架的原理是什么