前言:
此刻小伙伴们对“java二级教程”可能比较着重,你们都需要知道一些“java二级教程”的相关知识。那么小编在网摘上搜集了一些关于“java二级教程””的相关资讯,希望我们能喜欢,大家一起来了解一下吧!一丶从一个问题开始——读已提交情况下mybatis一级缓存造成的问题#
上图中,已知道users1的size为5,那么users2的大小为多少昵?
我们暂且抛弃mybatis框架中的知识,从mysql事务隔离级别进行分析,test方法第一次查询到总数,然后重新开启一个事务插入了一条(require_new的传播级别),后续addOne方法将立即提交,再次查询的时候,test方法应该可以立马查询到已经提交的数据,应该比第一次输出的应该多1,这是事务隔离级别指导我们做出的判断
但是事实上是users1大小和 users2一样大,这是为什么昵?
我们看下控制台
发现mybatis并没有进行第二次数据库的查询,这时候我们应该意识到mybatis具备缓存,从而导致第二次查询并没有访问数据库
也就是说 读已提交的隔离级别下,mybatis如果不关闭缓存将存在错误(这里的缓存指的一级缓存,二级缓存普遍是不开的)
具体原理,笔者此文讲到mybatis缓存后将进行解读,下面我们从springboot 和 mybatis整合,到mybatis执行原理展开讲mybatis的原理
二丶mybatis-springboot-starter的自动装配#
通常springboot整合mybatis只需要引入如下依赖
简单描述就是SpringBoot启动的时候会读取META-INF/spring.factories中自动配置的类,加入到容器中,后续springboot会将这些类当作配置类进行解析
上图是mybatis-spring-starter的META-INF/spring.factories,其中关键的是MybatisAutoConfiguration
1.导入SqlSessionTemplate,SqlSessionFactory#
这里可以看到当容器中没有SqlSessionFactory的时候,MybatisAutoConfiguration会为我们注入一个SqlSessionFactory,SqlSessionTemplate同样如此。
这里我们简单提一下SqlSessionFactory和SqlSessionTemplate的作用
1.1.mybatis中的SqlSessionFactory#
故名思意,它是创建SqlSession的工厂
这里Spring构建SqlSessionFactory,使用了SqlSessionFactoryBean#getObject
它实现了InitializingBean,但是由于没有被注入到容器中,所以其#afterProperties并不会被spring容器回调,在此方法中会调用buildSqlSessionFactory 进行别名扫描,TypeHandler注册,xml解析(调用XMLMapperBuilder#parse),拦截器注册,并且指定事务工厂使用SpringManagedTransactionFactory(mybatis,spring事务结合的关键,后续详细解析)等工作
那么什么是SqlSession?
1.2.mybatis中的SqlSession#
SqlSession是mybatis操作数据库抽象出来的接口,它可以执行增删改查,提交事务,回滚事务,创建mapper。我们平时依赖注入的mapper,其实一个动态代理类,其底层其实是调用SqlSession进行的数据库操作
1.3.mybatis-spring中的SqlSessionTemplate#
这个类和上面两个类都不同,它是org.mybatis.spring这个包下面的,一般是mybatis-spring这个依赖会引入的类,它的作用是SqlSession,与Spring事务管理一起工作,以确保实际使用的SqlSession与当前Spring事务关联。此外,它还管理会话生命周期,包括根据Spring事务配置在必要时关闭、提交或回滚会话。
它是spring事务和mybatis事务结合的关键,后面用到了我们再详细唠唠
2.注入AutoConfiguredMapperScannerRegistrar#
这里可以看到如果没有MapperFactoryBean和MapperScannerConfigurer这两个bean ,那么会import一个AutoConfiguredMapperScannerRegistrar,我们简单说下这三个类的作用,后续用到了详细解析其原理
2.1MapperFactoryBean#
MapperFactoryBean 是一个FactoryBean。FactoryBean中有一个方法叫getObject负责创建一个对象交给spring容器管理,通常我们定义的Controller,Service都具备实现类,而非一个接口,spring可以实例化一个service的实现。但是mybatis中的mapper往往是一个接口,spring不知道如何实例化这个mapper,这时候发现mapper的BeanDefinition中标记了这个class是MapperFactoryBean就会调用MapperFactoryBean#getObject实例化一个mapper,这个mapper便是我们注入到service中使用的mapper,它是源mapper的动态代理实现类,从而在代理类中调用Sqlsesession执行对应的sql操作
2.2AutoConfiguredMapperScannerRegistrar#
在没有MapperScannerConfigurer,mybatis自动装配会为我们注入它,它是一个ImportBeanDefinitionRegistrar
spring解析配置类的时候,若发现一个bean是ImportBeanDefinitionRegistrar的实现,那么会调用其registerBeanDefinitions方法,从而注入其他bean的BeanDefinition,这里bean便是MapperScannerConfigurer(ImportBeanDefinitionRegistrar注入的MapperScannerConfigurer扫描的时候要求mapper标注@Mapper注解)
2.3MapperScannerConfigurer#
MapperScannerConfigurer还可以使用@MapperScan或者@MapperScans注解,进行引入,若我们使用了@MapperScan或者@MapperScans,上面的AutoConfiguredMapperScannerRegistrar将不会被Import,AutoConfiguredMapperScannerRegistrar的作用便是默认配置一个MapperScannerConfigurer是一个BeanDefinitionRegistryPostProcessor
spring容器在启动的时候,会回调它的postProcessBeanDefinitionRegistry在这个方法里面会扫描所有的mapper接口,指定其class为MapperFactoryBean,从而在后续的实例化中,调用MapperFactoryBean#getObject生成mapper接口的动态代理对象
三丶mybatis扫描mapper接口,注册mapper的Beanfinition#1.MapperScannerConfigurer是如何被注册到spring容器中的#
上文中,我们说到,如果我们没有使用@MapperScan或者@MapperScans注解标注在配置类上面,那么会默认添加一个MapperScannerConfigurer,进行mapper接口的扫描注册工作
通常启动类都有这样的@MapperScan
@MapperScan上面存在@Import,会导入一个MapperScannerRegistrar,这是一个ImportBeanDefinitionRegistrar会在这里注册MapperScannerConfigurer的bean定义信息
其实就是把@MapperScan注解上的配置,绑定到MapperScannerConfigurer的属性上,
@MapperScan注解,可以指定mapper在的包,mapper接口必须标注的注解,Mapper接口动态代理对象生成使用的MapperFactoryBean等
2.MapperScannerConfigurer 如何进行扫描注册mapper的#
其实扫描注册的工作委托给了ClassPathMapperScanner,调用scan方法进行扫描注册
它是一个ClassPathBeanDefinitionScanner的子类,ClassPathBeanDefinitionScanner就是负责包路径扫描,注册BeanDefinition的
这里的扫描调用了ClassPathBeanDefinitionScanner的doScan方法,这个方法会根据包路径解析成Resouce对象,然后根据路径下的类包装成BeanDefinition(ScannedGenericBeanDefinition)
重点看下processBeanDefinitions
这里最关键的是definition.setBeanClass(this.mapperFactoryBeanClass),即将mapper接口的BeanDefinition类型指定为MapperFactoryBean,这样在spring后续实例化mapper的时候就调用MapperFactoryBean#getObject方法进行实例化了
至此我们学习了SpringBoot是如何和mybatis进行结合的,下面总结成一图
四丶Mapper bean的实例化#
当我们一个Service需要注入一个mapper的时候,会从Spring容器中找对应的实例,这时候边会涉及到这个mapper的实例化,但是我们mapper明明是一个接口呀,如何实例化昵?
虽然我们mapper是一个接口,但是注入到service属性上的是这个接口的实现类,它是mybatis动态代理后生成的对象。
这个实例化的入口便是AbstractBeanFactory#getBean方法
1.获取mapper对应的BeanDefinition#
这里获取的beanDefinition便是源自ClassPathMapperScanner注册到容器中的
2.实例化MapperFactoryBean#
我们上面说到过,实例化mapper需要调用MapperFactoryBean#getObject,那么首先需要实例化一个MapperFactoryBean
这里实例化MapperFactoryBean边是使用的createBean方法,然后Spring会使用反射调用构造方法实例化出MapperFactoryBean(Spring还存在使用CGLIB生成子类然后实例化的方式),其中调用的是
这个构造方法需要一个入参,表示Mapper接口类型,那么这个mapperInterface入参来自那么昵?ClassPathMapperScanner扫描完mapper接口,生成BeanDefinition后,还会在BeanDefinition中记录全限定类型,这个全限定类名将作为MapperFactoryBean的构造器入参
3.MapperFactory进行属性注入#
上面我们得到一个MapperFactoryBean,但是它构造出一个mapper需要借助SqlSession,这里使用的SqlSession其实是SqlSessionTemplate,我们指导MybatisAutoConfiguration会让容器中注入一个SqlSessionTemplate,那么spring是如何把这个SqlSessionTemplate设置到mapperFactoryBean的属性上的昵?
这一步就发生在populateBean方法中,其会调用applyPropertyValues,它会根据javaBean的内省,获取其需要SqlSessionFactory和SqlSessionTemplate,然后从容器中获取MybatisAutoConfiguration注入的实例,进行反射调用Set方法注入
4.MapperFactory的初始化#
MapperFactory的父类SqlSessionDaoSupport继承自DaoSupport(),其中DaoSupport又实现了InitializingBean,在Spring实例化MapperFactory,完成依赖注入后将回调InitializingBean#afterPropertiesSet
其中checkDaoConfig方法被MapperFactoryBean重写
这里会调用configuration.addMapper解析xml和mybaits相关的注解,然后进行注册和接口进行绑定,但是这一步解析xml操作通常不会真正进行,因为在创建SqlSessionFactory的时候已经进行了
5.调用MapperFactory#getObject实例化出一个mapper#
实例化出一个Mapper接口的动态代理对象,调用的是SqlSessesionTemplate#getMapper
那么到底mapper方法调用的时是如何操作数据库的昵?这一点我们后面继续说
至此我们知道了我们service注入的mapper其实是mybatis使用动态代理生成的对象,表面是一个什么方法实现都没有的接口,其实是动态代理"负重前行",下图展示了一个mapper被创造出来的全流程
五丶Mybatis 和spring事务的结合#
上面我们知道了xxMapper其实是一个jdk动态代理生成的对象 ,其InvocationHandler是MapperProxy
当mapper被调用其接口中声明的方法的时候,会调用到InvocationHandler#invoke这时候MapperProxy就会大显身手
1.MapperProxy#invoke#
MapperProxy内部使用了一个Map缓存方法和对应的执行器(MapperMethodInvoker),这个map通常来自MapperProxyFactory的ConcurrentHashMap属性。而真正方法的调用又委托给了MapperMethod#execute,MapperMethod根据方法调用的类型(增删改查)调用MapperProxy中的属性SqlSession(spring环境下的sqlSession实现类是SqlSessionTemplate)`对应的方法
2.SqlSessionTemplate 与mybatis spring事务#
SqlSessionTemplate实现了SqlSession接口,但是真正进行数据库操作的时候,都是委托给属性SqlSessionProxy,SqlSessionTemplate存在的意义在于"模板"——复用SqlSession,那么为什么需要复用,为何要复用?我们接着看下它的构造方法
可以看到,其内部的sqlSessionProxy是一个动态代理类,我们看下SqlSessionInterceptor,它是一个InvocationHandler
2.1 mybatis 和 spring结合后即使没有开启事务也能自动提交的原因#
上图可以看到如果事务并非交给spring管理(调用mapper执行单条增删改查的数据库操作,会自动提交事务)在反射调用sqlsession方法后,会进行事务提交。
//上面无事务注解 下面这条语句会调用到sqlsession的动态代理对象,进行自动提交public void test(){ xxxMapper.insertOne(xx); }
笔者校招的时候,面试官问过这个问题,我尼玛扯到了mysql的自动提交
原生mybatis使用sqlsession执行数据库操作后,需要手动调用其commit方法。spring环境的mybatis会自动提交,便是由于sqlsessionTemplate复用的sqlsession,其实是DefaultSqlsession的代理类,在执行数据库操作后,发现事务没有被spring管理便进行自动提交
2.2使用mapper执行多个数据库修改操作,具备事务的原因#
@Transactionalpublic void test(){ xxxmapper.insert1(); xxxmapper.insert2();}
众所周知,上面这个方法spring容器中的bean执行是具备事务的,那么为啥具备事务昵?
你可能会回答,容器中的bean是被BeanPostProcesser在bean完成实例化,依赖注入,后会被BeanPostProcessor后置处理器,依次进行处理。生成代理对象,其中存在AnnotationAwareAspectJAutoProxyCreator(@Aspect,@Before等注解感知能力的BeanPostProcessor,会将@Aspect标记的bean中的方法解析成Adivor然后使用ProxyFactory生成其代理对象)或者说任何AbstractAdvisorAutoProxyCreator,它会将使用Advisor 并基于CGLIB,或者java接口动态代理生成代理对象,其中便有BeanFactoryTransactionAttributeSourceAdvisor(一个Advior,真正事务代理的逻辑在TransactionInterceptor(一个Advise,实现事务开启,事务提交,回滚等逻辑)]中。以及TransactionAttributeSource(用于解析事务注解,判断方法是否需要开启事务,TransactionAttributeSourcePointcut这个pointcut 便是使用它进行判断方法是否需要被事务代理)它实现解析事务注解判断是否需要进行动态代理,实现事务功能。
但是这个问题归根结底还是没有说明,为什么mapper多次数据库修改操作,具备事务。
具备事务的前提是使用同一个连接,这样才能connection.commit提交事务,connnection.rollback,回滚事务,根本原理就在SqlSessionTemplate,对SqlSession的复用中
上图展示了mybatis在结合spring后,是如何让自己的sqlsession复用的,存于事务管理器中(基于ThreadLocal,存储事务信息,这也就是为啥多线程情况下事务效的原因)但是还是没有说明为啥复用了同一个connection
接下来我们看下使用SqlSessionFactory(mybatis自动配置类注入的DefaultSqlSessionFactory)开启一个sqlsession的逻辑
首先获取TransactionFactory事务工厂,这里使用的是SpringManagedTransactionFactory
它返回的事务是SpringManagedTransaction,然后创建一个Executor,然后封装成一个DefaultSqlSession返回,这里我们重点看下SpringManagedTransaction
这里Connection的获取便是从事务同步管理器的ThreadLocal中获取,如果没有connection一般是第一次数据库操作,那么这里会dataSource.getConnection()开启一个连接,然后交由事务同步管理器处理,后续便会复用此连接。
2.3 spring管理mybatis事务的时候,事务何时提交#
上面我们说到数据库操作都交由DefaultSqlSession处理,DefaultSqlSession是一个门面,其提交事务,最终还是调用到了SpringManagedTransaction的commit方法
这里可以看到SpringManagedTransaction#commit只有连接没有交给spring管理,并且连接并非自动提交才会生效,基本上调用这里的提交不会产生任何效果。
上面我们说到SqlSessionTemplate委托动态代理后的SqlSession执行操作的时候,会从事务同步管理器中获取SqlSession,如果没有那么new一个然后注册到事务同步管理器中
事务提交的奥秘就在registerSessionHolder中
这里的SqlSessionSynchronization是一个TransactionSynchronization对象,TransactionSynchronization接口提供了多个方法在事务不同的时期会在代理对象@Transactional标注的方法中进行回调
其中beforeCommit方法会在@Transactional标注的代理对象其业务逻辑执行完成后,如果需要提交事务,会被回调到,这时候就会调用SqlSession的commit方法进行提交事务
提交事务会继续委托给SpringManagedTransaction,可是其commit方法只会在事务不被spring管理的时候进行提交,如果事务被spring管理,@Transactional注解标注的代理对象方法执行后会调用PlatformTransactionManager#commit,这里会调用到DataSourceTransactionManager(如果是分布式事务那么是其他的实现类,基于数据源的事务都是调用此类)它会调用connection.commit 提交事务
2.4不加事务注解的mapper进行数据库操作,事务何时提交#
sqlSessionTemplate 中的SqlSessionInterceptor,在创建出SqlSession执行完数据库操作的时候,发现事务没有被Spring管理,此时便会立即提交事务
且getSqlsession无法从事务同步管理器中复用SqlSession,每次都是new出一个SqlSession 因为当前方法无事务注解,事务同步管理器不会处于活跃状态。提交事务会调用到SpringManagedTransaction其commit方法中判断得到事务没有被spring管理,便会调用connection.commit提交事务
2.5不加事务注解使用mapper执行多条数据库修改操作,会没有事务的原因#
public void test(){ xxxmapper.insert1(); xxxmapper.insert2();}
2.4中我们可以看到,insert1和insert2的执行,其实每次都会new出一个新的sqlsession,每一个sqlsession对应一个SpringManagedTransaction,每一次执行结束后都会立马提交事务,所有不具备事务。所有那怕test方法最后抛出异常,事务也会提交。
六丶Mybatis操作数据库#
上面我们研究了mybatis事务和spring事务的结合,并没有关注mybatis是如何进行数据库操作的,下面我们来看下mybatis是怎么拿到xml中的一条sql,把我们入参中的对象映射到sql中的占位符,执行sql然后将结果集解析为mapper方法的出参类型对象的。
前面几节的知识中提到,DefaultSqlSession是mybatis操作数据库的门面,增删改查都是交由它来实现
其中所有的查询操作都是调用select或者selectList方法,所有的新增,更新,删除都是调用update(这些都是数据库变更操作)方法,我们以查询操作为例
可以看到查询的操作最终委托给了Executor对象
1.Executor#1.1.Executor接口#该接口提供了改和查的基本功能(数据库的删除插入本质也是更新)提交和回滚缓存相关方法批处理刷新执行器关闭延迟加载1.2.BaseExecutor#
对Executor中的接口中的大部分方法进行了通用的实现,并且可以通过配置文件,或者手动指定执行器类型来让mybatis使用具体执行器实现(这里说的实现只有BatchExcutor,SimpleExecutor,ReuseExcutor),还提供了三个抽象方法(如下)让子类实现
doUpdatedoFlushStatementsdoQuery1.3.SimpleExecutor#
简单执行器,是 MyBatis 中默认使用的执行器,对BaseExecutor中的方法进行了简单的实现,(根据配置获取连接,根据连接获取Statement,执行sql,结果集映射)每执行一次 update 或 select,就开启一个 Statement 对象,用完就直接关闭 Statement 对象
1.4.BatchExecutor#
主要应对批量更新,插入,删除,一次向数据库发送多个SQL语句从而减少通信开销,从而提高性能。(对查找不生效)
批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用来提交它们,一次执行完成与数据库之间的交互。需要注意的是:JDBC中的批处理只支持 insert、update 、delete 等类型的SQL语句,不支持select类型的SQL语句。1.5.ReuseExecutor#
ReuseExecutor 不同于 SimpleExecutor 的地方在于 ReuseExecutor 维护了 Statement 缓存
ReuseExecutor顾名思义就是重复使用执行,其定义了一个Map<String, Statement>,将执行的sql作为key,将执行的Statement作为value保存,这样执行相同的sql时就可以使用已经存在的Statement,就不需要新创建了,从而避免SimpleExecutor这样多次进行参数拼接生成statement以提高性能
1.6.CachingExecutor#
CachingExecutor没有继承BaseExecutor,CachingExecutor 不具备 Executor 执行器功能,CachingExecutor 是一个装饰器, Mybatis 采用装饰者模式对 Executor 执行器提供了功能增强。CachingExecutor装饰器能够使得被装饰的Executor 具备二级缓存功能
下图是Configuration创建Executor的流程,如果全局配置指定了cachEnable,那么会使用CachingExcutor进行装饰,并且mybatis插件可以作用于Excutor,
1.7 Executor执行数据库操作流程#1.7.1 CachingExcutor装饰器模式实现二级缓存#
其装饰的作用就是让被装饰的Executor具备二级缓存的能力,在执行查询,更改等操作的时候会维护二级缓存,由于二级缓存并不常用(因为我们基本上都是微服务多实例,一个实例更新了二级缓存,如何同步到其他实例,我们需要自己实现cache,这有带来一致性等问题,一般是不开启二级缓存的)我们不继续深究二级缓存的原理
1.7.2 BaseExcutor模板方法设计模式#
BaseExcutor定义了基本的流程,对于子类具备差异的地方,留给子类自己去实现,从而达到高内聚的目的。以查询为例,一级缓存的刷新由BaseExecutor在合适的时机调用,首先从一级缓存中获取,如果缓存中存在,那么不会进行数据库查询操作,反之调用queryFromDatabase查询数据库,queryFromDatabase会调用到doQuery方法,这个方法由子类自己实现
至此我们可以解答下 一丶从一个问题开始——读已提交情况下mybatis一级缓存造成的问题,出现的原因便是一级缓存缓存了上一次的查询结果,由于我们执行的是同一个查询,mapperStatement(mapper方法全限定)一致,入参也样一致,也没有内存分页的内容,参数映射等内容也一致,便会命中缓存,所以读已提交的隔离级别,被mybatis 破坏
但是如果我们不加事务直接,便不会如此,因为不加事务直接,每一次查询操作都是new出的sqlsession,都会调用到不同的Executor,一级缓存是和Eexcutor中的一个属性(本质是一个map)这样一级缓存便是不同的对象,便不会命中缓存。
1.7.3 SimpleExecutor 如何查询数据库#
这里我们没有研究ReuseExecutor如何复用,其实使用map(key是执行查询的sql,value是statement)达到复用的目的也没有研究BatchExecutor,本质是执行更改操作的时候调用的是statement#addBatch,批量执行sql语句,二者使用的都很少,将不做过多赘述了
可以看到SimpleExcutor执行查询委托给了StatementHandler,它会用StatmentHandler创建statement,然后执行查询
我们总结下至此的执行流程,如下图
接下来我们探究下StatmentHandler是如何进行参数映射,使用Statment执行数据操作,并处理返回结果集的
2.StatmentHandler操作数据库#2.1.StatemenHanlder接口#
定义了StatementHandler的基本功能
准备语句 子类可以实现返回不同的Statement子类参数映射更新操作查询操作2.2BaseStatementHandler#
模板方法设计模式,提取公共的操作到父类,子类具备差异的地方使用抽象方法,交由子类实现
2.2RoutingStatementHandler#
主要是适配多个StatmentHandler的实现,有点装饰器适配器的意思
后续具体方法的实现都是调用delegate对应的方法,相当于RoutingStatementHandler 只是做了一个根据MappedStatement中的StatementType配置创建不同的StatmentHandler
2.3PrepareStatementHandler#
预处理Statement的handler,处理带参数允许的SQL, 对应JDBC的PreparedStatement(预编译处理)
2.4 SimpleStatementHandler#
最简单的StatementHandler,处理不带参数运行的SQL,对应JDBC的Statement
2.5 CallableStatementHandler#
存储过程的Statement的handler,处理存储过程SQL,对应JDBC的CallableStatement(存储过程处理)
下图是mybatis创建一个statementHandler,默认是RoutingStatementHandler,正在操作数据库的一般是PrepareStatmentHandler,并且mybatis插件会发挥作用
2.6 PrepareStatementHandler 如何创建一个Statement,并设置参数,执行查询的#2.6.1 prepare#
最终初始化一个statement是由子类PrepareStatementHandler调用connection.prepareStatement实现
2.6.2 参数映射#
可以看到参数映射的工作,交给了ParameterHandler(唯一的实现类是DefaultParameterHandler)具体设置参数的流程如下
//设置参数@Overridepublic void setParameters(PreparedStatement ps) throws SQLException { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); //1.获取sql语句的参数,ParameterMapping里面包含参数的名称类型等详细信息,还包括类型处理器 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { //2.遍历依次处理 for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); //3.OUT类型参数不处理 if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; //4.获取参数名称 String propertyName = parameterMapping.getProperty(); //5.如果propertyName是动态参数,就会从动态参数中取值。(当使用<foreach>的时候,MyBatis会自动生成额外的动态参数) if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { //6.如果参数是null,不管属性名是什么,都会返回null。 value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { //7.判断类型处理器是否有参数类型,如果参数是一个简单类型,或者是一个注册了typeHandler的对象类型,就会直接使用该参数作为返回值,和属性名无关。 value = parameterObject; } else { //8.这种情况下是复杂对象或者Map类型,通过反射方便的取值。通过MetaObject操作 MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); //9.获取对应的数据库类型 JdbcType jdbcType = parameterMapping.getJdbcType(); //空类型 if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } //10.对PreparedStatement的占位符设置值(类型处理器可以给PreparedStatement设值) try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } catch (SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } }}
可以看到最终使用TypeHandler设置参数,会调用到prepareStatement#setxxx方法设置参数
2.6.3 查询数据库,并转换结果集#
查询数据库的操作委托给了ResultSetHandler的实现类DefaultResultSetHandler
处理多结果集
存储过程存在多结果集的情况,
处理一行结果集
不存在嵌套子查询的时候,使用handleRowValuesForSimpleResultMap
这里出现一个类DefaultResultContext,实现了ResultContext,这是结果上下文,主要的职责是控制处理结果行的停止,配合rowBounds实现内存分页,后面的storeObject就是将一行对应的对象存在list(似乎对map这种出惨有特殊处理,对于嵌套子查询也有特殊处理)
这里的自动映射应该是处理,没有指定resultMap 凭借对象属性和数据库列名进行映射的情况 ,后面applyPropertyMappings 处理指定resultMap 中column和 property的情况,对于指定了TypeHandler的列,会使用TypeHandler进行设置(调用TypeHandler#getResult),自动映射的类使用MetaObject#setValue处理(反射设置属性)
七丶mybatis插件实现原理#1.拦截器接口#
public interface Interceptor { //拦截 Invocation :当前被拦截对象 参数 和被拦截方法 Object intercept(Invocation invocation) throws Throwable; //动态代理 default Object plugin(Object target) { return Plugin.wrap(target, this); } default void setProperties(Properties properties) { // NOP 可以在这里给拦截器赋值一些属性 }}2.Plugin.wrap(target, this)方法如何实现拦截#Plugin 实现了InvocationHandler——基于JDK动态代理读取注解信息,获取当前拦截器要拦截什么类的什么方法
注意获取方法的方式
Method method = sig.type().getMethod(sig.method(), sig.args());
getMethod方法是没有办法获取到私有方法的,所有无法拦截一个私有方法
获取被代理类的接口动态代理对象生成3.动态代理生成的对象是怎么被使用的#
如上我们知道了mybatis 是怎么支持插件的,根据拦截器上的信息生成动态代理对象,动态代理对象在执行方法的时候会进入拦截器的intercept拦截方法,那么动态代理的生成的对象在哪里被使用到昵
Configuration 类 也就是mybatis的大管家,在new一些mybatis四大对象的时候会使用到插件也就是说mybatis 只支持拦截ParmeterHandler,ResultSetHandler,StatementHandler,Excutor这四种对象在mybatis执行中的使用的四大对象其实是被动态代理后的对象,从而调用到插件功能
原文链接:
标签: #java二级教程