MyBatis接口式编程及创建代理对象原理分析

一、面向接口开发步骤

  1. 定义代理接口,将操作数据库的方法定义在代理接口中。
  2. 在SQL 映射文件中编写SQL 语句。
  3. 将SQL 映射文件注册在MyBatis 的全局配置文件中。
  4. 编写测试代码。

二、环境准备

数据库表结构:

DROP TABLE IF EXISTS `t_employee`;
CREATE TABLE `t_employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `gender` char(1) DEFAULT NULL,
  `email` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

pom.xml中添加依赖:

        <!-- MyBatis 的依赖 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <!-- 数据库驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>
        <!-- log4j 的依赖,用于在控制台查看执行的SQL 语句 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

项目工程目录:
这里写图片描述

三、开发过程

这里就不提供log4j 相关的配置信息了,有兴趣的可以自行百度搜索。

员工类Employee

public class Employee {
    private Integer id;
    private String username;
    private Character gender;
    private String email;

    /** 省略get 、set与toString 方法 */
}
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

接口EmployeeMapper

public interface EmployeeMapper {

    /***
     *  根据id 获取Employee 的信息
     *  
     * @param id
     * @return
     */
    Employee getEmpById(Integer id);
}
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

SQL 映射文件EmployeeMapper.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">
<!--
    namespace:EmployeeMapper 接口的全路径名
    id:唯一标识,EmployeeMapper 接口对应的方法名
    resultType:返回值类型,这里返回的是Employee 对象
-->
<mapper namespace="com.jas.mybatis.mapper.EmployeeMapper">
    <select id="getEmpById" resultType="com.jas.mybatis.bean.Employee">
        <!-- #{id} 表示要传进来的参数,类似于JDBC SQL 语句中的? -->
        select * from t_employee where id = #{id}
    </select>
</mapper>
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

MyBatis 全局配置文件mybatis-config.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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-study"/>
                <property name="username" value="root"/>
                <property name="password" value="1234"/>
            </dataSource>
        </environment>
    </environments>

    <!--  注册SQL 映射文件 -->
    <mappers>
        <mapper resource="EmployeeMapper.xml"/>
    </mappers>
</configuration>
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

测试代码

public class MyBatisTest {

    @Test
    public void helloWorld() throws Exception{
        String resource = "mybatis-config.xml";
        InputStream is = Resources.getResourceAsStream(resource);
        // 创建SqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        // 创建SqlSession 对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 利用sqlSession.getMapper() 方法创建EmployeeMapper 对象
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        // 调用EmployeeMapper 中的getEmpById() 方法获取数据库信息
        Employee employee = employeeMapper.getEmpById(2);
        // 关闭sqlSession 对象
        sqlSession.close();
        System.out.println(employee);
    }

}
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

测试结果
这里写图片描述
不知道大家有没有一个疑问:EmployeeMapper不是一个接口吗,为什么可以生成一个对象呢?原因是在调用sqlSession.getMapper()方法时,底层利用JDK 的动态代理方式为EmployeeMapper接口生成了一个代理对象。在下面会进行其底层的原理分析。

关于JDK 动态代理可参考博文:http://blog.csdn.net/codejas/article/details/79218302

四、为什么MyBatis要提供面向接口的开发方式

到这里我们知道MyBatis 提供了面向接口方式编程,但是为什么要这么做呢?这里就要提及一些关于设计模式方面的知识。我们知道一个设计原则:面向抽象编程,其实面向接口编程就是面向抽象编程。面向抽象编程可以降低代码之间的耦合,使代码更加具有弹性,从而提高程序扩展性。

五、sqlSession.getMapper() 方法创建接口代理对象原理解析

这里采用DUBUG 的方式深入MyBatis 的源码内部去探索sqlSession.getMapper(EmployeeMapper.class)创建对象的过程。

1.执行sqlSession.getMapper(EmployeeMapper.class)时,调用的是DefaultSqlSession中的getMapper(Class<T> type)方法。

/** DefaultSqlSession 中的方法  */
@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

2.DefaultSqlSession中的getMapper(Class<T> type)方法调用了Configuration中的
getMapper(Class<T> type, SqlSession sqlSession)方法。

/** Configuration 中的方法 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4

3.接着调用MapperRegistry类中的getMapper(Class<T> type, SqlSession sqlSession)方法,在这个方法中创建了一个MapperProxyFactory对象,然后调用MapperProxyFactory 类的newInstance(sqlSession)方法。

/** MapperRegistry 中的方法 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

4.下面是在MapperProxyFactory 类中关于newInstance(sqlSession)方法的调用过程,在这个方法中首先创建了一个MapperProxy<T>对象。

/** MapperProxyFactory 中的方法 */
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

5.下面是创建MapperProxy<T>对象的过程,仔细看你会发现MapperProxy<T>类实现了InvocationHandler接口,这个InvocationHandler接口是Proxy的一个辅助类,用于帮助Proxy创建代理对象。

/** MapperProxy<T> 类的构造函数 */
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6.创建完MapperProxy<T>对象后,接着调用MapperProxyFactory自身类中的另一个newInstance(MapperProxy<T> mapperProxy)方法,在newInstance(MapperProxy<T> mapperProxy) 中最终完成代理类的创建。然后一步步返回,把代理对象的引用赋给employeeMapper。到这里EmployeeMapper接口的代理对象就算是创建完成了。

/** MapperProxyFactory 中的方法 */
 protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
 
 
 
 
  • 1
  • 2
  • 3
  • 4

建议大家自己动手DEBUG 一下,只有动手了才是自己的。
最终执行结果图:
这里写图片描述
调用过程时序图:
这里写图片描述

六、总结

这篇博客前半部分主要介绍了MyBatis 接口式编程的使用方法,后半部分对MyBatis 接口式编程创建接口代理对象的过程进行了深入探究。如果你对后半部分的调用过程不是很理解,大可不必先急于了解其中的过程,首先会使用就可以了。学习不可一蹴而就,慢慢积累,过了一段时间,你自然就会理解了。希望这篇博客可以为你提供帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值