前言

上一章节我们学习了SqlSessionFactory的源码,SqlSessionFactory中的方法都是围绕着SqlSession来的.,那么SqlSession又是什么东东呢?这一章节我们就来揭开它的真面目吧!

1. SqlSession源码详解

SqlSession是一个接口类,文档上注明它是mybatis的一个主要的java 接口.官方都说很重要了,那肯定是非常重要.它的主要功能是执行命令,获取mapper和管理事务.
SqlSession有两个实现类,分别是DefaultSqlSessionSqlSessionManager,上一章节也出现过SqlSessionManager,大家还记得吗?SqlSessionManager既实现了SqlSession也实现了SqlSessionFactory.话不多说,详细学习以下两个类的源码吧!

2. DefaultSqlSession源码详解

2.1DefaultSqlSession源码

public class DefaultSqlSession implements SqlSession {/*** 全局配置对象*/private final Configuration configuration;/*** sql执行器*/private final Executor executor;/*** 是否自动提交*/private final boolean autoCommit;/*** 是否脏数据*/private boolean dirty;/*** 游标列表*/private List<Cursor<?>> cursorList;/**构造函数* @param configuration         核心配置对象* @param executor              sql执行器* @param autoCommit            是否自动提交*/public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}/**构造函数* @param configuration         核心配置对象* @param executor              sql执行器*/public DefaultSqlSession(Configuration configuration, Executor executor) {this(configuration, executor, false);}/**查询单条数据并返回对象* @param statement             the statement* @param <T>                   返回对象类型* @return                      对象*/@Overridepublic <T> T selectOne(String statement) {return this.selectOne(statement, null);}/**查询单条数据并返回对象* @param statement             the statement* @param parameter             sql语句参数* @param <T>                   返回对象类型* @return                      对象*/@Overridepublic <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.//调用selectListList<T> list = this.selectList(statement, parameter);if (list.size() == 1) {//结果为1条数据时,返回第0条return list.get(0);} else if (list.size() > 1) {//结果数大于1时,抛出异常throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {//返回nullreturn null;}}/**map查询* @param statement Unique identifier matching the statement to use.* @param mapKey    The property to use as key for each value in the list.* @param <K>       返回的map的key的泛型* @param <V>       返回的map的值的泛型* @return          返回map<K,V>*/@Overridepublic <K, V> Map<K, V> selectMap(String statement, String mapKey) {//调用selectMapreturn this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);}/**map查询* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param mapKey    The property to use as key for each value in the list.* @param <K>       返回的map的key的泛型* @param <V>       返回的map的值的泛型* @return          返回map<K,V>*/@Overridepublic <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {//调用selectMapreturn this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);}/**map查询* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param mapKey    The property to use as key for each value in the list.* @param rowBounds Bounds to limit object retrieval* @param <K>       返回的map的key的泛型* @param <V>       返回的map的值的泛型* @return          返回map<K,V>*/@Overridepublic <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {//调用selectList获取结果final List<? extends V> list = selectList(statement, parameter, rowBounds);//初始化一个默认的Map结果处理器final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());//初始化一个默认的结果上下文对象final DefaultResultContext<V> context = new DefaultResultContext<>();for (V o : list) {//遍历结果列表,放入context中context.nextResultObject(o);//使用Map结果处理器处理上下文mapResultHandler.handleResult(context);}//从结果处理器中获取处理完的map<K,V>返回return mapResultHandler.getMappedResults();}/**游标查询* @param statement Unique identifier matching the statement to use.* @param <T>       返回对象类型* @return          对象*/@Overridepublic <T> Cursor<T> selectCursor(String statement) {return selectCursor(statement, null);}/**游标查询* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param <T>       返回对象类型* @return          对象*/@Overridepublic <T> Cursor<T> selectCursor(String statement, Object parameter) {return selectCursor(statement, parameter, RowBounds.DEFAULT);}/**游标查询* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param rowBounds Bounds to limit object retrieval* @param <T>       返回对象类型* @return          对象*/@Overridepublic <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {try {//使用全局配置对象中的mappedStatements获取MappedStatement对象MappedStatement ms = configuration.getMappedStatement(statement);//调用sql处理器查询Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);//把游标注册到游标列表中,方便后续统一管理游标registerCursor(cursor);return cursor;} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/**查询列表* @param statement Unique identifier matching the statement to use.* @param <E>       结果的泛型* @return          结果列表*/@Overridepublic <E> List<E> selectList(String statement) {return this.selectList(statement, null);}/**查询列表* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param <E>       结果的泛型* @return          结果列表*/@Overridepublic <E> List<E> selectList(String statement, Object parameter) {//调用selectListreturn this.selectList(statement, parameter, RowBounds.DEFAULT);}/**查询列表* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param rowBounds Bounds to limit object retrieval* @param <E>       结果的泛型* @return          结果列表*/@Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {//使用全局配置对象中的mappedStatements获取MappedStatement对象MappedStatement ms = configuration.getMappedStatement(statement);//调用sql处理器查询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();}}/**带结果处理器的查询* @param statement Unique identifier matching the statement to use.* @param parameter A parameter object to pass to the statement.* @param handler   ResultHandler that will handle each retrieved row*/@Overridepublic void select(String statement, Object parameter, ResultHandler handler) {//调用selectselect(statement, parameter, RowBounds.DEFAULT, handler);}/**带结果处理器的查询* @param statement Unique identifier matching the statement to use.* @param handler   ResultHandler that will handle each retrieved row*/@Overridepublic void select(String statement, ResultHandler handler) {select(statement, null, RowBounds.DEFAULT, handler);}/**带结果处理器的查询* @param statement Unique identifier matching the statement to use.* @param parameter the parameter* @param rowBounds RowBound instance to limit the query results* @param handler*/@Overridepublic void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {try {//使用全局配置对象中的mappedStatements获取MappedStatement对象MappedStatement ms = configuration.getMappedStatement(statement);//调用sql处理器查询executor.query(ms, wrapCollection(parameter), rowBounds, handler);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/**插入数据* @param statement Unique identifier matching the statement to execute.* @return          影响的行数*/@Overridepublic int insert(String statement) {//调用insertreturn insert(statement, null);}/**插入数据* @param statement Unique identifier matching the statement to execute.* @param parameter A parameter object to pass to the statement.* @return          影响的行数*/@Overridepublic int insert(String statement, Object parameter) {//调用updatereturn update(statement, parameter);}/**更新数据* @param statement Unique identifier matching the statement to execute.* @return          影响的行数*/@Overridepublic int update(String statement) {//调用updatereturn update(statement, null);}/**更新数据* @param statement Unique identifier matching the statement to execute.* @param parameter A parameter object to pass to the statement.* @return          影响的行数*/@Overridepublic int update(String statement, Object parameter) {try {//设置脏数据状态为truedirty = true;//使用全局配置对象中的mappedStatements获取MappedStatement对象MappedStatement ms = configuration.getMappedStatement(statement);//调用sql处理器更新return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/**删除数据* @param statement Unique identifier matching the statement to execute.* @return          影响的行数*/@Overridepublic int delete(String statement) {//调用updatereturn update(statement, null);}/**删除数据* @param statement Unique identifier matching the statement to execute.* @param parameter A parameter object to pass to the statement.* @return          影响的行数*/@Overridepublic int delete(String statement, Object parameter) {//调用更新return update(statement, parameter);}/*** 提交事务*/@Overridepublic void commit() {commit(false);}/**提交事务* @param force     是否强制提交*/@Overridepublic void commit(boolean force) {try {//调用执行器提交事务executor.commit(isCommitOrRollbackRequired(force));//设置脏数据状态为falsedirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/*** 回滚事务*/@Overridepublic void rollback() {rollback(false);}/**回滚事务* @param force forces connection rollback*/@Overridepublic void rollback(boolean force) {try {//调用执行器回滚事务executor.rollback(isCommitOrRollbackRequired(force));//设置脏数据状态为falsedirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/**批量处理* @return        返回批量处理的结果*/@Overridepublic List<BatchResult> flushStatements() {try {//调用执行器进行批量操作return executor.flushStatements();} catch (Exception e) {throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}/*** 关闭sqlsession*/@Overridepublic void close() {try {//调用执行器进行关闭executor.close(isCommitOrRollbackRequired(false));//关闭所有游标closeCursors();//设置脏数据状态为falsedirty = false;} finally {ErrorContext.instance().reset();}}/*** 关闭全部游标*/private void closeCursors() {if (cursorList != null && !cursorList.isEmpty()) {for (Cursor<?> cursor : cursorList) {//遍历游标列表,进行关闭操作try {cursor.close();} catch (IOException e) {throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);}}//清空游标cursorList.clear();}}/**获取全局配置对象* @return        全局配置对象*/@Overridepublic Configuration getConfiguration() {return configuration;}/**获取Mapper<T>的动态代理类* @param type  Mapper interface class* @param <T>   返回对象的泛型* @return      Mapper接口的动态代理类*/@Overridepublic <T> T getMapper(Class<T> type) {//从核心配置对象中获取mapper, 实际是从mapperRegistry中获取mapper接口的代理工厂,使用代理工厂创建的动态代理类return configuration.getMapper(type, this);}/**获取数据库连接* @return      数据库连接*/@Overridepublic Connection getConnection() {try {//调用执行器获取连接return executor.getTransaction().getConnection();} catch (SQLException e) {throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);}}/*** 清除缓存*/@Overridepublic void clearCache() {//调用执行器清除本地缓存executor.clearLocalCache();}/**注册游标* @param cursor      游标对象* @param <T>         泛型*/private <T> void registerCursor(Cursor<T> cursor) {if (cursorList == null) {cursorList = new ArrayList<>();}//把游标加入到cursorListcursorList.add(cursor);}/**判断是否需求提交或者回滚* @param force         是否强制提交或回滚* @return              是否需求提交或者回滚*/private boolean isCommitOrRollbackRequired(boolean force) {//如果不是自动提交并且有脏数据返回true//如果强制提交或回滚返回truereturn (!autoCommit && dirty) || force;}/**将Collection或者数组类型的参数转换成Map* @param object      参数对象* @return            包装后的对象*/private Object wrapCollection(final Object object) {return ParamNameResolver.wrapToMapIfCollection(object, null);}/*** @deprecated Since 3.5.5*/@Deprecatedpublic static class StrictMap<V> extends HashMap<String, V> {private static final long serialVersionUID = -5741767162221585340L;@Overridepublic V get(Object key) {if (!super.containsKey(key)) {throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());}return super.get(key);}}}

DefaultSqlSession的方法分类
1.selectOne(),selectMap(),selectList()为一类,它们的实现都是通过selectList()得到的结构进行处理的
2.selectCursor()
3.select()
4.insert(),update(),delete()为一类,它们的实现都是通过update()
5.实现的事务相关的和其他方法

2.2 configuration.getMappedStatement(statement)

configuration的内部是维护了一个Map<String, MappedStatement> mappedStatements
在获取之前如果还有未完成的Statements,会先执行buildAllStatements()

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {if (validateIncompleteStatements) {buildAllStatements();}return mappedStatements.get(id);
}

那这个Map是什么时候插入值的呢,我们进行代码的跟踪,有没有很眼熟,是上一章的最后一节我们说到的解析所有的Mapper配置
在解析的过程里,会把MappedStatement的id属性作为key,MappedStatement作为值放入到mappedStatements中


ps: 查看调用关系快捷键 Ctrl + Alt + H

2.3 Executor

我们通过调用栈来看下Executor到底是什么时候进行初始化的呢

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//通过configuration创建一个Executorfinal Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;}final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);//通过configuration创建一个Executorfinal Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

public Executor newExecutor(Transaction transaction, ExecutorType executorType)代码

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {//为空返回默认的executorType  ExecutorType.SIMPLEexecutorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;//根据不同类型返回不同的Executor if (ExecutorType.BATCH == executorType) {//批处理型executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {//复用型executor = new ReuseExecutor(this, transaction);} else {//简单型executor = new SimpleExecutor(this, transaction);}//如果开启缓存,用缓存执行器对原始执行器进行包装,返回一个带缓存功能的执行器//默认是开启缓存的if (cacheEnabled) {executor = new CachingExecutor(executor);}//为所有的执行链加入当前的执行器插件executor = (Executor) interceptorChain.pluginAll(executor);return executor;
}

interceptorChain.pluginAll(executor)

public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;
}

interceptor.plugin(target)

default Object plugin(Object target) {return Plugin.wrap(target, this);
}

Plugin.wrap(target, this)

public static Object wrap(Object target, Interceptor interceptor) {//获取当前的interceptor上的注解中定义的Signature[],把需要代理的类和方法放入Map中Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();//匹配当前类有没有需要被代理的接口,有则返回代理对象,无返回原对象Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;
}

MyInterceptor和PageInterceptor类上的@Intercepts注解
此处定义的都是对Executor类的下面两个方法进行代理
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)
query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)


最后代理两次后返回的Executor类

3. SqlSessionManager源码详解

3.1 私有的构造方法

  //内部的SqlSessionFactory,为实现SqlSessionFactory相关接口而使用private final SqlSessionFactory sqlSessionFactory;//内部的SqlSession代理类,为实现SqlSession相关接口而使用private final SqlSession sqlSessionProxy;//ThreadLocal<SqlSession> 事务相关等方法操作是使用private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();//私有构造函数private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[]{SqlSession.class},new SqlSessionInterceptor());}

3.2 SqlSession的代理类SqlSessionInterceptor

private class SqlSessionInterceptor implements InvocationHandler {public SqlSessionInterceptor() {// Prevent Synthetic Access}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//从LocalThread中获取SqlSessionfinal SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();if (sqlSession != null) {//LocalThread中有SqlSession,则使用try {return method.invoke(sqlSession, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}} else {//LocalThread中无SqlSession,获取一个SqlSessiontry (SqlSession autoSqlSession = openSession()) {try {final Object result = method.invoke(autoSqlSession, args);//手动提交autoSqlSession.commit();return result;} catch (Throwable t) {//手动回滚autoSqlSession.rollback();throw ExceptionUtil.unwrapThrowable(t);}}}}
}

3.3 其他事务等方法

以下方法都是通过LocalThread中的SqlSession进行操作的

@Overridepublic void clearCache() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot clear the cache.  No managed session is started.");}sqlSession.clearCache();}@Overridepublic void commit() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot commit.  No managed session is started.");}sqlSession.commit();}@Overridepublic void commit(boolean force) {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot commit.  No managed session is started.");}sqlSession.commit(force);}@Overridepublic void rollback() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");}sqlSession.rollback();}@Overridepublic void rollback(boolean force) {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");}sqlSession.rollback(force);}@Overridepublic List<BatchResult> flushStatements() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot rollback.  No managed session is started.");}return sqlSession.flushStatements();}@Overridepublic void close() {final SqlSession sqlSession = localSqlSession.get();if (sqlSession == null) {throw new SqlSessionException("Error:  Cannot close.  No managed session is started.");}try {sqlSession.close();} finally {localSqlSession.set(null);}}

总结

SqlSession的两个实现类DefaultSqlSessionSqlSessionManager.SqlSessionManager是通过DefaultSqlSession实现功能的.而DefaultSqlSession是通过内部的Executor实现的.后续章节我们再对Executor相关的代码进行详细的学习

喜欢的小伙伴请动动小手关注和点赞吧,也可留言一起探讨怎样更好的学习源码!!!

文章链接

Mybatis源码学习(一)初探执行流程
Mybatis源码学习(二)配置文件解析到SqlSessionFactory构建
Mybatis源码学习(三)SqlSession详解
Mybatis源码学习(四)自定义Mapper方法执行流程
Mybatis源码学习(五)Executor和StatementHandler详解
Mybatis源码学习(六)结果集自动封装机制
Mybatis源码学习(七)mybatis缓存详解
Mybatis源码学习(八)Mybatis设计模式总结

学习资料整理

本人作为Java开发菜鸡,平时也收集了很多学习视频,在此分享给大家一起学习

整套VIP学习视频

架构师相关视频

扫码领取

更多资料链接

Java免费学习视频下载
Python免费学习视频下载
Web前端免费学习视频下载
人工智能免费学习视频下载
大数据免费学习视频下载
UI设计免费学习视频下载

Mybatis源码学习(三)SqlSession详解相关推荐

  1. nginx源码学习----内存池详解

    最近在进行监控平台的设计,之前一直觉得C/C++中最棘手的部分是内存的管理上,远不止new/delete.malloc/free这么简单.随着代码量的递增,程序结构复杂度的提高.各种内存方面的问题悄然 ...

  2. ElasticSearch 5.3源码学习 —— Segments_N 文件详解

    概览 Lucene当前活跃的Segment都会存在一个Segment Info文件里,也就是segments_N.如果有多个segments_N,那么序号最大的就是最新的. segments_N用Se ...

  3. 【博学谷学习记录】超强总结,用心分享 | 架构师 Mybatis源码学习总结

    Mybatis源码学习 文章目录 Mybatis源码学习 一.Mybatis架构设计 二.源码剖析 1.如何解析的全局配置文件 解析配置文件源码流程 2.如何解析的映射配置文件 Select inse ...

  4. Xposed源码剖析——app_process作用详解

    Xposed源码剖析--app_process作用详解 首先吐槽一下CSDN的改版吧,发表这篇文章之前其实我已经将此篇文章写过了两三次了.就是发表不成功.而且CSDN将我的文章草稿也一带>删除掉 ...

  5. php+mysql案例含源码_【专注】Zabbix源码安装教程—步骤详解(1)安装前准备

    一.实验环境准备 Rhel 7.6 x86_64(server) 192.168.163.72 Rhel 6.5 x86_64(agent) 192.168.163.61 均已配置操作安装光盘为YUM ...

  6. 【Mybatis源码学习】概述

    [Mybatis源码学习]概述 1.怎样下载源码 1.1 下载地址 1.2 导入Idea 1.2.1 环境 1.2.2 部署与打包 2.源码架构 2.1 核心流程三大阶段 2.1.1 初始化 2.1. ...

  7. mysql data文件夹恢复_【专注】Zabbix源码安装教程—步骤详解(2)安装并配置mysql...

    四.安装并配置mysql(1) 解压mysql-5.7.26.tar.gz与boost_1_59_0.tar.gz #tar -xvf mysql-5.7.26.tar.gz #tar -xvf bo ...

  8. mybatis源码阅读(三):mybatis初始化(下)mapper解析

    转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...

  9. React 源码系列 | React Context 详解

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

最新文章

  1. 活动报名 | “认知神经科学”助力人工智能突围的方法路径
  2. String ... String 三个点 jdk1.5的特性.才知道
  3. 自顶向下和自底向上测试的优缺点
  4. java并发编程之线程的生命周期详解
  5. Python+django建站入门篇:Hello world
  6. Fragment生命周期(转)
  7. Cocos2d-x 地图行走的实现3:A*算法
  8. renren-fast-vue:关闭ESLint检测
  9. JS:如何正确绘制文字
  10. 解决Android模拟器不能联网问题
  11. 批量搜狗提交软件-让搜狗快速收录你的网站
  12. 网易云音乐基于用户的推荐系统
  13. java开发购物系统菜单_Java控制台购物系统
  14. rabbitmq遇到的一些坑
  15. 计算机分区的优点,作为一个电脑老手来告诉你们:电脑分区真的是越多越好吗?...
  16. 3D角色模型欣赏:韩国3D设计师 Jiwoong Choi 科幻3d角色
  17. WPS中按条件拆分子表格(WPS更新了版本,为2019)
  18. 服务器维护与管理专业好就业吗,windows server服务器配置与管理专业就业方向
  19. 微信APP支付申请配置流程
  20. GCP: IAM的使用

热门文章

  1. Android项目实战
  2. 炸鸡大师Popeyes南京中央商场旗舰店开业
  3. android群发短信到通讯录所有联系人上并动态替换其中联系人信息
  4. python安卓app开发_[实例教程] 用python开发android应用
  5. 狮子难以猜透巨蟹的心(图
  6. 【Aladdin-Unity3D-Shader编程】之八-2D图常用的Shader效果
  7. android 开启一个定时线程_Android 定时器实现方式
  8. JavaScript程序基础(一)网页中引入Javascript的三种方法
  9. JS中的生成器知多少
  10. Java 的几种版本