一、ORM思想
目前的市场上,通过Java语言连接并操作数据库的技术或者方式以及有很多,例如:JDBC、MyBatis、Hibernate等等。其中JDBC是Java的原生API,支持连接并操作各种关系型数据库。
JDBC作为Java原生API,有优点也有缺点,缺点主要是:
- 编码繁琐,效率低
- 数据库连接的创建和释放比较重复,也造成了系统资源的浪费
- 大量硬编码,缺乏灵活性,不利于后期维护
- 参数的复制和数据的封装全是手动进行的
那么了解这些缺点以后,我们回顾一下以前是怎么使用JDBC来连接数据库,操作数据库的。
public List<Book> findAll(){ Connection connection = null; PreparedStatement pre = null; ResultSet result = 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(); } } } }
总的流程就是:
- 加载数据库驱动
- 获取数据库连接
- 准备sql语句和获得PreparedStatement对象
- 执行查询,获得结果集
- 遍历结果集
- 关闭数据库连接
很多程序员其实都亲自尝试去对JDBC进行封装和优化,设计并编写过一些API,其中设计的思想是不一样的,主要分为两大类:
- 对JDBC进行API层的抽取和封装,以及功能的增强,经典代表是DbUtils.
- 借助面向对象的思想,以对象的方式操作数据库,无需编写sql语句,典型代表就是ORM。ORM(Object Relational Mapping)吸收了面向对象的思想,把对 sql 的操作 转换为对象的操作,从而让程序员使用起来更加方便和易于接受。这种转换是通过对象和表之间的元数据映射实现的,这是实现 ORM 的关键,如下图所示
由于类和表之间以及属性和字段之间建立起了映射关系,所以,通过 sql 对表的操作就 可以转换为对象的操作,程序员从此无需编写 sql 语句,由框架根据映射关系自动生成,这 就是 ORM 思想。
二、MyBatis的ORM实现原理
为了探究MyBatis的ORM实现原理,我们接下来通过一个案例,使用debug的方式来查看这其中运行的过程。
2.1、案例结构
2.2、entity实体类
DeptEmp
package com.choi.mybatis.entity; //实体类 public class DeptEmp { private String dname; //部门名 private Integer total; //员工总数 public String getDname() { return dname; } public void setDname(String dname) { this.dname = dname; } public Integer getTotal() { return total; } public void setTotal(Integer total) { this.total = total; } @Override public String toString() { return "DeptEmp{" + "dname='" + dname + '\'' + ", total=" + total + '}'; } }
2.3、mapper接口和xml文件
DeptEmpMapper.java
package com.choi.mybatis.mapper; import com.choi.mybatis.entity.DeptEmp; import java.util.List; public interface DeptEmpMapper { List<DeptEmp> getEmpTotalByDept(); }
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映射配置文件--> <mapper namespace="com.choi.mybatis.mapper.DeptEmpMapper"> <!--自定义一个resultMap配置实体类和结果集之间的映射关系--> <resultMap id="dept_emp_result_map" type="deptEmp"> <result property="dname" 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>
2.4、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.choi.mybatis.entity"/> </typeAliases> <environments default="MySQLDevelopment"> <environment id="MySQLDevelopment"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/study?characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="6680124"/> </dataSource> </environment> </environments> <mappers> <package name="com.choi.mybatis.mapper"/> </mappers> </configuration>
2.5、测试类
DeptEmpTest.java
import com.choi.mybatis.entity.DeptEmp; import com.choi.mybatis.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.Test; import java.io.InputStream; import java.util.List; public class DeptEmpTest { @Test public void testGetEmpTotalByDept() throws Exception{ //1. 加载核心配置文件 SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder(); InputStream inputStream= Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 解析核心配置文件并创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream); //3. 创建核心对象 SqlSession sqlSession=sqlSessionFactory.openSession(); //4. 得到Mapper代理对象 DeptEmpMapper deptEmpMapper=sqlSession.getMapper(DeptEmpMapper.class); //5. 调用自定义的方法实现查询功能 List<DeptEmp> list= deptEmpMapper.getEmpTotalByDept(); for(DeptEmp de:list){ System.out.println(de); } //6. 关闭sqlSession sqlSession.close(); } }
2.6、pom文件
<?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.choi.mybatis</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> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build> </project>
注意:pom文件中必须引入这个<build>标签中的内容,否则mapper.xml文件将不会被编译,导致项目启动的时候找不到对应的xml文件。如下图所示</build>
2.7、运行结果
2.8、MyBatis的ORM实现原理
接下来,我们使用debug的方式来运行,看一下这其中运行的原理
- 解析MyBatis核心配置文件并创建出一个SqlSessionFactory对象
//1. 加载核心配置文件 SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder(); InputStream inputStream= Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 解析核心配置文件并创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory=sqlSessionFactoryBuilder.build(inputStream);
通过断点调试,在第69行的时候创建了一个xml解析器对象,并在第70行对MyBatis核心配置文件进行了解析,拿到了数据库连接数据和银蛇配置文件中的数据(包括我们编写的sql语句和自定义的resultMap),如下所示
在88行代码中创建了DefaultSqlSessionFactory对象,并把上图中展示的解析数据传给它,继续跟踪
- 调用sqlSessionFactory的openSession方法创建sqlSession
//3. 创建核心对象 SqlSession sqlSession=sqlSessionFactory.openSession();
图中71行-75行代码,主要是通过读取Configuration对象中的数据分别创建了Environment对象,事务对象,Executor对象,最终直接new了一个DefaultSqlSession对象(SqlSession接口的实现类),该对象是MyBatis对象的核心对象。
- 调用getMapper方法获取代理对象
//4. 得到Mapper代理对象 DeptEmpMapper deptEmpMapper=sqlSession.getMapper(DeptEmpMapper.class);
通过第34行代码中的代理工厂最终把我们自定义的Mapper接口的代理对象创建出来
- 调用代理对象中的方法执行查询
//5. 调用自定义的方法实现查询功能 List<DeptEmp> list= deptEmpMapper.getEmpTotalByDept();
当我们调用代理对象的getEmpTotalByDept方法时,框架内部会调用MapperProxy的invoke方法,我们可以观察这个方法的三个参数值:
在invoke方法的内部,调用了execute方法执行查询
在execute方法内部先进行增删查改的判断,本次案例是查询,并且可能查出多条记录,所以走的是60行代码,进入该行代码
第124行代码是判断是否进行分页查询,本案例中不需要,所以执行的是else中的代码
继续跟踪,发现123行代码从Configuration对象中,获取了MappedStatement对象,该对象存储的是映射配置文件中的所有信息,如下图所示
继续往下跟踪
最后发现,在134行代码中调用了queryFromDataBase方法最终执行了sql语句查询,并将查询结构按照我们自定义的resultMap进行封装,如下图所示
总结:MyBatis工作原理
- 读取核心配置文件并返回InputStream流对象。
- 根据InputStream流对象解析出Configuration对象,然后创建SqlSessionFactory工厂对象
- 根据一系列属性从SqlSessionFactory工厂中创建SqlSession
- 从SqlSession中调用Executor执行数据库操作&&生成具体SQL指令
- 对执行结果进行二次封装
- 提交与事务
扩展阅读