课程大纲

ORM 思想

ORM 的经典运用:Hibernate 案例及实现事理

phporm和entityORM 思惟及相干框架Hibernate以及MyBatis实现道理 Angular

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&gt; 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\"大众 \"大众http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd\公众><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\公众 \公众http://mybatis.org/dtd/mybatis-3-config.dtd\公众><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\"大众 \"大众http://mybatis.org/dtd/mybatis-3-mapper.dtd\公众><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 进行了封装,结果如下图所示: