MyBatis是一个持久层框架,实现了对JDBC操作的封装,主要用于简化JDBC操作中的一些相对繁琐的步骤,例如参数的映射,结果的映射等。
思考:为何使用MyBatis实现数据持久层应用?
稳定/灵活(支持动态SQL),功能强大(池/日志/缓存)
解耦,SQL的可维护性高,可复用性高
学习及使用成本低
所有框架都要解决一些共性问题(持久化),都是一种半成品,MyBatis也不例外,它作为一个Java框架要解决相关问题;
如何解决问题?采用怎样的架构解决问题?这是我们要学习的一个点。
战略:挟天子以令诸侯。
注意:此创建过程指单独测试MyBatisFramework时,DefaultSqlSessionFactory的创建过程。
思考:如果用Spring整合MyBatis,DefaultSqlSessionFactory对象是如何创建的?
答:在SqlSessionFactoryBean类中重写了getObject方法,最终还是调用了SqlSessionFactoryBuilder类中的.build方法构建DefaultSqlSessionFactory会话工厂对象。
提示:在package org.apache.ibatis.session;包下有个SqlSessionFactory顶层接口,此接口中定义了两个方法:查询源码
例码:InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
1.首先通过Resources对象将MyBatis全局配置文件读取到InputStream流对象中,形成字节信息;
2.在sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);之后开始执行配置信息封装
2.1XmlConfigBuilder对象从InputStream流对象中获取字节信息,将MyBatis全局配置文件封装到Environment环境对象中,并最终封装到Configuration配置对象中。
2.2XmlMapperBuilder对象从InputStream流对象中获取字节信息,将映射文件封装到MappedStatement映射声明对象中,
多个MappedStatement对象封装到Map集合中,并最终封装到Configuration配置对象中。
注意:在映射文件中一个元素对应一个MappedStatement对象,元素中封装了SQL语句;以namespace + id作为K,以SQL语句作为V。
3.SqlSessionFactoryBuilder对象需要依赖Configuration对象,通过.build方法构建DefaultSqlSessionFactory会话工厂对象。
注意:在Spring整合MyBatis之后,DefaultSqlSessionFactory会话工厂对象将交由Spring容器管理,且此对象是单例的;
拓展:在分布式软件架构中,DefaultSqlSessionFactory会话工厂对象可能是多例的,因为可能连接多个地方的不同数据库。
整合之后将取消mybatis-config.xml全局配置文件,文件中的数据源/事物等配置信息被封装到Druid阿里数据库连接池中。
思考:当DefaultSqlSessionFactory对象创建的时候,需要准备哪些元素?答:数据源/事务/连接池/映射文件
思考:当DefaultSqlSessionFactory对象被Spring整合的时候,需要哪些元素?答:数据源/事务/连接池/映射文件,其中前两者需要被封装到连接池中。
思考:如果没有数据源/事务/连接池/映射文件这些前提依赖条件,请问DefaultSqlSessionFactory对象能否被创建成功?
结论:整合了DefaultSqlSessionFactory对象,就相当于整合了整个MyBatisFramework,因为此对象是MyBatis的入口对象;
答:二者的区别就是创建DefaultSqlSessionFactory的方式不一样。
思考:此会话对象在Spring整合MyBatis后会交由Spring容器管理吗?答:不会,因为一个事务对应一个DefaultSqlSession,一亿个用户对应一亿个DefaultSqlSession。
例码:SqlSession session = sqlSessionFactory.openSession();
源码:private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {}
通过openSession();方法获取DefaultSqlSession会话对象,此对象上层接口为SqlSession,顶层接口为java.lang.AutoCloseable,此接口中定义了void close()方法;
DefaultSqlSession实现类封装了Executor执行器对象和Configuration配置对象;其中定义了许多操作数据库(CRUD)的方法,根据永远找其他对象做事的面向对象宗旨:
DefaultSqlSession依赖于Executor接口,依赖接口用于解耦;同时还依赖Configuration配置对象,因为此对象中封装了全局配置文件和映射文件配置信息;
源码:import org.apache.ibatis.executor.Executor;
源码:private final Executor executor;
源码:import org.apache.ibatis.session.Configuration;
源码:private final Configuration configuration;
DefaultSqlSession类中还依赖了(引入了)MappedStatement类,用于封装SQL语句;
源码:import org.apache.ibatis.mapping.MappedStatement;
Configuration对象中封装了全局配置文件信息和映射文件信息,其中映射文件中包含了SQL语句;
DefaultSqlSession类中的源码:
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在对应的增删改查方法中通过configuration.getMappedStatement(statement);方法获取SQL语句并封装到MappedStatement对象中;
思考:Configuration对象从何而来?
答:由SqlSessionFactoryBuilder类中的build方法:return new DefaultSqlSessionFactory(config);再到→
DefaultSqlSessionFactory类中的openSessionFromDataSource方法:return new DefaultSqlSession(configuration, executor, autoCommit);
源码:MappedStatement ms = configuration.getMappedStatement(statement);
注意:statement = namespace + id = K
思考:如果是基于Mapper接口开发,SQL语句如何拿到?
方法:基于Mapper接口类全名找到映射文件中的namespace;根据方法名找到SQL元素id,最终定位SQL = V。
结论:不管是单独使用MyBatis做测试还是Spring整合MyBatis,在DefaultSqlSessionFactory会话工厂对象创建的时候,映射文件中的SQL语句已经被封装到Map集合中了。
思考:SQL语句被封装到哪个Map集合中了?答:在Configuration类中第167行源码,有个HashMap
只是:使用Mapper接口开发和使用直接手动获取SQL开发,拿到SQL语句的方式不同;相同点是都是根据namespace + id的方式。
Executor执行器对象执行相应方法,传入SQL,返回结果;
思考:Executor对象从何而来?答:在DefaultSqlSessionFactory类中的openSessionFromDataSource方法中诞生;
源码:final Executor executor = configuration.newExecutor(tx, execType);
DefaultSqlSession类中源码:return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
注意:Executor执行器对象还不是真正的执行者,真正的执行者是StatementHandler接口下的实现类的对象;
query方法内部具体是如何执行的?后续步骤尚不明确,待补充......
但是当对应的Executor对象创建的时候,说明已经从数据库连接池中拿到了连接对象和SQL语句了;首先拿到连接对象,然后再拿到SQL语句;
源码中在BaseExecutor抽象类中依赖(引入)import org.apache.ibatis.logging.jdbc.ConnectionLogger;
Executor执行器对象家族继承体系
Executor顶层接口:
BaseExecutor抽象类:
BatchExecutor类:
ClosedExecutor静态最终类:
ReuseExecutor类:
SimpleExecutor类:对应MyBatis一级缓存
CachingExecutor类:对应MyBatis二级缓存
注意:如果是CachingExecutor二级缓存执行器,则无需再拿连接对象;说明CachingExecutor类中没有引入数据库连接对象,因为无需再访问数据库。
MyBatis是一个优秀的持久层框架,它支持普通SQL查询,实现了对JDBC的封装,能够更好的完成对象关系映射(ORM);
在用MyBatis开发中看不到JDBC代码,为了让开发人员更专注于SQL语句,所以MyBatis提供了接口开发;
在某种程度上用接口开发降低了性能,但是增强了程序的灵活性。
从上图中可以看到,MyBatis底层会基于Mapper接口通过反射机制创建代理对象(Proxy),当代理对象调用相应的增删改查方法,底层还是依赖DefaultSqlSession会话对象;
例码:AuthorDao dao = session.getMapper(AuthorDao.class);
例码:Map
注意:以上两行代码MyBatis底层本质上还是调用了DefaultSqlSession类中的public
思考:是会话工厂对象先创建还是代理对象先创建?答:会话工厂先创建,因为一个会话工厂对象对应一个数据库。
思考:如果创建了代理对象,那么此代理对象受不受Spring容器管理?答:受Spring容器管理。
思考:当代理对象创建时,SQL语句有没有拿到?答:没有,因为取SQL的任务必须交给DefaultSqlSession会话对象。
注意:此代理对象的创建过程尚不明确,待补充......
什么事代理设计模式?
代理模式分为:动态代理和静态代理
动态代理:代理类不明确代理对象的具体类型,代理类中直接用Object类型来接受代理对象,这样灵活一些;
静态代理:代理类明确代理对象的具体类型,可以在代理类中依赖代理对象的顶层接口,这样效率高一些。
SimpleExecutor类:对应MyBatis一级缓存
MyBatis一级缓存(SqlSession),当SqlSession关闭时一级缓存失效。在同一个事务内部多次执行同一个查询,后续查询会从此缓存取数据。
原理:在SimpleExecutor类的抽象父类BaseExecutor中有个PerpetualCache属性,是用protected修饰的;
源码:protected PerpetualCache localCache;
在PerpetualCache对象中封装了一个HashMap散列表,用于存放从数据库查询的数据信息;
源码:private Map
注意:底层以namespace + id作为K,以pojo缓存对象作为V,封装到散列表中。
测试:在pom.xml文件中添加log4j日志框架,以发送SQL的次数查看是否从缓存中调了数据。
CachingExecutor类:对应MyBatis二级缓存
MyBatis二级缓存(SqlSessionFactory),跨session共享数据时,可以打开和配置二级缓存。
注意:要想真正开启MyBatis二级缓存,必须要满足两个条件:
1.全局缓存开关必须为true,此配置需在MyBatis全局配置文件中配置;全局缓存开关默认是开启的,可到MyBatis官网查询验证。
例码:<setting name="cacheEnabled" value="true" />
2.映射文件中必须加cache标签
例码:<cache readOnly="false"/>
注意:MyBatis的全局缓存开关默认是开启的,一级缓存默认也是开启的;一级缓存不能手动开关闭,二级缓存可以手动开关闭;
若想开启二级缓存必须满足以上两个条件,若全局缓存开关为false,那么就算在映射文件中添加cache标签也不起作用;
注意:底层以namespace + id作为K,以pojo缓存对象作为V,封装到散列表中。(错,要看具体二级缓存算法和数据结构)
测试:在pom.xml文件中添加log4j日志框架,以发送SQL的次数查看是否从缓存中调了数据。
cache标签详解:(以下代码可在MyBatis官网查看)
readOnly="true" readOnly:是否设置缓存只读
注意:MyBatis二级缓存牵扯到pojo对象序列化的问题;
readOnly="true"表示每个SqlSession不会拷贝pojo对象到另一块内存空间,所以无需序列化pojo对象;
readOnly="false"表示每个SqlSession会拷贝pojo对象到另一块内存空间,必须要序列化pojo对象;
设置为true表示pojo无需序列化,这样效率会高一些;设置为false表示pojo必须序列化,这样数据安全一些,建议设置为false。
Spring中有两大类型的Bean对象
1.实现FactoryBean
注意:实现了FactoryBean
2.未实现FactoryBean
FactoryBean
说明:由此看出规则是Spring定义的,如果第三方框架中有工厂类,需要实现FactoryBean
Spring整合MyBatis_XML配置例码:
思考:为什么要整合SqlSessionFactoryBean对象?答:为了创建DefaultSqlSessionFactory会话工厂对象。
说明:此类属于package org.mybatis.spring;包下,其实现了FactoryBean
源码:public class SqlSessionFactoryBean implements FactoryBean
说明:Spring底层在IOC的时候会创建两个Bean对象。
此类部分属性源码:
private Resource configLocation;
private Configuration configuration;
private Resource[] mapperLocations;
private DataSource dataSource;
private TransactionFactory transactionFactory;
private Properties configurationProperties;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
从以上属性可以看出此类依赖Configuration对象/Resource[]数组(映射文件数组对象)/DataSource数据库连接池对象/SqlSessionFactoryBuilder会话工厂建造者对象等;
第339行源码:protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
第538行源码:return this.sqlSessionFactoryBuilder.build(configuration);
F3跟进build方法进入SqlSessionFactoryBuilder类内部源码:return new DefaultSqlSessionFactory(config);
说明:Spring底层首先会创建SqlSessionFactoryBean类型对象,然后通过此对象调用getObject()方法,最终创建DefaultSqlSessionFactory对象;
由此可以看出底层还是依赖了SqlSessionFactoryBuilder会话工厂建造者类来构建会话工厂对象。
总结:无论是单独测试MyBatis操作数据库,还是使用Spring整合MyBatis;DefaultSqlSessionFactory对象必须先被创建;
并且无论是前者还是后者,在会话工厂对象创建的时候,数据源/事务/连接池/映射文件等这些必要元素已经被读取并封装好,需要的时候方便取到;
只是:使用Mapper接口开发和使用直接手动获取SQL开发,拿到SQL语句的方式不同;相同点是都是根据namespace + id = K的方式取到SQL。
注意:以上笔记研究的是如何连接数据库并通过SQL语句操作数据库,以下笔记研究的是如何处理从数据库查询出来的结果。
单独使用MyBatis做CRUD测试
手动获取SQL开发:需要通过SqlSessionFactoryBuilder.build(InputStream)方法,创建DefaultSqlSessionFactory对象。
使用Mapper接口开发
使用Mapper接口代理对象开发:通过DefaultSqlSession的getMapper(Mapper Interface)方法,创建代理对象。
手写Mapper接口实现类开发:需要依赖DefaultSqlSessionFactory对象。
使用Spring整合MyBatis开发
手动获取SQL开发:Spring需要整合SqlSessionFactoryBean对象,DefaultSqlSessionFactory需要交给Spring容器管理。
使用Mapper接口开发
使用Mapper接口代理对象开发:Spring需要整合MapperScannerConfigurer对象,Spring扫描Mapper接口创建代理对象,然后交由Spring管理。
手写Mapper接口实现类开发:此实现类对象需要交给Spring容器管理。
注意:如果使用Spring整合MyBatis手写Mapper接口实现类开发,想要从Spring容器中获取实现类的对象,要么采用配置Bean标签的方式IOC,要么采用配置注解的方式IOC;
配置注解的方式前提是要配置包扫描器;如果使用Mapper接口代理对象开发,以上两部都无需配置,只要配置MapperScannerConfigurer对象Bean标签即可。