拉勾教育Java训练营学习感受/学习笔记–MyBatis
文章目录
- 拉勾教育Java训练营学习感受/学习笔记--MyBatis
- 1、普通jdbc操作流程以及问题
- 2、自定义持久层框架思路
- 2.1 使用端
- 2.2 框架端
- 3. 核心配置类Configuration
- 4. MyBatis工作流程以及源码分析
- 5. MyBatis基础概念及高级应用
- 6. 问题记录
1、普通jdbc操作流程以及问题
1.1 流程
-
加载数据库驱动
-
创建数据库连接
-
创建预执行语句
- statement:需要拼接sql;数据库需要对语句进行编译处理;不支持批量处理;
- preparedStatement:预编译的但开销较大,数据库可以直接执行(节省运行时间);支持批量处理;
- 执行sql语句
- 封装结果,映射为java对象
1.2 问题
-
代码重复耦合度高;
-
数据库配置信息和sql配置信息硬编码,操作不便;
2、自定义持久层框架思路
2.1 使用端
-
提供数据库连接信息配置文件
-
提供sql配置文件:crud的配置文件,要包括查询参数、返回值类型、sql语句
2.2 框架端
2.2.1 读取数据库配置文件和sql配置文件到内存
-
根据配置文件绝对路径,读取成字节流InputStream到内存中;
-
创建Resources工具类将配置文件读取为指定格式到内存中;
2.2.2 通过dom4j解析配置文件
- 创建JavaBean封装读取到的配置信息
- Configuration:数据库连接信息的封装类;
- MapperedStatement:sql配置信息的封装类
2.2.3 创建相关类进行操作
-
创建SqlSessionFactoryBuilder工厂类。利用建造者模式生产SqlSessionFactory;
类方法:build(inputStream);创建者模式,多种组合方式; -
创建SqlSessionFactory接口及其实现类DefaultSqlSession,生产SqlSession对象;
接口方法:openSession();生产SqlSession对象; -
创建SqlSession接口及其实现类DefaultSqlSession对象,执行封装的jdbc操作;
接口方法:selectList();selectOne(); -
创建Executor接口及其实现类SimpleExecutor,执行具体jdbc操作;
接口方法:query(Configuration,MappedStatement,Object…params);
3. 核心配置类Configuration
## 部分属性
protected Environment environment;
protected boolean aggressiveLazyLoading; //懒加载配置
protected boolean cacheEnabled; //是否开启二级缓存
protected boolean lazyLoadingEnabled; //懒加载配置
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry; // 二级缓存管理类
protected final InterceptorChain interceptorChain;
protected final TypeHandlerRegistry typeHandlerRegistry; //类型转换器配置
protected final TypeAliasRegistry typeAliasRegistry;
protected final LanguageDriverRegistry languageRegistry;
protected final Map<String, MappedStatement> mappedStatements; //语句对象集合
protected final Map<String, Cache> caches; //二级缓存需要使用
protected final Map<String, ResultMap> resultMaps;
protected final Map<String, ParameterMap> parameterMaps;
protected final Map<String, KeyGenerator> keyGenerators;
protected final Set<String> loadedResources;
protected final Map<String, XNode> sqlFragments;
protected final Collection<XMLStatementBuilder> incompleteStatements; //未完成的xml语句解析
protected final Collection<CacheRefResolver> incompleteCacheRefs;
protected final Collection<ResultMapResolver> incompleteResultMaps;
protected final Collection<MethodResolver> incompleteMethods;
protected final Map<String, String> cacheRefMap; //<cache-ref>标签解析结果
protected ProxyFactory proxyFactory; //mapper代理工程
- configuration是mybatis的核心配置类,包含了环境配置信息、数据库语句解析配置以及解析结果等;
4. MyBatis工作流程以及源码分析
4.1 Resources getResourcesAsStream(path); -->工具类读取配置文件为字节流、字符流等
4.2 SqlSessionFactoryBuilder类 –> 创建configuration对象,生产DefaultSqlSession对象
4.2.1 主要过程
- SqlSessionFactoryBuilder.build(resourceStream);
- Configuration config= XmlConfigurationBuilder(inputStream).parse();
- XmlConfigurationBuilder.pluginElement();解析插件配置
- XmlConfigurationBuilder.settingsElement(); 解析全局配置
- XmlConfigurationBuilder.mapperElement(); 解析核心配置文件中关于xxxMapper.xml文件的配置
- XMLMapperBuilder.parse(); 进行具体解析工作
- XMLMapperBuilder.configurationElement(); 解析mapper.xml文件中的cache-ref\cache\parameterMap\resultMap\select\update等标签
- XMLMapperBuilder.cacheRefElement(); ->MapperBuilderAssistant.useCacheRef();
- XMLMapperBuilder.cacheElement(); ->MapperBuilderAssistant.useNewCache()
- XMLMapperBuilder.parameterMapElement(); ->MapperBuilderAssistant.addParameterMap();
- XMLMapperBuilder.resultMapElements();
- XMLMapperBuilder.buildStatementFromContext();
- XMLStatementBuilder.parseStatementNode(); 解析select、update等标签;
- MapperBuilderAssistant.addMappedStatement();
将生成的mapperStatement对象放入到configuration;如果curretCache未赋值,抛出IncompleteElementException异常,并被捕获处理;MappedStatement.Builder.build()生成MappedStatement对象,并放入到configuration的mappedStatements成员变量中
- MapperBuilderAssistant.addMappedStatement();
- XMLStatementBuilder.parseStatementNode(); 解析select、update等标签;
- bindMapperForNamespace;
将mapper对应的接口实现类的代理工厂类和namespace绑定;Class<?> boundType=Resources.classForName(namespace);向configuration中的MapperRegistry成员变量中的Map
- XMLMapperBuilder.configurationElement(); 解析mapper.xml文件中的cache-ref\cache\parameterMap\resultMap\select\update等标签
- XMLMapperBuilder.parse(); 进行具体解析工作
- Configuration config= XmlConfigurationBuilder(inputStream).parse();
4.3 SqlSessionFactory接口 --> 创建SqlSession对象
4.3.1 接口方法
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean var1);
SqlSession openSession(Connection var1);
SqlSession openSession(TransactionIsolationLevel var1);
SqlSession openSession(ExecutorType var1);
SqlSession openSession(ExecutorType var1, boolean var2);
SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
SqlSession openSession(ExecutorType var1, Connection var2);
Configuration getConfiguration();
}
4.3.2 接口主要实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b0Ao10d9-1636810845875)(/Users/yjwu/Desktop/image-20211113163951438.png)]
4.3.3 主要过程和功能
- 创建executor对象;
- 创建transactionFactory对象;
- 创建DefaultSqlSession对象;
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
4.4 **SqlSession**接口
4.4.1 接口方法
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1);
<E> List<E> selectList(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
<K, V> Map<K, V> selectMap(String var1, String var2);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
<T> Cursor<T> selectCursor(String var1);
<T> Cursor<T> selectCursor(String var1, Object var2);
<T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
4.4.2 接口主要实现类
4.4.3 主要过程和功能
- 封装了API,具体操作由executor执行。insert、update、delete都是执行executor的update接口,先调用了BaseExecutor、CachingExecutor中的query(),然后调用BaseExecutor子类SimpleExecutor的doQuery();修改同理;
-
过程
-
1、根据statement的id,创建了MappedStatement对象;
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { List var6; try { MappedStatement ms = this.configuration.getMappedStatement(statement); var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler); } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6; }
-
2、调用executor.query()、executor.update();
-
4.5 Executo接口r --> 执行jdbc操作,模版模式
4.5.1 接口方法
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement var1, Object var2) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean var1) throws SQLException;
void rollback(boolean var1) throws SQLException;
CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);
boolean isCached(MappedStatement var1, CacheKey var2);
void clearLocalCache();
void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);
Transaction getTransaction();
void close(boolean var1);
boolean isClosed();
void setExecutorWrapper(Executor var1);
}
4.5.2 主要实现类
4.5.4 主要过程和功能
4.5.4.1 维护缓存;创建StatementHandler、Statement执行数据库操作;
4.5.4.2 过程:未开启二级缓存
-
BaseExecutor
- BaseExecutor成员变量PerpetualCache localCache 维护了一级缓存;
- simpleExecutor继承了父类baseExecutor,重写了父类的doQuery()、doUpdate(),这两个方法目的都是处理参数、获取StatementHandler和Statement对象,然后执行handler.query()或handler.update();
-
BaseExecutor.query();
-
1、根据MappedStatement对象获取BoundSql;根据ms、parameters、rowBounds、boundSql创建CacheKey对象;
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
-
2、继续调用BaseExecutor.query()重载方法:判断是否需要清除一级缓存,是则清除;然后从缓存中获取数据,未获取到数据则继续调用queryFromDatabase(), 进而继续调用其子类doQuery()方法。然后将查询的结果放入到一级缓存中,最后返回数据。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER); List list; try { list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { this.localCache.removeObject(key); } this.localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { this.localOutputParameterCache.putObject(key, parameter); } return list; }
-
3、this.doQuery() 是抽象方法,实际调用子类SimpleExecutor.doQuery();
- 3.1 通过configuration.newStatementHandler() 创建StatementHandler对象;
- 3.2 调用SimpleExecutor.prepareStatement() 创建statement对象、完成参数设置。该方法通过调用StatementHandler.prepare()创建statement对象,然后调用了StatementHandler.parameterize()设置statement的参数;
- 3.3 通过statementHandler.query()执行查询并返回结果
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); } return var9; }
-
4.5.3 过程:开启二级缓存
-
CachingExecutor
- CachingExecutor.query 通过TransactionalCacheManager tcm全局变量维护二级缓存,TransactionalCacheManager类有成员变量Map<Cache, TransactionalCache> transactionalCaches;
- tcm的key为MappedStatement对象的cache变量,同namespace下的mappedStatement对象拥有同一个Cache对象;
- 先判断二级缓存中是否存在;具体查询、修改操作由其全局变量delegate(先baseExecutor再simpleExecutor)完成;
- 注意:二级缓存,查询到的结果先放入到HashMap结构的entriesToAddOnCommit全局变量中,只有commit之后,才会将该变量中的数据刷新到真正的缓存对象cache中,然后才可以被查询到;同一个namespace中有增删改操作发生,就会清空二级缓存。
-
CachingExecutor.query();
1、获取MappedStatement的Cache对象,判断二级缓存TransactionalManager中是否含有Cache为大key、cacheKey为小key的数据;有则返回,否则进行数据库查询;
2、调用BaseExecutor.query();public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { this.flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, boundSql); List<E> list = (List)this.tcm.getObject(cache, key); if (list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } // 调用BaseExecutor.query();然后过程和未开启缓存一样; return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
4.6 StatementHandler接口
4.6.1 接口方法
public interface StatementHandler {
Statement prepare(Connection var1, Integer var2) throws SQLException;
void parameterize(Statement var1) throws SQLException;
void batch(Statement var1) throws SQLException;
int update(Statement var1) throws SQLException;
<E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;
<E> Cursor<E> queryCursor(Statement var1) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
4.6.2 接口主要实现类
4.6.3 主要过程和功能
4.6.3.1功能
- SimpleStatementHandler 管理statement对象并且执行不需要预编译的的sql语句;
- PreparedStatemenHandler 管理statemetn对象并且向数据库执行需要预编译的sql语句;
- RoutingStatementHandler:根据MappedStatement的statementType创建simpleStatement、preparementStatement
4.6.3.1 过程
-
1、BaseStatementHandler.prepare(); 创建statement对象;
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(this.boundSql.getSql()); Statement statement = null; try { statement = this.instantiateStatement(connection); this.setStatementTimeout(statement, transactionTimeout); this.setFetchSize(statement); return statement; } catch (SQLException var5) { this.closeStatement(statement); throw var5; } catch (Exception var6) { this.closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + var6, var6); } }
-
BaseStatementHandler.prepare();
调用instantiateStatement(),由子类重写,进而创建statement对象。
-
SimpleStatementHandler.instantiateStatement();
connection.createStatement()创建statement对象。 -
PreparedStatementHandler.instantiateStatement();
connection.prepareStatement()创建statement对象
-
-
-
2、设置preparedStatement的参数;
PreparedStatementHandler.parameterize();
调用paramenterHandler.setParameters()设置参数;public void parameterize(Statement statement) throws SQLException { this.parameterHandler.setParameters((PreparedStatement)statement); }
-
3、执行sql并处理查询结果;
- 执行Statement或者preparedStatement的execute(),然后执行resultSetHandler.handlerResultSets();
4.6.3.1 ParameterHandler接口和ResultSetHandler接口
- ParameterHandler接口:
- 参数处理器,为preparedStatement动态设置参数。在创建StatementHandler对象的时候创建,configuration.newParameterHandler
- DefaultParameterHandler.setParameters();
1、BoundSql对象中有集合属性parameterMappings,其中存储了xml中sql语句中的参数名称,用于通过反射从参数对象中获取指定属性值;
2、通过TypeHandler来处理jdbc类型和Java类型之间的数据映射;ParameterMapping实体有TypeHandler属性;
- ResultSetHandler接口:
- 结果映射器:将数据库查询结果映射为实体类。在创建StatementHandler对象的时候创建,configuration.newResultSetHandler
4.7 Statement接口
- 该接口是java.sql.Statement,由jdk提供;实现类为数据库厂商;
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rdv7mez4-1636810845880)(/Users/yjwu/Library/Application Support/typora-user-images/image-20211113172834245.png)]
5. MyBatis基础概念及高级应用
5.1 缓存
聊聊MyBatis缓存机制 - 美团技术团队 (meituan.com)
5.1.1 自带缓存
-
一二级缓存是底层数据结构都是hashmap,key=namespace:sqlid:params:sql:分页参数
-
一级缓存
-
默认开启,SqlSession级别,不同SqlSession之间隔离,当执行commit、rollback操作后就会清空;
-
一级缓存缓存的是结果对象,同一SqlSession多次查询的结果是同一个对象;
-
一级缓存可能会发生脏读,一个sqlsession命中缓存,无法读取到另一个sqlsession更新后的数据。
-
PreputalCache实现了mybatis中的cache接口,底层数据结构是HashMap。
-
BaseExecutor的构造方法中,实例化PreputalCache类型的localCache变量。此后,由BaseExecutor完成的query()查询,会先从localCache中获取对象;获取不到,才会调用queryFromDatabase()从数据库中查询,当查询到结果后先放入到localCache中再返回。
-
-
二级缓存
-
作用域是namespace级别,默认关闭,需要全局配置开启;mapper可以配置标签设置该namespace下二级缓存的刷新算法、刷新周期、存储对象的个数、缓存是否只读;同时,每个查询语句可以单独配置useCache=false设置二级缓存的关闭,设置flush=true开启刷新缓存(默认开启,一般地,commit操作后都会刷新缓存)。如果使用注解开发,可以在mapper接口上使用@ CacheNamespac注解进行相关配置,该注解还可以配置自定义缓存的实现类,在sql注解上使用@ option(usecache=false)关闭二级缓存,
-
因为二级缓存缓存的是结果的值,可能保存在内存、磁盘中,所以缓存对象必须实现序列化,这样才能从二级缓存中进行查询后通过反序列化返回。
-
CachingExecutor类使用BaseExecutor类完成实际的jdbc操作,其内部通过TransactionalCacheManager类来进行二级缓存的管理。该tcm变量的key为Cache接口的实现类,实际值为MappedStatement(查询语句)中Cache类型的cache变量;进而等同于Configuration中HashMap类型的caches成员变量中的一个value,而caches变量的key即解析sql配置文件时,从标签中解析出来的namespace值,默认即为namespace值。所以同一个namespace下的语句,他们再tcm中是同一个key
-
二级缓存以hashMap存储数据,无法实现分布式缓存;
-
5.1.2 自定义缓存
- 使用redis作为缓存
- @ NameSpace(implemention=PerpetualCache.class)设置二级缓存的实现类;
- mybatis默认使用PerpetualCache类实现Cache接口操作二级缓存,mybatis提供mybatis-redis包来实现分布式redis缓存;
5.2 mybatis插件机制
拦截器;intercepts注解、signature注解;
6. 问题记录
6.1 sqlSession线程安全问题、为什么要用线程不安全的defaultSqlSession
defaultSqlSession不是线程安全的,所以不能使用单例模式。sqlSessionFactory每次都创建一个新的sqlSession对象。SqlSessionTemplate是线程安全的,TransactionSynchronizationManager通过map缓存,key为SqlSessionFactyory,从中获取;SqlSessionManager是线程安全的,通过threadLocal实现。
6.2 设计模式
- 创造者模式
- 模版模式
- 工厂模式;
- 装饰者模式:
- 适配器模式:baseStatement