ConfigurationConfiguration是mybatis的全局配置类,保存了环境对象Enviroment(Environment代表数据源相关的环境),各种配置信息,以及注册解析为各种资源后表java训练。比如MapperRegister代表Mapper的注册中心,TypeHandlerRegistry是TypeHandler的注册中心,TypeAliasRegistry是TypeAlias的注册中心,还以Map的形式保存了MappedStatement、ResultMap、ParameterMaps等的映射关系,其中key在namespace+id的形式。SqlSessionFactorySqlSessionFactory是负责创建SqlSession的工厂。公共接口SqlSessionFactory{SqlSessionopenSession();SqlSessionopenSession(booleanautoCommit);SqlSessionopenSession(连接连接);SqlSessionopenSession(TransactionIsolationLevellevel);SqlSessionopenSession(ExecutorTypeexecType);SqlSessionopenSession(ExecutorTypeexecType,booleanautoCommit);SqlSessionopenSession(ExecutorTypeexecType,TransactionIsolationLevel级别);SqlSessionopenSession(ExecutorTypeexecType,Connection连接);ConfigurationgetConfiguration();}主要是通过openSession()创建SqlSession。此外,还有一个返回全局配置对象的方法getConfiguration()。可以猜测,其实现类应该直接或间接维护了对该Configuration的引用。观察openSession()的参数,猜测创建SqlSession有两种方式,一种是直接根据传入的数据库连接Connection。另一种方式是通过全局配置对象Configuration获取数据源环境Environment,获取环境。SqlSessionFactory的默认实现是DefaultSqlSessionFactory。SqlSessionSqlSession代表了某个数据库操作会话,所以SqlSession接口主要定义CRUD和事务操作的相关接口。另外还有一个重要的方法getMapper,可以返回Mapper对应的对象。?注意:SqlSessionCRUD相关方法的参数中第一个参数是statement的字符串,不是SQL语句,而是MappedStatement的ID。?SqlSession的默认实现是DefaultSqlSession。DefaultSqlSession不是线程安全的,需要用户自己保证线程安全,或者使用SqlSessionManager,它提供了SqlSession的线程安全管理。Executor负责实际执行数据库操作,并提供缓存能力。每个DefaultSqlSession里面都有一个Executor。在创建DefaultSqlSession实例时,会同时创建一个Executor对象,因此Excecutor和SqlSession是一对一绑定的。Executor可以分为两类:第一类是BaseExecutor及其子类。这类Executor具有操作数据库的能力,提供mybatis的一级缓存。第二种是CachingExecutor,它包装了一级执行器,提供二级缓存,当二级缓存未命中时,委托给内部一级执行器处理。MappedStatementMappedStatement表示在mapper.xml中定义的SQL节点。在创建Configuration对象和创建xml时,每个节点都会被解析成对应的MappedStatement对象。MappedStatement中的大部分属性都可以在xml的定义中找到相关的配置。四种处理器TypeHandler、ParameterHandler、StatementHandler、ResultSetHandlerTypeHandler类型处理器提供了Java对象和JDBCTYPE之间的转换。publicinterfaceTypeHandler{//将一个ParameterJava类型转换为JDBC类型执行voidsetParameter(PreparedStatementps,inti,Tparameter,JdbcTypejdbcType)throwsSQLException;//将结果集中的某列转换为Java类型TgetResult(ResultSetrs,StringcolumnName)throwsSQLException;TgetResult(ResultSetrs,intcolumnIndex)throwsSQLException;TgetResult(CallableStatementcs,intcolumnIndex)throwsSQLException;}通常我们可以扩展这个接口来实现JDBC的自定义枚举引用类型和转换类型。ParameterHandler参数处理器负责将PreparedStatement中的占位符替换成相应的参数。publicinterfaceParameterHandler{ObjectgetParameterObject();voidsetParameters(PreparedStatementps)throwsSQLException;}StatementHandler核心组件,与数据库交互,从数据库连接获取Statement对象,执行SQL,映射结果集等功能。publicinterfaceStatementHandler{Statementprepare(Connectionconnection,IntegertransactionTimeout)throwsSQLException;voidparameterize(Statementstatement)throwsSQLException;voidbatch(Statementstatement)throwsSQLException;intupdate(Statementstatement)throwsSQLException;Listquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException;CursorqueryCursor(Statementstatement)throwsSQLException;BoundSqlgetBoundSql();ParameterHandlergetParameterHandler();}ResultSetHandler,会提交给ResultSetHandler处理转换成Java对象的集合。publicinterfaceResultSetHandler{ListhandleResultSets(Statementstmt)throwsSQLException;CursorhandleCursorResultSets(Statementstmt)throwsSQLException;voidhandleOutputParameters(CallableStatementcs)抛出SQLException;MappedStatement定义的Sql片段,一个SqlSource可能由多个SqlNode组成。而BoundSql是SqlSource应用上下文环境(指用户输入的参数)后得到的对象,对SqlSource中的条件和参数进行过滤,形成实际的SQL(可能还有'?'占位符)。执行过程以一个简单的程序为切入点,看看mybatis中一个查询执行的主要流程。classMybatisTest{publicstaticvoidmain(){//第一步Stringresource="org/mybatis/example/mybatis-config.xml";InputStreaminputStream=Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);//STEP2try(SqlSessionsession=sqlSessionFactory.openSession()){//STEP3BlogMappermapper=session.getMapper(BlogMapper.class);//第四步博客blog=mapper.selectBlog(101);}}}形成一个全局配置对象。构建SqlSessionFactory的第一步就是读取全局配置文件,解析文件,形成我们的全局配置Configuration。解析过程主要是针对xml文件的各个节点进行解析。本文的目的是掌握主要流程,这里就不深入分析了。SqlSessionFacotryBuilder获取配置对象后,会通过Configuration对象来创建SqlSessionFactory对象。打开SqlSession获取SqlSessionFactory工厂对象后,可以通过openSession()获取SqlSession对象(默认为DefaultSqlSession)。DefaultSqlSession的构造函数依赖三个参数,分别是Configuration、Executor和autocommit。Configuration是全局的,但是Executor是和DefaultSqlSession一一绑定的(也就是说,在创建DefaultSqlSession的时候,会创建一个新的Executor,这个Executor不会暴露给其他的SqlSession),这样理解就对了清除一级缓存很有用。获取代理对象在调用SqlSession.getMapper()时,会先检查相应的类型是否已经从Congifuration注册中心注册,如果没有则抛出异常。如果存在,则通过MapperProxyFactory创建一个代理对象。MapperProxyFactory主要通过JDK动态代理创建代理对象。这个过程分为两步:首先创建JDK动态代理中的重要组件InvocationHandler。这里这个接口对应的实现是MapperProxy对象,MapperProxy保存了SqlSession的引用。然后通过Proxy.newProxyInstance()获取动态代理对象。?注意:即使是同一个SqlSession,getMapper每次获取的代理对象也不相同,但是对于同一个SqlSession创建的Mapper,MapperProxy引用的SqlSession是相同的。?代理对象通过反射调用执行方法。由于代理对象是由JDK动态代理创建的,其方法的执行最终会落入InvocationHandler中,也就是这里对MapperProxy的调用。而MapperProxy.invoke()调用MapperMethod.execute()。MapperMethod.execute()在SQL执行前后做了两件事,处理参数,统计执行结果,而核心的SQL执行还是交给了SqlSession对象。当Executor执行SqlSession执行CRUD时,会从Configuration中找到对应的MappedStatement对象,然后将MappedStatement传递给Executor对象执行。此时如果开启了二级缓存,CachingExecutor会先从MappedStatement的Cache中查找。如果缓存未命中,CachingExecutor会将搜索任务委托给内部的BaseExecutor。BaseExecutor会先从内部的LocalCache中查找,如果缓存未命中,则将SQL执行交给StatementHandler。StatementHandler执行SQLStatementHandler的执行过程分为两个阶段:准备阶段:该阶段的主要目的是获取Statement对象执行阶段:通过Statement执行SQL并得到Sql的执行结果后,会应用ResultSetHandler对结果进行转换设置为Java容器类。用一张粗略的图来概括一下上面的业务流程图:一级缓存和二级缓存什么是一级缓存?一级缓存是Executor内部的缓存机制。主要原理是BaseExecutor有一个字段叫localCache,用来存放本次session的执行结果。所以一级缓存是SqlSession的内部缓存(因为Executor和SqlSession是一一绑定的)。一级缓存的有效期为某个会话进程。一旦session关闭,一级缓存就会失效。另外,如果session中发生增删改写操作,一级缓存中的session也会失效。什么是二级缓存二级缓存就是MappedStatement的缓存,MappedStatement有一个Cache字段用来存放二级缓存。所以,我们常说二级缓存是跨SqlSession的。二级缓存默认关闭。如果要开启二级缓存,还必须保证mybatis设置中的Cache开启,对应的MappedStatement开启了缓存。那么二级缓存的实现原理是什么呢?我们知道二级缓存的使用者是CachingExecutor。CachingExecutor在执行查询之前,会先检查MappedStatement中是否存储了对应的缓存。如果缓存未命中,CachingExecutor将由内部的BaseExecutor执行数据库查询操作。CachingExecutor获取到查询结果后,会交给内部的TransactionCacheManager进行存储。只有事务提交完成后,TransactionCacheManager保存的缓存才会写入MappedStatement的Cache。读者可以自己想想这样做的目的。二级缓存脏读是因为二级缓存绑定了MappedStatement,也就是绑定了命名空间。假设存在这种情况,MappedStatementA缓存了User数据,但是MappedStatmentB也可能缓存了User表A做了修改,但是A中的缓存感知不到这种变化,缓存一直有效。这就造成了二级缓存脏读的问题。为了避免上述问题,首先我们在开发的时候需要保证相应的规范,让同一张表的操作尽可能在同一个命名空间下。如果真的需要在不同命名空间下操作同一张表,需要设置CacheRef让两者使用同一个缓存。自定义拦截器mybatis通过Interceptor接口为用户提供扩展机制。底层实现原理还是使用JDK的动态代理。当我们通过Configuraion.newExecutor()时,创建的Executor会被动态代理打包,达到拦截方法执行的目的。这里InvocationHandler的实现是一个Proxy对象,可以看到它的invoke()方法的实现。publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{//SignatureSetmethods=signatureMap.get(method.getDeclaringClass());if(methods!=null&&methods.contains(method)){//将连接点信息(方法、参数、目标对象)封装成一个Invocation对象,传入并由Interceptor执行returninterceptor.intercept(new调用(目标、方法、参数));}returnmethod.invoke(target,args);}catch(Exceptione){throwExceptionUtil.unwrapThrowable(e);}}mybatis与Spring的整合在mybatis与Spring的整合过程中,以下几个组件起着重要的作用:ClassPathMapperScanner:负责扫描相关的Mapper对象,并在容器中注册为BeanDefinition。MapperFactoryBean:注册的BeanDefinition为FactoryBean。当Mapper被实例化时,它的getObject()方法将被调用。主要流程还是通过JDK动态代理创建Mapper实例,但是这里关联的SqlSession是SqlSessionTemplate。SqlSessionTemplate(核心):SqlSessionTemplate虽然实现了SqlSession接口,但是其方法实现委托给SqlSession的一个动态代理,其InvocationHandler由SqlSessionInterceptor实现。它会在执行前获取到真正的SqlSession,从而保证SqlSession在Spring环境下的线程安全。转载自素人草