APP下载
报价宝  ›  科技  › 

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

报价宝 来源:baojiabao.com 发布时间:2019-10-13 11:51:00 11月15日更新
报价宝综合消息ORM 思想及相关框架(Hibernate以及MyBatis)实现原理

该课程要求程序员必须已经基本掌握 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 findAll() { Connection connection = null;

PreparedStatement preparedStatement = null; ResultSet resultSet = null;

List 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 list = qr.query(sql, new BeanListHandler(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

cn.itcast.orm

HibernateDemo

1.0-SNAPSHOT

org.hibernate

hibernate-core

5.3.6.Final

junit

junit

4.12

mysql

mysql-connector-java

5.1.36

src/main/java

**/*.xml

false

src/main/resources

**/*.xml

false

2.Hibernate 核心配置档案 hibernate.cfg.xml

br>"-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

jdbc:mysql://localhost:3306/test

com.mysql.jdbc.Driver

root

123

update

org.hibernate.dialect.MySQLDialect

true

该配置档案主要设定了数据库连线资讯和对映配置档案的位置资讯

3.实体类 Book.java

public class Book {

private int id; //主键

private String name; //图书名字

private String author; //图书作者

private double price; //图书价格

... ...

}

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

4.对映配置档案 Book.hbm.xml

该配置档案非常关键,重点体现了 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;

@Before

public void init(){

//1. 建立一个 Configuration 物件,解析 hibernate 的核心配置档案

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

//2. 建立 SessinFactory 物件,解析对映资讯并生成基本的 sql factory=cfg.buildSessionFactory();

}

@Test

public 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();

}

@Test

public void testGet(){

//3. 得到 Session 物件,该物件具有增删改查的方法

Session session=factory.openSession();

//4. 通过 Session 物件进行查询Book book=session.get(Book.class,2); System.out.println(book);

//5. 释放资源

session.close();

}

@Test

public 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

cn.itcast.orm

MyBatisDemo

1.0-SNAPSHOT

org.mybatis

mybatis

3.4.0

mysql

mysql-connector-java

5.1.36

junit

junit

4.12

src/main/java

**/*.xml

false

src/main/resources

**/*.xml

false

2.MyBatis 核心配置档案 SqlMapConfig.xml

br>PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

该配置档案主要设定了数据库连线资讯和对映配置档案的位置资讯

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 getEmpTotalByDept();

}

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

4.对映配置档案 DeptEmpMapper.xml

br>PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select dname, count(*) as total from dept,emp where emp.dept_id=dept.did group by dname

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

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

5.测试类

public class DeptEmpTest {

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 testGetEmpTotalByDept() {

//得到 sqlSession 物件

SqlSession sqlSession = sqlSessionFactory.openSession();

//得到 Mapper 代理物件

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

//呼叫方法实现查询各部门员工总数

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

文章标签: 报价宝 降噪耳机价格 耳机价格 红米手机价格 华为手机价格 小米手机价格 电视机价格 笔记本电脑价格 笔记本价格 汽车价格 报价宝 手机价格 汽车价格 笔记本电脑价格 耳机价格