1. 回顾 jdbcTemplate

  • 知识准备:可参考之前总结的内容 - 从 jdbc 到 jdbcTemplate

    1.1 jdbcTemplate 的设计初衷

    • jdbcTemplate 对 jdbc 的简单封装,是简化操作数据库的一种手段;
    • 传统 jdbc 的实现

      • jdbc 是一套操作所有关系型数据库的规范(接口),由数据库厂商实现接口并提供驱动 jar 包;
      • 基本操作步骤:
          1. 注册驱动: Class.forName(“com.mysql.jdbc.Driver“);【前提是提供驱动 jar 包】
          1. 获取数据库连接对象:conn = DriverManager.getConnection(“jdbc:mysql://localhost:3306/base_name”,”user”,”password”);
          1. 获取执行 sql 的对象:pstmt=conn.prepareStatement(“sql 语句”);
          1. 执行 sql:pstmt.executeUpdate() or pstmt.executeQuery();
          1. 结果处理:主要用于将 executeQuery 查询的结果集封装成 domain 对象;
          1. 释放资源:pstmt.close(); conn.close();
      • 存在的问题
        • “不变”所带来的“代码重复”问题
          • 驱动、连接对象、执行 sql 的对象、执行方法(update、query)、释放资源是“不变”的;
          • 查询结果集 resultSet 到 domain 对象的映射过程(创建 domian 对象,匹配映射字段,set)是“不变”的;
        • 可“变”参数注入所带来的“耦合”问题
          • sql 语句、查询结果集 resultSet 映射的 domain 对象是“变”的;
          • 查询结果集 resultSet 映射时需指定 domain 的对象;
          • 执行方法需传入的 sql 语句;
        • 连接对象的频繁构建与销毁所带来的资源损耗和浪费问题;

          1.2 jdbcTemplate 的设计方案

          1.2.1 jdbcTemplate 改进了什么

    • jdbcUtil:将“不变”的【注册驱动、创建连接对象、释放资源】抽象成工具包 ;

    • dataSource:针对创建与销毁连接对象所带来的资源问题用“池”的手段解决;
    • BeanRowMapper:利用 反射技术 实现查询结果集 resultSet 与 指定 domain 对象的映射过程;
      • newInstance(反射创建 domain 对象)、getFields(获取类对象的所有字段) 等;
    • JdbcTemplate:内置 BeanRowMapper 的结果映射方法,对普通执行对象 prepareStatement 的替代;

      1.2.2 jdbcTemplate 待改进的地方

    • jdbcTemplate 主要改进的是“代码重复”问题,利用的手段主要是“独立工具包”、“池”、“反射”;

    • 待改进之处主要在于“如何解耦”:执行过程中注入的 sql ,返回结果指定的 domain 类对象;

image.png

2. mybatis 的框架设计

2.1 mybatis 在 jdbcTemplate 基础上的改进

  • 由于 mybatis 是对 jdbcTemplate 的进一步封装,其应用目的依然是对数据库进行操作,所以针对 mybatis 框架,我们需要从两个方面进行了解:

    • 一是:mybatis 是如何依据 jdbc 规范完成执行数据库操作的?
    • 二是:相比 jdbcTemplate 的“降重”,mybatis 的改进重点在于“解耦”,那 ta 又是如何实现的?

      2.1.1 mybatis 如何操作数据库的

      2.1.1.1 对 mybatis 的使用案例

    1. dao 层实现:IUerDao 接口,对应的 domain 对象为 User,User 包括:id、username、address、sex、birthday 这 5 个字段; ```java package com.cyt.dao;

import com.cyt.domain.User; import java.util.List;

public interface IUserDao { // 查询所有用户 List findAll();

  1. // 查询单个用户
  2. User findUserById(Integer id);
  3. // 根据名称查找用户
  4. List<User> findByName(String name);
  5. // 查询记录总数
  6. Integer findTotal();
  7. // 保存用户
  8. void saveUser(User user);
  9. // 更新用户
  10. void updateUser(User user);
  11. // 根据 id 删除用户
  12. void deleteUser(Integer id);

}

  1. - 2. 配置 IUserDao.xml,即 IUserDao 接口对应的映射文件
  2. ```java
  3. <?xml version="1.0" encoding="UTF-8"?>
  4. <!DOCTYPE mapper
  5. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  6. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  7. <mapper namespace="com.cyt.dao.IUserDao">
  8. <select id="findAll" resultType="User">
  9. select * from user
  10. </select>
  11. <select id="findUserById" parameterType="java.lang.Integer" resultType="com.cyt.domain.User" useCache="true">
  12. select * from user where id=#{id}
  13. </select>
  14. <select id="findByName" parameterType="string" resultType="com.cyt.domain.User">
  15. select * from user where username like '%${value}%'
  16. </select>
  17. <select id="findTotal" resultType="java.lang.Integer">
  18. select count(*) from user;
  19. </select>
  20. <insert id="saveUser" parameterType="com.cyt.domain.User">
  21. <!-- 配置插入操作后,获取插入数据的 id ,其中keyProperty对应的是实体类中的名称-->
  22. <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
  23. select last_insert_id();
  24. </selectKey>
  25. <!-- 字段的顺序必须和 User 类中的类成员变量顺序一致,且 #()取值与 getXXX 一致-->
  26. insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
  27. </insert>
  28. <update id="updateUser" parameterType="com.cyt.domain.User">
  29. update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
  30. </update>
  31. <delete id="deleteUser" parameterType="java.lang.Integer">
  32. delete from user where id=#{user_id};//此处对名称无要求
  33. </delete>
  34. </mapper>
    1. 配置 SqlMapConfig.xml
      • mybatis 的主配置文件,利用其中信息可连接数据库,并找到存储 sql 语句(dao 层的映射文件)的地方;
        1. <?xml version="1.0" encoding="UTF-8"?>
        2. <!DOCTYPE configuration
        3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
        5. <configuration>
        6. <!-- 配置环境 -->
        7. <environments default="mysql">
        8. <!-- 配置 mysql 的环境-->
        9. <environment id="mysql">
        10. <!-- 配置事务的类型 -->
        11. <transactionManager type="JDBC"></transactionManager>
        12. <!-- 配置数据源(连接池)-->
        13. <dataSource type="POOLED">
        14. <!-- 配置连接数据库的 4 个基本信息-->
        15. <property name="driver" value="com.mysql.jdbc.Driver"/>
        16. <property name="url" value="jdbc:mysql://localhost:3306/cyt_mybatis"/>
        17. <property name="username" value="root"/>
        18. <property name="password" value="1234"/>
        19. </dataSource>
        20. <!-- 此处记录一个报错:property name 的顺序和内容必须如上一致,否则会报错 BuilderException:Error parsing SQL Mapper Configuration.. unknown DataSource property user-->
        21. </environment>
        22. </environments>
        23. <!-- 指定映射配置文件的位置,映射配置文件指的是每个 dao 独立的配置文件,如果使用注解来配置的话,此处应使用 class 属性指定被注解的 dao 全限定类名-->
        24. <mappers>
        25. <mapper resource="com/cyt/dao/IUserDao.xml"/>
        26. </mappers>
        27. </configuration>
    1. 测试:调用 IUserDao 接口的方法,实现简单的 CURD; ```java package com.cyt.test;

import com.cyt.domain.User; 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.After; import org.junit.Before; import org.junit.Test;

import java.io.InputStream; import java.util.Date; import java.util.List;

public class MybatisTest {

  1. private InputStream in;
  2. private SqlSession sqlSession;
  3. private IUserDao userDao;
  4. @Before
  5. public void init() throws Exception{
  6. in = Resources.getResourceAsStream("SqlMapConfig.xml");//读取 mybatis 主配置文件
  7. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  8. SqlSessionFactory factory = builder.build(in);
  9. sqlSession = factory.openSession(true);
  10. userDao = sqlSession.getMapper(IUserDao.class);
  11. }
  12. @After
  13. public void desTroy() throws Exception{
  14. sqlSession.close();
  15. in.close();
  16. }
  17. @Test
  18. public void testFindAll() throws Exception{
  19. List<User1> users = userDao.findAll();
  20. for (User1 u : users) {
  21. System.out.println(u);
  22. }
  23. }
  24. @Test
  25. public void tesFindUsersByName() throws Exception{
  26. List<User> users = userDao.findByName("陈玉婷");
  27. for (User u : users) {
  28. System.out.println(u);
  29. }
  30. }
  31. @Test
  32. public void testSaveUser() throws Exception{
  33. User user = new User();
  34. user.setUsername("陈玉婷_annotationInsert");
  35. user.setAddress("天津财经大学~~~.....................");
  36. user.setSex("女");
  37. user.setBirthday(new Date());
  38. System.out.println(user);
  39. userDao.saveUser(user);
  40. System.out.println(user);
  41. }
  42. @Test
  43. public void testUpdateUser() throws Exception{
  44. int id = 64;
  45. User user = userDao.findUserById(id);
  46. user.setAddress(user.getAddress() + "update");
  47. userDao.updateUser(user);
  48. }
  49. @Test
  50. public void testDeleteUser() throws Exception{
  51. userDao.deleteUser(65);
  52. }

}

  1. <a name="HwlLe"></a>
  2. #### 2.1.1.2 对比 jdbcTemplate,mybatis 是如何操作数据库的
  3. - 先来总结下 jdbcTemplate 操作数据库的步骤
  4. - 1. **获取连接池对象**:DataSource = JdbcPoolUtil.getDS(),其中 JdbcPollUtil 是 C3P0 数据库连接池工具类,封装了连接数据库和释放连接的基本方法;
  5. - 2. **创建 JdbcTemplate 对象**:template = new JdbcTemplate(ds);
  6. - 3. **执行 sql 语句,并获取结果**
  7. - int count = template.update(sql);//影响的行数
  8. - List<IUserDao> list template.query(sql, new BeanPropertyRowMapper<IUserDao>(IUserDao.class)); // 查询结果集映射的集合对象
  9. - 4. 释放资源,连接池会自动回收连接;
  10. - 从 **2.2.1.1 的案例** 来总结 mybatis 操作数据库的基本步骤
  11. - 1. **读取** mybatis 主配置文件 **SqlMapConfig.xml** 来获取两类信息,一类是连接数据库所需的 driver、url、name 和 password,另一类是 dao 层对应的存储 sql 语句的映射文件位置;
  12. - in = Resources.getResourceAsStream("SqlMapConfig.xml");
  13. - 2. 利用步骤1所获得的信息,通过 SqlSessionFactoryBuilder 、SqlSessionFactory **获取 sqlSession**;
  14. - SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  15. SqlSessionFactory factory = builder.build(in);<br /> ** **sqlSession = factory.openSession(true);
  16. - 3. sqlSession 本质是存储 IUserDao.xml 映射 sql 语句的“大 mapper”,方便**获取 dao 层对象**;
  17. - userDao = sqlSession.getMapper(IUserDao.class);
  18. - 4. **调用 dao 层方法**,如 List<User> users = userDao.findAll();
  19. - 5.** 释放资源**:sqlSession.close(); in.close();
  20. - 对比 jdbcTemplate,mybatis 操作数据库的步骤
  21. - **共同点-建立与释放连接**:
  22. - 从某个配置类或配置文件中获取连接对象,并且执行操作之后都要释放资源;
  23. - **不同点-获取结果的过程**:
  24. - jdbcTemplate 显式调用 sql 语句,需手动注入 sql 语句和待映射的 dao 层类对象来获取结果;
  25. - mybatis 将执行逻辑赋予更贴近上层的 dao 对象,隐藏了 jdbcTemplate 执行 sql 语句的过程;
  26. <a name="b9fSh"></a>
  27. ### 2.1.2 mybatis 如何改进 jdbc 的“过耦合”问题
  28. <a name="qczs1"></a>
  29. #### 2.1.2.1 借鉴 spring 的“解耦”思路
  30. - spring 如何“解耦”
  31. - “静态”上的设计
  32. - 通过不同的配置文件创建不同层次的“容器”,并在容器中维护bean 对象的生命周期;
  33. - 将 bean 对象保存在一张大“mapper”表中,方便查找获取;
  34. - “动态”上的实现
  35. - 解析配置文件生成上下文环境;
  36. - 通过一定的映射机制从 mapper 中动态获取 bean 对象,并实现自动装配和组合;
  37. - 小结:spring 的解耦技巧
  38. - 利用“容器”存储那些想要实现动态注入的对象;
  39. - 为了方便找到“容器”中的对象,需要提供一定的映射规则,比如:id、全限定名等;
  40. - 解析配置文件时主要是将这些“容器”中的对象保存到 mapper 中,这样在调用时能方便地获取到对象;
  41. <a name="A0Ps9"></a>
  42. #### 2.1.2.2 探寻 mybatis 的本质及“解耦”实现
  43. ![mvc1.png](https://cdn.nlark.com/yuque/0/2020/png/611598/1584002674158-0cff1480-f1f6-4ef9-a4f5-9df9e26d8d20.png#align=left&display=inline&height=340&name=mvc1.png&originHeight=340&originWidth=1361&size=50064&status=done&style=none&width=1361)
  44. - 对 mybatis 的大体认知
  45. - 从上图可以看出,mybatis 的本质是隐藏 jdbcTemplate 具体执行 sql 的过程,通过 sqlSession 这样类代理对象存储类路径下能够真正执行不同 sql 语句的唯一 template 对象,而且通过 mapper 表和一定的映射机制,实现 sql 语句和映射 domain 对象的动态注入,这样做的好处是将有关 jdbc 的底层处理逻辑向上层抽象,最终以业务逻辑的形式展现;
  46. - mybatis 的“解耦”实现
  47. - 两个容器
  48. - 一个是基础环境配置文件,包含创建数据库连接的基本信息和存储 sql 语句文件的路径信息;
  49. - 另一个是存储 sql 语句的文件,包含对应接口所有方法用到的 sql 语句,输入和返回参数信息等;
  50. - 创建代理对象 SqlSession
  51. - 通过动态注入 XXXdao 类对象,动态创建 XXXdao.xml 配置 sql 文件的 mapper 表,mapper 的 key 对对应 dao 接口方法的命名 id,value 为 sql 语句及相关的参数信息等;
  52. <a name="dK9U4"></a>
  53. ## 2.2 自定义 mybatis
  54. <a name="qbAlx"></a>
  55. ### 2.2.1 代码
  56. - 目录
  57. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/611598/1584003460004-e81bd734-a8ec-4f9d-b6fe-d06ed102d69b.png#align=left&display=inline&height=181&name=image.png&originHeight=361&originWidth=255&size=18755&status=done&style=none&width=127.5)
  58. - 注:以下按着 mybatis 操作数据库的步骤创建所需要的类
  59. - 1. 使用类加载器读取配置文件
  60. ```java
  61. public class Resources {
  62. public static InputStream getResourceAsStream(String filePath){
  63. return Resources.class.getClassLoader().getResourceAsStream(filePath);//根据传入的参数,获取一个字节输入流
  64. }
  65. }
    1. 创建能够生成 sqlSession 的相关类
        1. 1 三个 Proxy:一个调用入口类,两个接口
          1. // 用于创建一个 SqlSessionFactory 对象
          2. public class SqlSessionFactoryBuilder {
          3. public SqlSessionFactory build(InputStream config){
          4. Configuration cfg = XMLConfigBuilder.loadConfiguration(config);//根据参数的字节输入流来创建一个 SqlSessionFactory 工厂
          5. return new DefaultSqlSessionFactory(cfg);
          6. }
          7. }
          8. // 用于打开一个新的 session 对象
          9. public interface SqlSessionFactory {
          10. SqlSession openSession();
          11. }
          12. // 自定义 Mybatis 中和数据库交互的核心类,它里面可以创建 dao 接口的代理对象
          13. public interface SqlSession {
          14. <T> T getMapper(Class<T> daoInterfaceClass);//根据参数创建一个代理对象
          15. void close();
          16. }
  • 2.2 针对两个 Proxy 接口的实现类

    // SqlSessionFactory 接口的实现类
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration cfg;
    public DefaultSqlSessionFactory(Configuration cfg){
       this.cfg = cfg;
    }
    @Override
    public SqlSession openSession() {
       return new DefaultSqlSession(cfg);//用于创建一个新的操作数据库对象
    }
    }
    // SqlSession 接口的实现类
    public class DefaultSqlSession implements SqlSession {
    
    private Configuration cfg;
    private Connection connection;
    
    public DefaultSqlSession(Configuration cfg){
       this.cfg = cfg;
       connection = DataSourceUtil.getConnection(cfg);
    }
    // 用于创建代理对象,不指定返回类型的话,会报错
    public <T> T getMapper(Class<T> daoInterfaceClass) {
       return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
               new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection));
    }
    //用于释放连接资源
    public void close() {
       if (connection != null) {
           try {
               connection.close();
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
    }
    }
    
    1. 配置(SqlMapConfig.xml)和映射文件(IUserDao.xml)所对应的两个类对象
      • SqlMapConfig.xml 对应 Configuration 类,用于存储创建数据库连接的信息和映射文件的存储路径信息;
      • IUserDao.xml 对应 Mapper 类,存储该 Dao 接口中每个方法对应的 sql 语句和可能返回的结果类型; ```java // 自定义 Mybatis 的配置类,省略 getter/setter 和 toString public class Configuration { private String driver; private String url; private String username; private String password; private Map mappers = new HashMap(); } // 用于封装执行的 SQL 语句和结果类型的全限定类名,省略 getter/setter 和 toString public class Mapper { private String queryString;//SQL private String resultType;//实体类的全限定类名 }

- 4. 三个工具类
   - 4.1  用于创建数据源的工具类
```java
public class DataSourceUtil {
    // 用于获取一个数据库连接
    public static Connection getConnection(Configuration cfg){
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • 4.2 mybatis 的动态执行器 Executor,执行 SQL 语句并封装结果集,释放资源;

    public class Executor {
    // selectList() 方法负责执行 SQL 语句,并且封装结果集
    public <E> List<E> selectList(Mapper mapper, Connection conn) {
       PreparedStatement pstm = null;
       ResultSet rs = null;
       try {
           //1.取出mapper中的数据
           String queryString = mapper.getQueryString();//select * from user
           String resultType = mapper.getResultType();//com.cyt.domain.User
           Class domainClass = Class.forName(resultType);
           //2.获取PreparedStatement对象
           pstm = conn.prepareStatement(queryString);
           //3.执行SQL语句,获取结果集
           rs = pstm.executeQuery();
           //4.封装结果集
           List<E> list = new ArrayList<E>();//定义返回值
           while(rs.next()) {
               //实例化要封装的实体类对象
               E obj = (E)domainClass.newInstance();
               //取出结果集的元信息:ResultSetMetaData
               ResultSetMetaData rsmd = rs.getMetaData();
               //取出总列数
               int columnCount = rsmd.getColumnCount();
               //遍历总列数
               for (int i = 1; i <= columnCount; i++) {
                   //获取每列的名称,列名的序号是从1开始的
                   String columnName = rsmd.getColumnName(i);
                   //根据得到列名,获取每列的值
                   Object columnValue = rs.getObject(columnName);
                   //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                   PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
                   //获取它的写入方法
                   Method writeMethod = pd.getWriteMethod();
                   //把获取的列的值,给对象赋值
                   writeMethod.invoke(obj,columnValue);
               }
               //把赋好值的对象加入到集合中
               list.add(obj);
           }
           return list;
       } catch (Exception e) {
           throw new RuntimeException(e);
       } finally {
           release(pstm,rs);
       }
    }
    // release() 方法释放资源
    private void release(PreparedStatement pstm,ResultSet rs){
       if(rs != null){
           try {
               rs.close();
           }catch(Exception e){
               e.printStackTrace();
           }
       }
    
       if(pstm != null){
           try {
               pstm.close();
           }catch(Exception e){
               e.printStackTrace();
           }
       }
    }
    }
    
  • 4.3 解析配置文件的类 XMLConfigBuilder,SqlSesson 主要通过此方法创建连接,并获取 mapper;

    • 该方法针对 xml 和 annotation 有不同的创建 mapper 的方法; ```java public class XMLConfigBuilder {

    /**

    • 解析主配置文件,把里面的内容填充到 DefaultSqlSession 所需要的地方
    • 使用的技术:
    • dom4j+xpath */ public static Configuration loadConfiguration(InputStream config){ try{ //定义封装连接信息的配置对象(mybatis的配置对象) Configuration cfg = new Configuration(); //1.获取SAXReader对象 SAXReader reader = new SAXReader(); //2.根据字节输入流获取Document对象 Document document = reader.read(config); //3.获取根节点 Element root = document.getRootElement(); //4.使用xpath中选择指定节点的方式,获取所有property节点 List propertyElements = root.selectNodes(“//property”); //5.遍历节点 for(Element propertyElement : propertyElements){
      //判断节点是连接数据库的哪部分信息
      //取出name属性的值
      String name = propertyElement.attributeValue("name");
      if("driver".equals(name)){
          //表示驱动
          //获取property标签value属性的值
          String driver = propertyElement.attributeValue("value");
          cfg.setDriver(driver);
      }
      if("url".equals(name)){
          //表示连接字符串
          //获取property标签value属性的值
          String url = propertyElement.attributeValue("value");
          cfg.setUrl(url);
      }
      if("username".equals(name)){
          //表示用户名
          //获取property标签value属性的值
          String username = propertyElement.attributeValue("value");
          cfg.setUsername(username);
      }
      if("password".equals(name)){
          //表示密码
          //获取property标签value属性的值
          String password = propertyElement.attributeValue("value");
          cfg.setPassword(password);
      }
      
      } //取出mappers中的所有mapper标签,判断他们使用了resource还是class属性 List mapperElements = root.selectNodes(“//mappers/mapper”); //遍历集合 for(Element mapperElement : mapperElements){
      //判断mapperElement使用的是哪个属性
      Attribute attribute = mapperElement.attribute("resource");
      if(attribute != null){
          System.out.println("使用的是XML");
          //表示有resource属性,用的是XML
          //取出属性的值
          String mapperPath = attribute.getValue();//获取属性的值"com/cyt/dao/IUserDao.xml"
      
      // System.out.println(“mapperPath:” + mapperPath);
          //把映射配置文件的内容获取出来,封装成一个map
          Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
          //给configuration中的mappers赋值
          cfg.setMappers(mappers);
      }else{
          System.out.println("使用的是注解");
          //表示没有resource属性,用的是注解
          //获取class属性的值
          String daoClassPath = mapperElement.attributeValue("class");
          //根据daoClassPath获取封装的必要信息
          Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
          //给configuration中的mappers赋值
          cfg.setMappers(mappers);
      }
      
      } //返回Configuration return cfg; }catch(Exception e){ throw new RuntimeException(e); }finally{ try {
      config.close();
      
      }catch(Exception e){
      e.printStackTrace();
      
      } } }

    /**

    • 根据传入的参数,解析XML,并且封装到Map中
    • @param mapperPath 映射配置文件的位置
    • @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
    • 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名) */ private static Map loadMapperConfiguration(String mapperPath)throws IOException { InputStream in = null; try{ //定义返回值对象 Map mappers = new HashMap(); //1.根据路径获取字节输入流 in = Resources.getResourceAsStream(mapperPath); //2.根据字节输入流获取Document对象 SAXReader reader = new SAXReader(); Document document = reader.read(in); //3.获取根节点 Element root = document.getRootElement(); //4.获取根节点的namespace属性取值 String namespace = root.attributeValue(“namespace”);//是组成map中key的部分 // System.out.println(“namespace:”+namespace); //5.获取所有的select节点 List selectElements = root.selectNodes(“//select”); //6.遍历select节点集合 for(Element selectElement : selectElements){ //取出id属性的值 组成map中key的部分 String id = selectElement.attributeValue(“id”); //取出resultType属性的值 组成map中value的部分 String resultType = selectElement.attributeValue(“resultType”); //取出文本内容 组成map中value的部分 String queryString = selectElement.getText(); //创建Key String key = namespace+”.” + id; //创建Value Mapper mapper = new Mapper(); mapper.setQueryString(queryString); mapper.setResultType(resultType); //把key和value存入mappers中 mappers.put(key,mapper); } return mappers; }catch(Exception e){ throw new RuntimeException(e); }finally{ in.close(); } }

    /**

    • 根据传入的参数,得到dao中所有被select注解标注的方法。
    • 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
    • @param daoClassPath
    • @return */ private static Map loadMapperAnnotation(String daoClassPath)throws Exception{ //定义返回值对象 Map mappers = new HashMap();

      //1.得到dao接口的字节码对象 Class daoClass = Class.forName(daoClassPath); //2.得到dao接口中的方法数组 Method[] methods = daoClass.getMethods(); //3.遍历Method数组 for(Method method : methods){

       //取出每一个方法,判断是否有select注解
       boolean isAnnotated = method.isAnnotationPresent(Select.class);
       if(isAnnotated){
           //创建Mapper对象
           Mapper mapper = new Mapper();
           //取出注解的value属性值
           Select selectAnno = method.getAnnotation(Select.class);
           String queryString = selectAnno.value();
           mapper.setQueryString(queryString);
           //获取当前方法的返回值,还要求必须带有泛型信息
           Type type = method.getGenericReturnType();//List<User>
           //判断type是不是参数化的类型
           if(type instanceof ParameterizedType){
               //强转
               ParameterizedType ptype = (ParameterizedType)type;
               //得到参数化类型中的实际类型参数
               Type[] types = ptype.getActualTypeArguments();
               //取出第一个
               Class domainClass = (Class)types[0];
               //获取domainClass的类名
               String resultType = domainClass.getName();
               //给Mapper赋值
               mapper.setResultType(resultType);
           }
           //组装key的信息
           //获取方法的名称
           String methodName = method.getName();
           String className = method.getDeclaringClass().getName();
           String key = className+"."+methodName;
           //给map赋值
           mappers.put(key,mapper);
       }
      

      } return mappers; }

} ```

2.2.2 过程图分析

image.pngmybatis 与 jdbcTemplate - 图3

2.3 mybatis 还能怎么优化

  • 在 mybatis 现有框架下,提供两个配置文件,一个负责连接任务(一端连接数据库,另一端连接存储 sql 的文件),另一个负责存储对应接口方法相关 sql 信息;
  • 注:以下都是基于目前认知所提出的小猜想,可能不成熟,也可能错误,待以后验证;
  • 从代码优化角度考虑
    • 可以定义一套规范来简写 SQL,又或者能够以某种更简单的方式拼接 SQL;
  • 从内存角度考虑
    • 可以精简 dao 层对应的 mapper 信息,尤其是其中的 sql 信息,可以用的时候动态组装起来,从这个角度考虑,是不是 SQL 也能像 class 对象以某种字节码实现呢?
  • 从性能角度考虑

    • 调用 dao 层的某一个方法就能把其他方法的 mapper 都获取到,应该会有某种缓存机制来保存这些信息,防止重复的构建 mapper;

      3. 一些小思考

  • dao 层为什么是接口类:

    • 因为 sqlSession.getMapper() 中使用的是动态代理;
  • dao 类与 对应的 mapper.xml 是如何映射的
    • 由 sqlMapConfig.xml 提供 sql 映射路径和 sql 语句对应的方法(或自定义 id)为 key,sql 语句相关信息为 value;
  • mybatis 是如何处理依赖注入的
    • select from user where id=*#{id}
  • mapper.xml 中的 sql 语句有“生命对象”的特性吗?
    • 待查资料;