MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例,深入代码,来探究MyBatis的实现。
一、MyBatis的框架设计
上述使用MyBatis的方法,是创建一个和数据库打交道的SqlSession对象,然后根据Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis为了适应这一趋势,增加了第二种使用MyBatis支持接口(Interface)调用方式。
1.2. 使用Mapper接口
MyBatis将配置文件中的每一个<mapper>节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟<mapper>节点中的<select|update|delete|insert>节点项对应,即<select|update|delete|insert>节点的id值为Mapper接口中的方法名称,parameterType值表示Mapper对应方法的入参类型,而resultMap值则对应了Mapper接口表示的返回值类型或者返回结果集的元素类型。
二、MyBatis的主要构件及其相互关系
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
- SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
- Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
- ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
- ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
- MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
- SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
- BoundSql 表示动态生成的SQL语句以及相应的参数信息
- Configuration MyBatis所有的配置信息都维持在Configuration对象之中。
(注:这里只是列出了我个人认为属于核心的部件,请读者不要先入为主,认为MyBatis就只有这些部件哦!每个人对MyBatis的理解不同,分析出的结果自然会有所不同,欢迎读者提出质疑和不同的意见,我们共同探讨~)
它们的关系如下图所示:
三、从MyBatis一次select 查询语句来分析MyBatis的架构设计
一、数据准备(非常熟悉和应用过MyBatis 的读者可以迅速浏览此节即可)
1. 准备数据库数据,创建EMPLOYEES表,插入数据:
[sql] view plain copy
- --创建一个员工基本信息表
- create table "EMPLOYEES"(
- "EMPLOYEE_ID" NUMBER(6) not null,
- "FIRST_NAME" VARCHAR2(20),
- "LAST_NAME" VARCHAR2(25) not null,
- "EMAIL" VARCHAR2(25) not null unique,
- "SALARY" NUMBER(8,2),
- constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
- );
- on table EMPLOYEES is '员工信息表';
- on column EMPLOYEES.EMPLOYEE_ID is '员工id';
- on column EMPLOYEES.FIRST_NAME is 'first name';
- on column EMPLOYEES.LAST_NAME is 'last name';
- on column EMPLOYEES.EMAIL is 'email address';
- on column EMPLOYEES.SALARY is 'salary';
- --添加数据
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (100, 'Steven', 'King', 'SKING', 24000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
[sql] view plain copy
- --创建一个员工基本信息表
- create table "EMPLOYEES"(
- "EMPLOYEE_ID" NUMBER(6) not null,
- "FIRST_NAME" VARCHAR2(20),
- "LAST_NAME" VARCHAR2(25) not null,
- "EMAIL" VARCHAR2(25) not null unique,
- "SALARY" NUMBER(8,2),
- constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
- );
- on table EMPLOYEES is '员工信息表';
- on column EMPLOYEES.EMPLOYEE_ID is '员工id';
- on column EMPLOYEES.FIRST_NAME is 'first name';
- on column EMPLOYEES.LAST_NAME is 'last name';
- on column EMPLOYEES.EMAIL is 'email address';
- on column EMPLOYEES.SALARY is 'salary';
- --添加数据
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (100, 'Steven', 'King', 'SKING', 24000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
- insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
2. 配置Mybatis的配置文件,命名为mybatisConfig.xml:
[html] view plain copy
- <?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>
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC" />
- <dataSource type="POOLED">
- <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
- <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
- <property name="username" value="louis" />
- <property name="password" value="123456" />
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
- </mappers>
- </configuration>
[html] view plain copy
- <?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>
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC" />
- <dataSource type="POOLED">
- <property name="driver" value="oracle.jdbc.driver.OracleDriver" />
- <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />
- <property name="username" value="louis" />
- <property name="password" value="123456" />
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
- </mappers>
- </configuration>
3. 创建Employee实体Bean 以及配置Mapper配置文件
[java] view plain copy
- package com.louis.mybatis.model;
- import java.math.BigDecimal;
- public class Employee {
- private Integer employeeId;
- private String firstName;
- private String lastName;
- private String email;
- private BigDecimal salary;
- public Integer getEmployeeId() {
- return employeeId;
- }
- public void setEmployeeId(Integer employeeId) {
- this.employeeId = employeeId;
- }
- public String getFirstName() {
- return firstName;
- }
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
- public String getLastName() {
- return lastName;
- }
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
- public String getEmail() {
- return email;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public BigDecimal getSalary() {
- return salary;
- }
- public void setSalary(BigDecimal salary) {
- this.salary = salary;
- }
- }
[java] view plain copy
- package com.louis.mybatis.model;
- import java.math.BigDecimal;
- public class Employee {
- private Integer employeeId;
- private String firstName;
- private String lastName;
- private String email;
- private BigDecimal salary;
- public Integer getEmployeeId() {
- return employeeId;
- }
- public void setEmployeeId(Integer employeeId) {
- this.employeeId = employeeId;
- }
- public String getFirstName() {
- return firstName;
- }
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
- public String getLastName() {
- return lastName;
- }
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
- public String getEmail() {
- return email;
- }
- public void setEmail(String email) {
- this.email = email;
- }
- public BigDecimal getSalary() {
- return salary;
- }
- public void setSalary(BigDecimal salary) {
- this.salary = salary;
- }
- }
[html] view plain copy
- <?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.louis.mybatis.dao.EmployeesMapper" >
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
- <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
- <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
- <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
- <result column="EMAIL" property="email" jdbcType="VARCHAR" />
- <result column="SALARY" property="salary" jdbcType="DECIMAL" />
- </resultMap>
- <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
- select
- EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
- from LOUIS.EMPLOYEES
- EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
- </select>
- </mapper>
[html] view plain copy
- <?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.louis.mybatis.dao.EmployeesMapper" >
- <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
- <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
- <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
- <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
- <result column="EMAIL" property="email" jdbcType="VARCHAR" />
- <result column="SALARY" property="salary" jdbcType="DECIMAL" />
- </resultMap>
- <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
- select
- EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
- from LOUIS.EMPLOYEES
- EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
- </select>
- </mapper>
4. 创建eclipse 或者myeclipse 的maven项目,maven配置如下:
[html] view plain copy
- <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>batis</groupId>
- <artifactId>batis</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>batis</name>
- <url>http://maven.apache.org</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.2.7</version>
- </dependency>
- <dependency>
- <groupId>com.oracle</groupId>
- <artifactId>ojdbc14</artifactId>
- <version>10.2.0.4.0</version>
- </dependency>
- </dependencies>
- </project>
[html] view plain copy
- <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>batis</groupId>
- <artifactId>batis</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>batis</name>
- <url>http://maven.apache.org</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <version>3.2.7</version>
- </dependency>
- <dependency>
- <groupId>com.oracle</groupId>
- <artifactId>ojdbc14</artifactId>
- <version>10.2.0.4.0</version>
- </dependency>
- </dependencies>
- </project>
5. 客户端代码:
[java] view plain copy
- package com.louis.mybatis.test;
- import java.io.InputStream;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- 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 com.louis.mybatis.model.Employee;
- /**
- * SqlSession 简单查询演示类
- * @author louluan
- */
- public class SelectDemo {
- public static void main(String[] args) throws Exception {
- /*
- * 1.加载mybatis的配置文件,初始化mybatis,创建出SqlSessionFactory,是创建SqlSession的工厂
- * 这里只是为了演示的需要,SqlSessionFactory临时创建出来,在实际的使用中,SqlSessionFactory只需要创建一次,当作单例来使用
- */
- "mybatisConfig.xml");
- new SqlSessionFactoryBuilder();
- SqlSessionFactory factory = builder.build(inputStream);
- //2. 从SqlSession工厂 SqlSessionFactory中创建一个SqlSession,进行数据库操作
- SqlSession sqlSession = factory.openSession();
- //3.使用SqlSession查询
- new HashMap<String,Object>();
- "min_salary",10000);
- //a.查询工资低于10000的员工
- "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
- //b.未传最低工资,查所有员工
- "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
- "薪资低于10000的员工数:"+result.size());
- //~output : 查询到的数据总数:5
- "所有员工数: "+result1.size());
- //~output : 所有员工数: 8
- }
- }
[java] view plain copy
- package com.louis.mybatis.test;
- import java.io.InputStream;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- 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 com.louis.mybatis.model.Employee;
- /**
- * SqlSession 简单查询演示类
- * @author louluan
- */
- public class SelectDemo {
- public static void main(String[] args) throws Exception {
- /*
- * 1.加载mybatis的配置文件,初始化mybatis,创建出SqlSessionFactory,是创建SqlSession的工厂
- * 这里只是为了演示的需要,SqlSessionFactory临时创建出来,在实际的使用中,SqlSessionFactory只需要创建一次,当作单例来使用
- */
- "mybatisConfig.xml");
- new SqlSessionFactoryBuilder();
- SqlSessionFactory factory = builder.build(inputStream);
- //2. 从SqlSession工厂 SqlSessionFactory中创建一个SqlSession,进行数据库操作
- SqlSession sqlSession = factory.openSession();
- //3.使用SqlSession查询
- new HashMap<String,Object>();
- "min_salary",10000);
- //a.查询工资低于10000的员工
- "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
- //b.未传最低工资,查所有员工
- "com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
- "薪资低于10000的员工数:"+result.size());
- //~output : 查询到的数据总数:5
- "所有员工数: "+result1.size());
- //~output : 所有员工数: 8
- }
- }
二、SqlSession 的工作过程分析:
1. 开启一个数据库访问会话---创建SqlSession对象:
[java] view plain copy
- SqlSession sqlSession = factory.openSession();
[java] view plain copy
- SqlSession sqlSession = factory.openSession();
MyBatis封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSession对象中。