拉勾教育学习-笔记分享の"斩杀"框架篇(持久层)

【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---

〇、 脑图总览

图片可能存在更新延迟,最新脑图会在“传送门”中实时更新。

模块一 脑图传送门

image

一、 从持久层开始


:) Hello Mybatis

1.1 持久层定义

From360百科

系统逻辑层面上,专注于实现数据持久化的一个相对独立的领域(Domain),把数据保存到可掉电式存储设备中。持久层是负责向(或者从)一个或者多个数据存储器中存储(或者获取)数据的一组类和组件

1.2 早期JDBC

JDBC虽然是最接近数据库的访问方式,但也是最“僵硬”的,随着项目庞大起来,响应的维护量巨大。

它的问题非常明显:

  • 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能

  • Sql语句在代码中硬编码,造成代码不易维护

  • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护

  • 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便


public static void main() {

        Connection connection = null; // 准备连接实体

        PreparedStatement preparedStatement = null; // 准备statement

        ResultSet resultSet = null; // 准备结果集

        try {

            /* 1. 加载数据库驱动 (适用Mysql5+,8+请使用新驱动com.mysql.cj.jdbc.Driver)*/

            Class.forName("com.mysql.jdbc.Driver");

            /* 2. 通过驱动管理类获取数据库链接 */

            connection = DriverManager

                    .getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");

            /* 3. 定义sql语句 ? 占位符 */

            String sql = "SELECT * FROM user WHERE user_name = ?";

            /* 4. 获取预处理statement */

            preparedStatement = connection.prepareStatement(sql);

            /* 5. 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 */

            preparedStatement.setString(1, "Archie");

            /* 6. 向数据库发出sql查询,并接收结果集 */

            resultSet = preparedStatement.executeQuery();

            /* 7. 遍历查询结果集 */

            while (resultSet.next()) {

                // Do something ...

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            /* 8. 释放资源 */

            try {

                if (resultSet != null) {

                    resultSet.close();

                }

                if (preparedStatement != null) {

                    preparedStatement.close();

                }

                if (connection != null) {

                    connection.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

于是一个个持久层框架——HibernateMyBatisSpring Data ……横空出世

1.3 自定义持久层框架(借鉴Mybatis)

1.3.1 我们对于JDBC的问题,可以有以下解决思路:

  1. 使用数据库连接池 初始化 资源;

  2. 将“僵硬的”sql语句抽取到xml配置文件中;

  3. 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

1.3.2 框架设计

传送门

image

具体实现代码

有心者可以使用这份代码认真研读,这对后期“手撕”Mybatis源码很有帮助!

二、 Mybatis由浅入深 (代码附小节末)

2.1 Mybatis基本概念

2.1.1 ORM(Object/Relation Mapping)--对象/关系数据库映射:

编程人员只需利用面向对象的编程语言,把对持久化对象的CRUD操作,转化为对数据库底层的操作。

2.1.2 Mybatis简介与历史:

  • 是基于ORM的半自动/轻量级/持久层框架;

  • 支持定制化SQL、高级映射;

  • 可以使用简单的 XML/注解 来 配置/映射 接口、POJO.

原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)

2.1.3 Mybatis优势:

核心SQL可以由程序员自己定制以及优化,且SQL和java编码分离,使得功能边界清晰———前者专注数据、后者专注业务逻辑

2.2 踏出第一步

首先要明确,无论是何种技术,我们最好的学习文档就是 官方网址 -->

Mybatis官网

-----鉴于大部分人在工作中经常使用Mybatis,基本的使用在此简要带过,详细步骤可以参考各大教程博客------

2.2.1 开发顺序

  1. 确认核心文件SqlMapConfig.xml

  2. pom.xml中添加mybatis依赖

  3. 确认数据库的目标table

  4. 确认与之对应的POJO类(属性和表对应)

  5. 编写“桥梁”——XXMapper.xml

  6. 自测

Mybatis的代理开发方式:程序员只编写Mapper接口(相当于Dao接口),由Mybatis识别这些接口并创建动态代理对象。

2.2.2 编写Mapper接口的基本规范:

  1. Mapper.xml文件中的namespacemapper接口的全限定名相同

  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

2.3 核心配置文件sqlMapConfig.xml

官方说明

image

2.3.1 基本配置


<!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.cj.jdbc.Driver"/>

                <property name="url"

                          value="jdbc:mysql://localhost:3306/springdb?useSSL=false&amp;serverTimezone=UTC&amp;allowPublicKeyRetrieval=true"/>

                <property name="username" value="root"/>

                <property name="password" value="root"/>

            </dataSource>

        </environment>

    </environments>

    <mappers>

        <mapper resource="UserMapper.xml"/>

    </mappers>

</configuration>

2.3.2 mybatis支持的三种数据源(DataSource)

  1. UNPOOLED:每次被请求时打开和关闭连接

  2. POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来

  3. JDNI:为了能在如 EJB 或应用服务器这类容器中使用而实现的数据源

2.3.3 mybatis自生产常用类型别名(TypeAlias)

| 别名 | 数据类型 |

| ------- | -------- |

| string | String |

| long | Long |

| int | Integer |

| _int | int |

| double | Double |

| boolean | Boolean |

2.3.4 动态SQL

| 标签元素 | 作用 | 备注 |

| ------------------------------------------------------------ | -------------------------------------- | ---------- |

| if | 对输入参数进行判定,实现按条件拼接语句 | 单条件分支 |

| choose / when / otherwise | 相当于JAVA中的 if...else... | 多条件分支 |

| trim | 辅助(前置/后置)拼接 | 处理 SQL 拼接问题 |

| where | 内嵌sql职能处理至'where'后拼接 | 1.若输出是以and开头,会把首个'and'忽略掉
2.内部条件可以全空 |

| set | 和where类似 | 内部条件不可全空 |

| foreach | 在sql中迭代一个集合 | collection情况:
1. List -> "list"
2. Array -> "array"
3. Map -> 对应key |

| script | 注解中使用动态sql | |

| bind | 在OGNL表达式外创建一个变量并绑定到当前上下文 | |

2.4 复杂映射开发(基于配置)

2.4.1 一对一查询

业务描述:每一个订单包含唯一一个用户信息

分析语句:SELECT * FROM orders o, user u WHERE o.uid = u.id;

image
2.4.1.1 orders表 + user表
image
image
2.4.1.2 Order实体 + User实体

public class User {

    private Integer id;



    private String userName;



    private Integer age;



    // ...省略 get/set/toString

}


public class Order {



    private Integer id;

    private Date orderTime;

    private Double total;



    // 表明该订单属于哪个用户

    private User user;



  // ......省略 get/set/toString

}

2.4.1.3 OrderMapper.xml + OrderMapper.java

<?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="orderMapper">

    <resultMap id="orderMapper" type="com.archie.pojo.Order">

        <result column="id" property="id"/>

        <result column="ordertime" property="orderTime"/>

        <result column="total" property="total"/>

        <association property="user" javaType="com.archie.pojo.User">

            <result column="uid" property="id"/>

            <result column="username" property="userName"/>

            <result column="age" property="age"/>

        </association>

    </resultMap>



    <select id="listAll" resultMap="orderMapper">

        SELECT * FROM orders o, user u WHERE o.uid = u.id;

    </select>

</mapper>


public interface OrderMapper {



    List<Order> listAll();



}

2.4.1.4 自测 & 结果

    @Test

    public void testDemo() {

        SqlSession sqlSession = getFactory().openSession();

        List<Order> orderList = sqlSession.selectList("orderMapper.listAll");

        Assert.assertNotNull(orderList);

        for (Order order : orderList) {

            System.out.println(order);

        }

        sqlSession.close();

    }

image

2.4.2 一对多查询

业务描述:一个用户有多个订单,一个订单只从属于一个用户

分析语句:SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;

image
2.4.2.1 修改User实体

public class User {

    private Integer id;



    private String userName;



    private Integer age;



    //【新增】表示用户关联的订单

    private List<Order> orderList;

// ......省略 get/set/toString

}

2.4.2.2 UserMapper.xml + UserMapper.java

<?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="userMapper">

    <resultMap id="userMapper" type="com.archie.pojo.User">

        <result property="id" column="id"/>

        <result property="userName" column="username"/>

        <result property="age" column="age"/>

        <!--    使用collection接收List<Order>    -->

        <collection property="orderList" ofType="com.archie.pojo.Order">

            <result column="oid" property="id"/>

            <result column="ordertime" property="orderTime"/>

            <result column="total" property="total"/>

        </collection>

    </resultMap>

    <select id="listAll" resultMap="userMapper">

        SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;

    </select>

</mapper>


public interface UserMapper {



    List<User> listAll();



}

2.4.2.3 自测 & 结果

    @Test

    public void test003() {

        SqlSession sqlSession = getFactory().openSession();

        List<User> userList = sqlSession.selectList("userMapper.listAllWithOrders");

        Assert.assertNotNull(userList);

        for (User user : userList) {

            System.out.println(user);

        }

        sqlSession.close();

    }

image

2.4.3 多对多查询

业务描述:一个用户有多个角色,一个角色被多个用户使用

语句分析:`SELECT * FROM user u LEFT JOIN sys_user_role ur ON u.id = ur.userid

LEFT JOIN sys_role r ON r.id = ur.roleid`

image
2.4.3.2 sys_role表 + sys_user_role表
image
image
2.4.3.2 创建Role实体 + 修改User实体

public class Role {



    private Integer id;

    private String roleName;

    private String roleDesc;

// ...省略set/get/toString

}


public class User {

    private Integer id;



    private String userName;



    private Integer age;



    //表示用户关联的订单

    private List<Order> orderList;

    //【新增】表示用户关联的角色

    private List<Role> roleList = new ArrayList<>();

    // ...省略set/get/toString

}

2.4.3.3 在UserMapper接口中添加方法

public interface UserMapper {



    List<User> listAll(); // 获取所有用户(自身)



    List<User> listAllWithOrders(); // 获取所有用户和对应的订单信息(一对多)



    List<User> listAllWithRole(); // 获取所有用户和他们的角色(多对多)



}

2.4.3.4 修改UserMapper.xml中的配置(多加一个resultMap)

<?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="userMapper">

    <resultMap id="userMapper01" type="com.archie.pojo.User">

        <result property="id" column="id"/>

        <result property="userName" column="username"/>

        <result property="age" column="age"/>

        <!--    使用collection接收List<Order>    -->

        <collection property="orderList" ofType="com.archie.pojo.Order">

            <result column="oid" property="id"/>

            <result column="ordertime" property="orderTime"/>

            <result column="total" property="total"/>

        </collection>

    </resultMap>

    <resultMap id="userMapper02" type="com.archie.pojo.User">

        <result property="id" column="id"/>

        <result property="userName" column="username"/>

        <result property="age" column="age"/>

        <collection property="roleList" ofType="com.archie.pojo.Role">

            <result property="id" column="roleid"/>

            <result property="roleName" column="rolename"/>

            <result property="roleDesc" column="roleDesc"/>

        </collection>

    </resultMap>

<!-- 自身查询 -->

    <select id="listAll" resultType="com.archie.pojo.User">

        SELECT * FROM user;

    </select>

<!-- 获取所有用户和对应的订单信息(一对多) -->

    <select id="listAllWithOrders" resultMap="userMapper01">

        SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;

    </select>

<!-- 获取所有用户和他们的角色(多对多) -->

    <select id="listAllWithRole" resultMap="userMapper02">

        SELECT * FROM user u LEFT JOIN sys_user_role ur ON u.id = ur.userid LEFT JOIN sys_role r ON r.id = ur.roleid

    </select>

</mapper>

2.4.3.5 自测 & 结果

    @Test

    public void test004() {

        SqlSession sqlSession = getFactory().openSession();

        List<User> userList = sqlSession.selectList("userMapper.listAllWithRole");

        Assert.assertNotNull(userList);

        for (User user : userList) {

            System.out.println(user);

        }

        sqlSession.close();

    }

image

2.5 复杂映射开发(基于注解)

暂略......

2.6 Mybatis一级缓存

image

Mybatis的一级缓存默认打开,底层的实现是HashMap,生命周期在每一条SqlSession

2.6.1 测试 & 结果


    @Test

    public void test005() {

        SqlSession sqlSession = getFactory().openSession();

        List<Object> userList01 = sqlSession.selectList("userMapper.listAll");

        System.out.println(userList01.get(0));

        System.out.println("---------------------------------------------------");

        List<Object> userList02 = sqlSession.selectList("userMapper.listAll");

        System.out.println(userList02.get(0));

        Assert.assertEquals(userList01, userList02); // 断言:两个对象地址是否一样

    }

image

再试试如果在两次相同操作过程中,执行了其他RUD操作,是否会导致缓存被刷新


    @Test

    public void test005() {

        SqlSession sqlSession = getFactory().openSession();

        List<Object> userList01 = sqlSession.selectList("userMapper.listAll");

        System.out.println(userList01.get(0));

        System.out.println("---------------------------------------------------");

        User user = new User();

        user.setId(1);

        user.setUserName("Archie(2.0)");

        sqlSession.update("userMapper.updateById", user);

        sqlSession.commit();

        System.out.println("---------------------------------------------------");

        List<Object> userList02 = sqlSession.selectList("userMapper.listAll");

        System.out.println(userList02.get(0));

        Assert.assertEquals(userList01, userList02); // 断言:两个对象地址是否一样

        sqlSession.close();

    }

image

2.7 Mybatis二级缓存

Mybatis的二级缓存是跨SqlSession的,它基于mapper文件中的namespace——

所以:若两个mapper的namespace相同,就算mapper不同,也共享同一个二级缓存。

2.7.1 打开二级缓存

和一级缓存默认开启不一样,二级缓存需要我们手动开启


    <settings>

        <setting name="cacheEnable" value="true"/>

    </settings>

还得再UserMapper.xml中开启缓存


<mapper namespace="userMapper">

    <cache/>

    ...

<mapper/>

最后,因为二级缓存的存储介质多种多样(内存/硬盘...),所以我们要将对应POJO类实现Serializable接口,从而实现序列/反序列化。


public class User implements Serializable

2.7.2 测试 & 结果

A. 二级缓存和sqlSession无关

    @Test

    public void test006() {

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);



        List<User> userList1 = mapper1.listAll();

        System.out.println(userList1.get(0));

        sqlSession1.close(); // 关闭 1号SqlSession ,清空一级缓存

        System.out.println("---------------------------------------------------");

        List<User> userList2 = mapper2.listAll();

        System.out.println(userList2.get(0));

        sqlSession2.close();

        Assert.assertNotEquals(userList1, userList2); // 断言:两个对象地址是否不一样

    }

image
B. 执行commit()操作,二级缓存数据清空

    @Test

    public void test007() {

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        SqlSession sqlSession3 = sqlSessionFactory.openSession();



        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

        UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);



        List<User> userLis1 = mapper1.listAll();

        sqlSession1.close(); // 清空一级缓存

        System.out.println("-----------------执行commit操作-------------------");

        User user = new User();

        user.setId(1);

        user.setUserName("Archie(3.0)");

        mapper2.updateById(user);

        sqlSession2.commit();

        System.out.println("------------------------------------");

        List<User> userList2 = mapper3.listAll();

        Assert.assertNotEquals(userLis1, userList2); // 断言:两个对象地址是否不一样

    }

image

2.7.3 二级缓存整合Redis

由于mybatis自带的二级缓存是单服务器工作,无法在分布式系统上工作,因此需要借助分布式缓存:【Redis/memcached/ehcache】

2.7.3.1 准备
  1. 准备pom依赖

  <dependency>

      <groupId>org.mybatis.caches</groupId>

      <artifactId>mybatis-redis</artifactId>

      <version>1.0.0-beta2</version>

  </dependency>

  1. sqlMapConfig.xml指定分布式缓存类型 + 对应接口是否启用

    <!-- 整合Redis加入分布式缓存 -->

    <cache type="org.mybatis.caches.redis.RedisCache"/>


    <!-- useCache="true" -->

    <select id="listAll" resultType="com.archie.pojo.User" useCache="true">

        SELECT * FROM user;

    </select>

  1. 加入redis.properties文件(若无指定修改,可不添加该文件,底层有默认值)

redis.host=localhost

redis.port=6379

redis.connectionTimeout=5000

redis.password=

redis.database=0

  1. 测试前 开启Redis服务
image

Redis快速运行脚本可以自己写:redis-server redis.windows.conf

image
2.7.3.1 测试

    @Test

    public void test008() {

        SqlSession sqlSession1 = sqlSessionFactory.openSession();

        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

        SqlSession sqlSession2 = sqlSessionFactory.openSession();

        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);



        List<User> userList1 = mapper1.listAll();

        System.out.println(userList1.get(0));

        sqlSession1.close(); // 清空一级缓存

        System.out.println("---------------------------------------------------");

        List<User> userList2 = mapper2.listAll();

        System.out.println(userList2.get(0));

        sqlSession2.close();

        Assert.assertNotEquals(userList1, userList2); // 断言:两个对象地址是否不一样

    }

image

2.8 MyBatis插件(plugin)

暂略...


代码传送门

三、Mybatis源码“斩杀”

攻坚战开始了!O.O

P.S. 之前的自定义持久层框架一定要熟烂于心!

3.1 流程总览

暂略...

3.2 传统方式源码剖析


      // 1.使用ClassLoader对文件进行 “流化” !(但还未具体解析)

      InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");

      // 2. 使用XPathParse 解析出 XMLConfigBuilder 对象,调用parse()方法

      // -- 2.1. parse()方法内部  利用 XNode 解析出 每个标签并将它们封装在 [JavaBean]Configuration中

      // -- 2.2. 构建者模式创建 DefaultSqlSessionFactory 对象

      sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);

      // 3. 生成DefaultSqlsession实例对象了,并将 Executor 对象封装在其中

      // -- 3.1. 其中默认设置了 事务提交方式为:[非自动提交]

      sqlSession = sqlSessionFactory.openSession();

      // 4. 根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象

      // -- 4.1. 执行 executor.query() 从而 将查询任务委派到了executor执行器

      // --- 4.1.2. 顶层query() -> 设置分页值、设置CacheKey

      // --- 4.1.2. 判断 执行器是否被关闭、是否需要刷新缓存, 若否 -> 调用queryFromDatabase()方法

      // --- 4.1.3. 核心调用SimpleExecutor.doQuery()

      // ---- 4.1.3.1. 创建StatementHandler对象

      // ---- 4.1.3.2. 构建者模式生成ParameterHandler

      // ---- 4.1.3.3. 获取TypeHandler

      // ---- 4.1.3.4. 调用ResultSetHandler.handleResultSets() 处理statement

      // ***** 底层JDBC操作 *****

      // 5. 返回List<E> 的结果

      List<User> userList = sqlSession.selectList("com.archie.dao.UserMapper.listAll");

      // 6. 释放资源

      sqlSession.close();

3.3 Mapper代理方式源码剖析

获取sqlSession前的步骤和 传统方式一致;


      // ......

      SqlSession sqlSession = sqlSessionFactory.openSession();

      // 4. 通过mapperRegistry 调用 getMapper()方法

      // -- 4.1. 从mapperRegistry中的 HashMap 拿 MapperProxyFactory

      // -- 4.2. 通过动态代理工厂 生成实例

      // --- 4.2.1. 实例的生成过程中实现了对每一个SqlSession 的动态代理

      UserMapper mapper = sqlSession.getMapper(UserMapper.class);

      // 5. 获取到 代理对象后,就可以直接调用被代理接口的方法了

      // -- 5.1. 代理对象,调用方法时都是执行MapperPorxy中的invoke()方法

      // ---- 5.1.1. invoke()中,若非Object的方法,则获取MapperMethod对象,【重点】它来最终调用execute()方法

      // ---- 5.1.2. execute()中,判断方法类型-->CRUD, 最终交由ResultHandler处理,返回Object型的result值

      List<User> userList01 = mapper.listAll();

3.4 二级缓存源码剖析

查询请求优先级:【 二级缓存 > 一级缓存 > 数据库 】

二级缓存和具体namespace绑定,一个Mapper中有一个Cache,相同Mapper的MapperStatement共用一个Cache

3.4.1 思考A - ‘为什么说一个Mapper中的Cache被共用?’

在3.2小节中,我们分析步骤2时,sqlMapConfig.xml 的具体解析是由XMLConfigBuilder调用parse()解析的,它中途需要解析到<cache/>标签,这个标签也是我们打开二级缓存的标志。所以我们得分析从这里开始。

高清传送门

image

3.4.2 思考B - ‘为什么优先级 :二级缓存 > 一级缓存?’

P.S. 二级缓存开启时,走的是CachingExecutor实现类

3.4.3 思考C - ‘为何只有SqlSession提交或者关闭后,二级缓存才会生效?’

暂略...

3.5 延迟加载源码剖析

暂略...

3.5.1 使用场景

当需要用到数据的时候才进行多表关联查询。

  • 优点:大大提高数据库性能;

  • 缺点:因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降

建议使用场景:

  • 一对一/多对一: 立即加载
  • 一对多/多对多: 延迟加载

懒加载是基于 嵌套查询的!

3.5.2 实现

3.5.2.1 UserMapper.xml + UserMapper.java
  1. 指明当前 collection标签 需要定位的内嵌sql select="com.archie.dao.OrderMapper.listByUid"

  2. 传给内嵌sql的参数 column="id"

  3. 实现局部懒加载 fetchType="lazy"


  <resultMap id="userMapper01" type="com.archie.pojo.User">

      <id property="id" column="id"/>

      <result property="userName" column="username"/>

      <result property="age" column="age"/>



      <collection property="orderList" ofType="com.archie.pojo.Order"

      select="com.archie.dao.OrderMapper.listByUid" column="id"

      fetchType="lazy">

          <result column="uid" property="id"/>

          <result column="ordertime" property="orderTime"/>

          <result column="total" property="total"/>

      </collection>

  </resultMap>



  <select id="listById" resultMap="userMapper01" parameterType="com.archie.pojo.User">

      SELECT * FROM user WHERE id = #{id};

  </select>


User listById(User user);

3.5.2.2 OrderMapper.xml + OrderMapper.java

  <resultMap id="orderMapper" type="com.archie.pojo.Order">

      <id column="id" property="id"/>

      <result column="ordertime" property="orderTime"/>

      <result column="total" property="total"/>

      <association property="user" javaType="com.archie.pojo.User">

          <result column="uid" property="id"/>

          <result column="username" property="userName"/>

          <result column="age" property="age"/>

      </association>

  </resultMap>



  <select id="listByUid" resultType="com.archie.pojo.Order">

      SELECT * FROM orders WHERE uid = #{uid};

  </select>


List<Order> listByUid(Integer uid);

3.5.2.3 自测 & 结果

    @Test

    public void test009() {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = new User();

        user.setId(1);

        User resultUser = sqlSession.selectOne("com.archie.dao.UserMapper.listById", user);

        System.out.println(resultUser.getUserName());

        System.out.println("****** 还未使用到订单信息 *******");

        System.out.println("**********  现在想要获取对应用户的订单信息了  ************");

        System.out.println(resultUser.getOrderList());

    }

image
3.5.2.4 全局配置懒加载

在sqlMapConfig.xml中加入<setting name="lazyLoadingEnabled" value="true">即可实现全局懒加载

懒加载的优先级: 局部 高于 全局

因此——当全局开启时,若有部分接口不想懒加载,可以直接 fetchType="eager"实现局部立即加载

3.5.3 源码剖析

暂略...

四、 设计模式

4.1 Builder构建者模式

4.1.1 通俗定义

使用多个简单对象 --构建--> 复杂对象 的过程。

4.1.2 简单实现

Demo【构建一个Computer复杂对象】

  • 简单对象:键盘、鼠标、显示器、主机……

  • 复杂对象:Computer

  • 顺序:创建构造类 --> 依次创建部件 --> 将部件组装成target对象


【step-A】 定义目标对象Computer


public class Computer {

    /* 显示器部件 */

    private String disPlayer;

    /* 主机部件 */

    private String mainEngine;

    /* 鼠标部件 */

    private String mouse;

    /* 键盘部件 */

    private String keyBoard;



    // ...省略 set、get、toString

}

【step-B】 定义构建者ComputerBuilder


public class ComputerBuilder {



    /* 定义目标对象 */

    private Computer target = new Computer();



    /**

    * 安装显示器部件给目标对象

    * @param disPlayer

    */

    public void installDisPlayer(String disPlayer) {

        target.setDisPlayer(disPlayer);

    }



    /**

    * 安装主机部件给目标对象

    * @param mainEngine

    */

    public void installMainEngine(String mainEngine) {

        target.setMainEngine(mainEngine);

    }



    /**

    * 安装鼠标部件给目标对象

    * @param mouse

    */

    public void installMouse(String mouse) {

        target.setMouse(mouse);

    }



    /**

    * 安装键盘部件给目标对象

    * @param keyBoard

    */

    public void installKeyBoard(String keyBoard) {

        target.setKeyBoard(keyBoard);

    }



    /**

    * 实现复杂对象构建

    * @return

    */

    public Computer build() {

        return target;

    }



}


测试


public class realizeBuildingComputer {



    public static void main(String[] args) {

        ComputerBuilder builder = new ComputerBuilder();

        builder.installDisPlayer("A-1001-显示器");

        builder.installMainEngine("B-2344-主机");

        builder.installMouse("C-6617-鼠标");

        builder.installKeyBoard("D-4990-键盘");

        /* 安装部件完毕,生产电脑 */

        Computer computer = builder.build();

        /* 测试打印 */

        System.out.println("computer = \n" + computer);

    }



}

image

4.1.3 mybatis中的体现

  • Mybatis的初始化流程非常复杂,在创建SqlSessionFactory的过程中,利用了大量的Builder进行分层构建;

  • 核心javaBean-'Configuration' 也是使用了XmlConfigBuilder来构建的;

高清传送门

image

4.2 工厂模式

(目前只需掌握 /简单工厂模式/ 即可)

4.2.1 通俗定义

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建型模式。

专门有一个类 ——> 根据参数的不同返回不同类的实例(这些实例往往是同一个父类)

4.2.2 简单实现

Demo 【一个厂商生产不同品牌的电脑】


【Step-A】创建产品抽象类


public abstract class Computer {



    /* 启动电脑方式 :由具体产品实现 */

    public abstract void start();



}

【Step-B】创建具体产品

创建不同品牌的电脑,都继承抽象父类->Computer


public class HPComputer extends Computer{

    @Override

    public void start() {

        System.out.println("惠普电脑启动");

    }

}

public class LenovoComputer extends Computer {

    @Override

    public void start() {

        System.out.println("联想电脑开机");

    }

}

【Step-C】创建工厂类

提供静态方法createComputer用来生产电脑(用户只需要传输想要的品牌,响应对象即刻实例化)


public class ComputerFactory {



    public static Computer createComputer(String brand) {

        Computer computer = null;

        switch (brand) {

            case "lenovo":

                computer = new LenovoComputer();

            case "hp":

                computer = new HPComputer();

        }

        return computer;

    }



}


测试


  public static void main(String[] args) {

      ComputerFactory.createComputer("hp").start();

      System.out.println("-----------");

      ComputerFactory.createComputer("lenovo").start();

  }

image

4.2.3 Mybatis中体现

SqlSessionFactory中的openSession有很多重载,支持各类参数类别的输入,从而构建核心SqlSeesion对象

image

这个工厂的核心底层方法是openSessionFromDataSource


  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

    Transaction tx = null;

    try {

      final Environment environment = configuration.getEnvironment();

      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);

      // 根据参数创建制定类型的Executor

      final Executor executor = configuration.newExecutor(tx, execType);

      // 执行器注入构建方法,返回SqlSession对象

      return new DefaultSqlSession(configuration, executor, autoCommit);

    } catch (Exception e) {

      closeTransaction(tx); // may have fetched a connection so lets call close()

      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);

    } finally {

      ErrorContext.instance().reset();

    }

  }

4.3 代理模式

(目前掌握动态代理即可)

4.3.1 通俗定义

给某一个对象提供一个代理,并由代理对象控制对原对象的引用

4.3.2 简单实现

Demo【对Person实体进行代理】


【Step-A】‘人’接口和默认实现类


public interface Person {

    void doSomething(); 

}

public class Justin implements Person{

    @Override

    public void doSomething() {

        System.out.println("Justin is doing something...");

    }

}

【Step-B】具体的代理实现类


public class JdkDynamicProxyImpl implements InvocationHandler {



    // 声明被代理的对象

    private Person person;



    // 构造函数

    public JdkDynamicProxyImpl(Person person) {

        this.person = person;

    }



    /**

    * 获取代理对象

    * @return

    */

    public Object getTarget() {

        Object proxyInstance = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this);

        return proxyInstance;

    }



    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("...对原方法进行前置增强");

        Object invoke = method.invoke(person, args); // 原方法的执行

        System.out.println("...对原方法进行后置增强");

        return invoke;

    }

}


测试


public class ProxyTest {



    public static void main(String[] args) {

        System.out.println("【直接调用某人类的方法】");

        Justin justin = new Justin();

        justin.doSomething();

        System.out.println("-----------------");

        System.out.println("【使用代理类调用同一个人类的行为】");

        Person proxyJustn = (Person) new JdkDynamicProxyImpl(new Justin()).getTarget();

        proxyJustn.doSomething();

    }



}

image

4.3.3 Mybatis中体现

代理模式是MyBatis的核心模式

由于这个模式,我们只需要编写XXXMapper.java接口,而不需要实现;由MyBatis完成具体SQL的执行。

高清传送门

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容