• 持久层框架设计实现及MyBatis源码分析 ---- MyBatis基础回顾及高级应用


    一、基本应用

    基本开发步骤:
    ① 添加MyBatis的坐标
    ② 创建xxx数据表
    ③ 编写Xxx实体类
    ④ 编写sql映射⽂件XxxMapper.xml
    ⑤ 编写核⼼配置⽂件SqlMapConfig.xml
    ⑥ 编写测试类

    二、配置文件介绍

    1. sql映射配置文件 XxxMapper.xml

    (1) 基础使用

    在这里插入图片描述

    (2) 动态SQL

    where + if 标签
    在这里插入图片描述
    where标签 mybatis会 自动添加where语句if标签 根据if条件,如果成立,会为后面多个判断语句 添加and(第一个除外)

    foreach 标签
    在这里插入图片描述
    foreach标签的属性含义如下:
    • collection:代表要遍历的集合元素。作为入参,对象为list、array时,collection属性值分别默认用"list"、"array"代替,Map对象没有默认的属性值。但是,在作为入参时可以使@Param(“name”)注解来设置自定义collection属性值,设置name后,list、array会失效
    • open:代表语句的开始部分
    • close:代表结束部分
    • item:代表遍历集合的每个元素,⽣成的变量名
    • sperator:代表分隔符

    抽取Sql片段
    在这里插入图片描述

    2. 核心配置文件 SqlMapConfig.xml

    在这里插入图片描述

    (1) properties标签

    实际开发中,习惯将数据源的配置信息单独抽取成⼀个properties⽂件,该标签可以加载额外配置的
    properties⽂件
    在这里插入图片描述

    (2) typeAliases标签

    类型别名是为Java 类型设置⼀个短的名字。原来的类型名称配置如下
    在这里插入图片描述
    在这里插入图片描述
    上⾯我们是⾃定义的别名,mybatis框架已经为我们设置好的⼀些常⽤的类型的别名
    在这里插入图片描述

    (3) environments标签

    在这里插入图片描述
    数据库环境的配置,⽀持多环境配置

    其中,事务管理器(transactionManager)类型有两种:
    •JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作⽤域。
    •MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣
    命周期(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因
    此需要将 closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。

    其中,数据源(dataSource)类型有三种:
    •UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
    •POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
    •JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配
    置数据源,然后放置⼀个 JNDI 上下⽂的引⽤。

    (4) mapper标签

    该标签的作⽤是加载映射的,加载⽅式有如下⼏种:

    	•使⽤相对于类路径的资源引⽤,例如:
    	<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    	•使⽤完全限定资源定位符(URL),例如:
    	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
    	•使⽤映射器接⼝实现类的完全限定类名,例如:
    	<mapper class="org.mybatis.builder.AuthorMapper"/>
    	•将包内的映射器接⼝实现全部注册为映射器,例如:
    	<package name="org.mybatis.builder"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、Mybatis相应API介绍

    	String resource = "org/mybatis/builder/mybatis-config.xml";
    	InputStream inputStream = Resources.getResourceAsStream(resource);
    	SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    	SqlSessionFactory factory = builder.build(inputStream);
    
    • 1
    • 2
    • 3
    • 4

    Resources ⼯具类
    这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、⽂
    件系统或⼀个 web URL 中加载资源⽂件。

    SqlSession⼯⼚构建器SqlSessionFactoryBuilder
    常⽤API:SqlSessionFactory build(InputStream inputStream)
    通过加载mybatis的核⼼⽂件的输⼊流的形式构建⼀个SqlSessionFactory对象

    SqlSession⼯⼚对象SqlSessionFactory
    SqlSessionFactory 有多个⽅法创建SqlSession 实例。常⽤的有如下两个:
    在这里插入图片描述

    SqlSession会话对象
    SqlSession 实例在 MyBatis 中是⾮常强⼤的⼀个类。在这⾥你会看到所有执⾏语句、提交或回滚事务
    和获取映射器实例的⽅法。
    执⾏语句的⽅法主要有:

    	<T> T selectOne(String statement, Object parameter)
    	<E> List<E> selectList(String statement, Object parameter)
    	int insert(String statement, Object parameter)
    	int update(String statement, Object parameter)
    	int delete(String statement, Object parameter)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    操作事务的⽅法主要有:

    	void commit() 
    	void rollback()
    
    • 1
    • 2

    四、Mybatis复杂映射开发

    1. 一对一查询

    给定要求:因为一个订单对应着一个用户,所以要求查询出订单的同时将其所属用户也查询出来
    sql语句查询结果:
    在这里插入图片描述

    因为程序当中没有一个指定的实现类能够与查询结果对应,所以我们需要自定义查询结果集类型
    使用resultMap来进行封装
    在这里插入图片描述
    Order.java

    public class Order {
        private int id;
        private Date ordertime;
        private double total;
        //代表当前订单从属于哪⼀个客户
        private User user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    User.java

    public class User {
        private int id;
        private String username;
        private String password;
        private Date birthday;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 一对多查询

    给定要求:因为一个用户可能对应着多个用户,所以要求查询出用户及其所属的订单
    sql语句查询结果:
    在这里插入图片描述
    使用collection标签来配置多的一方的属性,封装为一个集合
    在这里插入图片描述
    Order.java

    public class Order {
        private int id;
        private Date ordertime;
        private double total;
        //代表当前订单从属于哪⼀个客户
        private User user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    User.java

    public class User {
        private int id;
        private String username;
        private String password;
        private Date birthday;
        // 代表当前⽤户具备哪些订单
        private List<Order> orderList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3. 多对多查询

    给定要求:⽤户表和⻆⾊表的关系为,⼀个⽤户有多个⻆⾊,⼀个⻆⾊被多个⽤户使⽤;查询⽤户同时查询出该⽤户的所有⻆⾊
    sql语句查询结果:
    在这里插入图片描述
    同样使用collection标签
    在这里插入图片描述
    User.java

    public class User {
        private int id;
        private String username;
        private String password;
        private Date birthday;
        // 代表当前⽤户具备哪些订单
        private List<Order> orderList;
        // 代表当前⽤户具备哪些⻆⾊
        private List<Role> roleList;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Role.java

    public class Role {
        private int id;
        private String roleName;
        private String roleDesc;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    总结

    MyBatis多表配置⽅式:
    ⼀对⼀配置:使⽤做配置
    ⼀对多配置:使⽤ + 做配置
    多对多配置:使⽤ + 做配置

    五、Mybatis注解开发

    常用注解

    @Insert:实现新增
    @Update:实现更新
    @Delete:实现删除
    @Select:实现查询
    @Result:实现结果集封装
    @Results:可以与@Result ⼀起使⽤,封装多个结果集
    @One:实现⼀对⼀结果集封装
    @Many:实现⼀对多结果集封装

    注解方式增删改查

    在这里插入图片描述
    直接在接口方法上使用注解 + Sql语句的方式完成

    注解实现复杂映射开发

    实现复杂关系映射之前我们可以在映射⽂件中通过配置来实现,使⽤注解开发后,我们可以使⽤
    @Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
    在这里插入图片描述
    在这里插入图片描述

    1. ⼀对⼀查询

    需求同上面的一对一查询
    在这里插入图片描述

    2. ⼀对多查询

    在这里插入图片描述

    3. 多对多查询

    在这里插入图片描述

    六、Mybatis缓存

    在这里插入图片描述

    1. ⼀级缓存

    使用

    在这里插入图片描述

    总结:
    1、第⼀次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,如果没有,从 数据
    库查询⽤户信息。得到⽤户信息,将⽤户信息存储到⼀级缓存中。
    2、 如果中间sqlSession去执⾏commit操作(执⾏插⼊、更新、删除),则会清空SqlSession中的 ⼀
    级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。
    3、 第⼆次发起查询⽤户id为1的⽤户信息,先去找缓存中是否有id为1的⽤户信息,缓存中有,直 接从
    缓存中获取⽤户信息

    源码分析

    首先我们要去找到缓存存在的位置及数据结构

    根据以下流程图
    在这里插入图片描述

    最终找到chche为一个HashMap集合,也就是说,缓存其实就是本地存放的⼀个map对象,每⼀个SqISession都会存放⼀个map对象的引⽤
    在这里插入图片描述

    然后要找到缓存对象在什么时候被创建添加进去呢?

    因为Executor是执⾏器,⽤来执⾏SQL请求,⽽且清除缓存的⽅法也在Executor中执⾏,所以很可能缓存的创建也很 有可能在Executor中,看了⼀圈发现Executor中有⼀个createCacheKey⽅法

    通过这个方法,会将namespaceid、分页属性、Sql语句、环境信息等等创建为一个缓存对象

    	public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
            if (this.closed) {
                throw new ExecutorException("Executor was closed.");
            } else {
                CacheKey cacheKey = new CacheKey();
                cacheKey.update(ms.getId());
                cacheKey.update(rowBounds.getOffset());
                cacheKey.update(rowBounds.getLimit());
                cacheKey.update(boundSql.getSql());
                
                ......
    
                if (this.configuration.getEnvironment() != null) {
                    cacheKey.update(this.configuration.getEnvironment().getId());
                }
    
                return cacheKey;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    最后就要看这个缓存在哪个地方被使用?

    定位到Executor中query方法
    在这里插入图片描述
    在这里插入图片描述

    二、二级缓存

    ⼆级缓存的原理和⼀级缓存原理⼀样,第⼀次查询,会将数据放⼊缓存中,然后第⼆次查询则会直接去
    缓存中取。
    但是⼀级缓存是基于sqlSession的,⽽⼆级缓存是基于mapper⽂件的namespace的,也就是说多个sqlSession可以共享⼀个mapper中的⼆级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执⾏sql查询到的数据也将存在相同的⼆级缓存区域中
    在这里插入图片描述

    二级缓存的手动开启(默认开启)
    非注解方式:sqlMapConfig.xml中

    	
    	<settings>
    		 <setting name="cacheEnabled" value="true"/>
    	settings>
    
    • 1
    • 2
    • 3
    • 4

    注解方式: 在接口上面加上@CacheNamespace注解

    其次在XxxMapper.xml⽂件中开启缓存

    	
    	<cache>cache>
    
    • 1
    • 2

    开启了⼆级缓存后,还需要将要缓存的pojo实现Serializable接⼝,为了将缓存数据取出执⾏反序列化操
    作,因为⼆级缓存数据存储介质多种多样,不⼀定只存在内存中,有可能存在硬盘中,如果我们要再取
    这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接⼝

    注意useCache和flushCache属性
    在这里插入图片描述

    三、⼆级缓存整合redis

    引入依赖

    	<dependency>
    		 <groupId>org.mybatis.cachesgroupId>
    		 <artifactId>mybatis-redisartifactId>
    		 <version>1.0.0-beta2version>
    	dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置使用RedisCache
    非注解方式
    在Mapper.xml中添加chche标签

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

    注解方式
    在接口上加上:@CacheNamespace(implementation = RedisCache.class)

    创建redis.properties(名称固定)

    	redis.host=localhost
    	redis.port=6379
    	redis.connectionTimeout=5000
    	redis.password=
    	redis.database=0
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后使用即可

    七、Mybatis插件

    插件简介

    ⼀般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者⾃⾏拓展。这样的好处是显⽽易⻅的,⼀是增加了框架的灵活性。⼆是开发者可以结合实际需求,对框架进⾏拓展,使其能够更好的⼯作。以MyBatis为例,我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能

    Mybatis插件介绍

    Mybati s作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件
    (Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象
    在这里插入图片描述
    MyBatis所允许拦截的⽅法如下:

    • 执⾏器Executor (update、query、commit、rollback等⽅法);
    • SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅法);
    • 参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
    • 结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

    插件原理

    在四⼤对象创建的时候
    1、每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
    2、获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返回 target 包装后的对象
    3、插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可以为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;
    在这里插入图片描述
    interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调⽤拦截器链中的拦截器依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象

    自定义插件

    首先要实现Interceptor接口及其里面的方法
    Mybatis 插件接⼝-Interceptor
    • Intercept⽅法,插件的核⼼⽅法
    • plugin⽅法,⽣成target的代理对象
    • setProperties⽅法,传递插件所需参数

    然后添加@Intercepts注解,里面嵌套@Signature注解完成对指定类中指定方法的定位
    在SqlMapConfig主配置文件中配置拦截器标签及属性

    @Intercepts({   // 这个⼤花括号,表示这⾥可以定义多个@Signature对多个地⽅拦截,都⽤这个拦截器
            @Signature(type = StatementHandler.class,  // 指定拦截哪个接口
                    method = "prepare",  // 这个接⼝内的哪个⽅法名
                    args = {Connection.class, Integer.class})  // 这是拦截的⽅法的⼊参,按顺序写到这,如果⽅法重载,要通过⽅法名和⼊参来确定唯⼀
    })
    public class MyPlugin implements Interceptor {
        /**
         * 每次执⾏操作的时候,都会执行拦截器当中的这个方法
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("对方法增强。。。。。。");
            return invocation.proceed();  // 执行原方法
        }
    
        /**
         * 主要是为了把这个拦截器⽣成⼀个代理放到拦截器链中
         * @param o 要拦截的对象
         * @return 生成的代理对象
         */
        @Override
        public Object plugin(Object o) {
            System.out.println("要包装的目标对象:" + o);
            return Plugin.wrap(o, this);
        }
    
        /**
         * 插件初始化的时候调⽤,也只调⽤⼀次,将插件配置的属性从这⾥设置进来
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
            System.out.println("获取到的插件配置参数:" + properties);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
        <plugins>
            <plugin interceptor="com.lagou.interceptor.MyPlugin">
                <property name="key" value="value"/>
            plugin>
        plugins>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    配置后执行某一查询操作结果
    在这里插入图片描述

    源码分析

    public class Plugin implements InvocationHandler {
        private final Object target;
        private final Interceptor interceptor;
        private final Map<Class<?>, Set<Method>> signatureMap;
    
    	......
    	// 代理对象都会执行invoke方法,里面逻辑较为简单,
    	// 就是如果找到了我们拦截器上面配置的接口及其对应方法,就会执行其intercept方法
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
                return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
            } catch (Exception var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        }
    	......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
  • 相关阅读:
    如何理解低代码开发工具?
    c#如何把字符串中的指定字符删除
    老卫带你学---leetcode刷题(49. 字母异位词分组)
    联邦学习中的非独立同分布Non-IID
    LeetCode 每日一题——655. 输出二叉树
    MySQL添加、查看、修改与删除数据
    怎么在Linux中用tmux跑深度学习模型
    9.8 段错误,虚拟内存,内存映射 CSAPP
    Oracle导出clob字段到csv
    massCode设置中文
  • 原文地址:https://blog.csdn.net/Amxrjgcs/article/details/128104601