1.Mybatis的架构

1.1 Mybatis的框架分层

1.2 MyBatis的实现原理

mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过 SqlSessionFactory,SqlSession Executor,StatementHandler,ParameterHandler,ResultHandler和TypeHandler等几个处理器封装了这些过程

 执行器:Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)   参数处理器: ParameterHandler (getParameterObject, setParameters)   结构处理器 ResultSetHandler (handleResultSets, handleOutputParameters)    sql查询处理器:StatementHandler (prepare, parameterize, batch, update, query)

其中StatementHandler用通过ParameterHandler与ResultHandler分别进行参数预编译 与结果处理。而ParameterHandler与ResultHandler都使用TypeHandler进行映射。如下图:

2.Mybatis工作过程

通过读mybatis的源码进行分析mybatis的执行操作的整个过程,我们通过debug调试就可以知道Mybatis每一步做了什么事,我先把debug每一步结果 截图,然后在分析这个流程。

第一步:读取配置文件,形成InputStream

2.1 创建SqlSessionFacotry的过程

从debug调试看出 返回的 sqlSessionFactory 是DefaultSesssionFactory类型的,但是configuration此时已经被初始化了。查看源码后画如下创建DefaultSessionFactory的时序图:

2.2 创建SqlSession的过程

从debug调试 看出SqlSessinoFactory.openSession() 返回的sqlSession是 DefaultSession类型的,此SqlSession里包含一个Configuration的对象,和一个Executor对象。查看源码后画如下创建DefaultSession的时序图:

2.3 创建Mapper的过程

从debug调试可以看出,mapper是一个Mapper代理对象,而且初始化了Configuration对象,Executor的对象。查看源码后画如下创建Mapper的时序图:

2.4 执行CRUD过程

2.4.1 以select为例查看各步执行的源码

1.mapper.selectEmployeeList()其实是MapperProxy执行invoke方法,此方法显示是判断Method的方法是不是Object的toString等方法如果不是就执行MapperMethod

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 判断Method的方法是不是Object的toString等方法     if(Object.class.equals(method.getDeclaringClass())) {            try {                return method.invoke(this, args);            } catch (Throwable var5) {                throw ExceptionUtil.unwrapThrowable(var5);            }        } else {        //判断private final Map methodCache;这个map里面有没有这个方法的一级缓存,如果没            MapperMethod mapperMethod = this.cachedMapperMethod(method);            return mapperMethod.execute(this.sqlSession, args);        }    }    //查询一级缓存和设置一级缓存     private MapperMethod cachedMapperMethod(Method method) {        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);        if(mapperMethod == null) {            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());            this.methodCache.put(method, mapperMethod);        }        return mapperMethod;    }

经过上面的调用后进入MapperMethod里面执行

//判断sql命令类型public Object execute(SqlSession sqlSession, Object[] args) {        Object param;        Object result;        if(SqlCommandType.INSERT == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));        } else if(SqlCommandType.UPDATE == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));        } else if(SqlCommandType.DELETE == this.command.getType()) {            param = this.method.convertArgsToSqlCommandParam(args);            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));        } else if(SqlCommandType.SELECT == this.command.getType()) {        //我们测试的是select类型,则再判断这个方法的返回类型            if(this.method.returnsVoid() && this.method.hasResultHandler()) {                this.executeWithResultHandler(sqlSession, args);                result = null;            } else if(this.method.returnsMany()) {               //我们是查询列表,此方法执行                result = this.executeForMany(sqlSession, args);            } else if(this.method.returnsMap()) {                result = this.executeForMap(sqlSession, args);            } else {                param = this.method.convertArgsToSqlCommandParam(args);                result = sqlSession.selectOne(this.command.getName(), param);            }        } else {            if(SqlCommandType.FLUSH != this.command.getType()) {                throw new BindingException("Unknown execution method for: " + this.command.getName());            }            result = sqlSession.flushStatements();        }        if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");        } else {            return result;        }    }private  Object executeForMany(SqlSession sqlSession, Object[] args) {//将param做处理 自动处理为param1,param2..        Object param = this.method.convertArgsToSqlCommandParam(args);        List result;        if(this.method.hasRowBounds()) {            RowBounds rowBounds = this.method.extractRowBounds(args);            //调用该对象的DefaultSqlSession的selectList方法            result = sqlSession.selectList(this.command.getName(), param, rowBounds);        } else {            result = sqlSession.selectList(this.command.getName(), param);        }        return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;    }//处理参数方法 public Object convertArgsToSqlCommandParam(Object[] args) {            int paramCount = this.params.size();            if(args != null && paramCount != 0) {                if(!this.hasNamedParameters && paramCount == 1) {                    return args[((Integer)this.params.keySet().iterator().next()).intValue()];                } else {                    Map param = new MapperMethod.ParamMap();                    int i = 0;                    for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) {                        Entry entry = (Entry)i$.next();                        param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);                        String genericParamName = "param" + String.valueOf(i + 1);                        if(!param.containsKey(genericParamName)) {                            param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);                        }                    }                    return param;                }            } else {                return null;            }        }

调用DefaultSqlSession的selectList的方法

public  List selectList(String statement, Object parameter, RowBounds rowBounds) {        List var5;        try {        //获取MappedStatement对象            MappedStatement ms = this.configuration.getMappedStatement(statement);            //调用cachingExecutor执行器的方法            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);        } catch (Exception var9) {            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);        } finally {            ErrorContext.instance().reset();        }        return var5;    }//CachingExector的query方法public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {    //        BoundSql boundSql = ms.getBoundSql(parameterObject);        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);        //调用下2代码        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);    }    //2代码 public  List 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, parameterObject, boundSql);                List list = (List)this.tcm.getObject(cache, key);                if(list == null) {                //这里是调用Executor里的query方法 如果开启了缓存这掉CachingExecutor的 如果没有则是调用BaseExecutor的                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);                    this.tcm.putObject(cache, key, list);                }                return list;            }        }        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);    }

BaseExecutor的query方法

public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());        if(this.closed) {            throw new ExecutorException("Executor was closed.");        } else {            if(this.queryStack == 0 && ms.isFlushCacheRequired()) {                this.clearLocalCache();            }            List list;            try {                ++this.queryStack;                list = resultHandler == null?(List)this.localCache.getObject(key):null;                if(list != null) {                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);                } else {                //如果缓存中没有就从数据库中查询                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);                }            } finally {                --this.queryStack;            }            if(this.queryStack == 0) {                Iterator i$ = this.deferredLoads.iterator();                while(i$.hasNext()) {                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();                    deferredLoad.load();                }                this.deferredLoads.clear();                if(this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {                    this.clearLocalCache();                }            }            return list;        }    }//从数据库中查询private  List 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 {        //此处是调用子Executor的方法,ExecutorType默认是使用的SimpleExecutor            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;    }

SimpleExecutor的doQuery方法

public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {        Statement stmt = null;        List var9;        try {            Configuration configuration = ms.getConfiguration();            //创建StateMentHandler处理器            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);            //调用下3的方法            stmt = this.prepareStatement(handler, ms.getStatementLog());            //调用no4的方法            var9 = handler.query(stmt, resultHandler);        } finally {            this.closeStatement(stmt);        }        return var9;    }    //下3方法private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {        Connection connection = this.getConnection(statementLog);        Statement stmt = handler.prepare(connection);        //SatementHanlder 采用PreparedStatementHandler来实现此方法,而PreparedStatementHandler调用的是父接口ParameterHandler的方法        handler.parameterize(stmt);        return stmt;    }

ParameterHandler参数处理器的方法

public interface ParameterHandler {    Object getParameterObject();    //此方法是用DefaultParameterHandler实现的    void setParameters(PreparedStatement var1) throws SQLException;}

DefaultParameterHandler默认参数处理器的方法

public void setParameters(PreparedStatement ps) {        ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());        List parameterMappings = this.boundSql.getParameterMappings();        if(parameterMappings != null) {            for(int i = 0; i < parameterMappings.size(); ++i) {                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);                if(parameterMapping.getMode() != ParameterMode.OUT) {                    String propertyName = parameterMapping.getProperty();                    Object value;                    if(this.boundSql.hasAdditionalParameter(propertyName)) {                        value = this.boundSql.getAdditionalParameter(propertyName);                    } else if(this.parameterObject == null) {                        value = null;                    } else if(this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {                        value = this.parameterObject;                    } else {                        MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);                        value = metaObject.getValue(propertyName);                    }                   //这里用调用 TypeHandler类型映射处理器来映射                    TypeHandler typeHandler = parameterMapping.getTypeHandler();                    JdbcType jdbcType = parameterMapping.getJdbcType();                    if(value == null && jdbcType == null) {                        jdbcType = this.configuration.getJdbcTypeForNull();                    }                    try {                    //类型处理器设置参数映射                                               typeHandler.setParameter(ps, i + 1, value, jdbcType);                    } catch (TypeException var10) {                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);                    } catch (SQLException var11) {                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var11, var11);                    }                }            }        }    }

no4的方法

 public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {       //此处调用原生sql的处理器        PreparedStatement ps = (PreparedStatement)statement;        //发出原生sql命令        ps.execute();        //采用ResultHandler结果处理器对结果集封装        return this.resultSetHandler.handleResultSets(ps);    }

ResultHandler代码

public interface ResultSetHandler {    //此处调用的是DefaultResultSetHandler的方法     List handleResultSets(Statement var1) throws SQLException;    void handleOutputParameters(CallableStatement var1) throws SQLException;}

DefaultResultSetHandler的方法

public List handleResultSets(Statement stmt) throws SQLException {        ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());        List multipleResults = new ArrayList();        int resultSetCount = 0;        ResultSetWrapper rsw = this.getFirstResultSet(stmt);        List resultMaps = this.mappedStatement.getResultMaps();        int resultMapCount = resultMaps.size();        this.validateResultMapsCount(rsw, resultMapCount);        while(rsw != null && resultMapCount > resultSetCount) {            ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);            this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);            rsw = this.getNextResultSet(stmt);            this.cleanUpAfterHandlingResultSet();            ++resultSetCount;        }        String[] resultSets = this.mappedStatement.getResulSets();        if(resultSets != null) {            while(rsw != null && resultSetCount < resultSets.length) {                ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);                if(parentMapping != null) {                    String nestedResultMapId = parentMapping.getNestedResultMapId();                    ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);                    this.handleResultSet(rsw, resultMap, (List)null, parentMapping);                }                rsw = this.getNextResultSet(stmt);                this.cleanUpAfterHandlingResultSet();                ++resultSetCount;            }        }        return this.collapseSingleResultList(multipleResults);    }//处理结果集 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException {        try {            if(parentMapping != null) {                this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);            } else if(this.resultHandler == null) {                DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);                this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);                multipleResults.add(defaultResultHandler.getResultList());            } else {                this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);            }        } finally {            this.closeResultSet(rsw.getResultSet());        }    }private void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {        if(resultMap.hasNestedResultMaps()) {            this.ensureNoRowBounds();            this.checkResultHandler();            this.handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);        } else {            this.handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);        }    }   private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {        DefaultResultContext resultContext = new DefaultResultContext();        this.skipRows(rsw.getResultSet(), rowBounds);        Object rowValue = null;        while(this.shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {            ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, (String)null);            CacheKey rowKey = this.createRowKey(discriminatedResultMap, rsw, (String)null);            Object partialObject = this.nestedResultObjects.get(rowKey);            if(this.mappedStatement.isResultOrdered()) {                if(partialObject == null && rowValue != null) {                    this.nestedResultObjects.clear();                    this.storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());                }        //获取行的值                rowValue = this.getRowValue(rsw, discriminatedResultMap, rowKey, (String)null, partialObject);            } else {                rowValue = this.getRowValue(rsw, discriminatedResultMap, rowKey, (String)null, partialObject);                if(partialObject == null) {                    this.storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());                }            }        }        if(rowValue != null && this.mappedStatement.isResultOrdered() && this.shouldProcessMoreRows(resultContext, rowBounds)) {            this.storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());        }    }    String resultMapId = resultMap.getId();        Object resultObject = partialObject;        if(partialObject != null) {            MetaObject metaObject = this.configuration.newMetaObject(partialObject);            this.putAncestor(partialObject, resultMapId, columnPrefix);            this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);            this.ancestorObjects.remove(resultMapId);        } else {            ResultLoaderMap lazyLoader = new ResultLoaderMap();            resultObject = this.createResultObject(rsw, resultMap, lazyLoader, columnPrefix);            if(resultObject != null && !this.typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {                MetaObject metaObject = this.configuration.newMetaObject(resultObject);                boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();                if(this.shouldApplyAutomaticMappings(resultMap, true)) {                    foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;                }                foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;                this.putAncestor(resultObject, resultMapId, columnPrefix);                foundValues = this.applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;                this.ancestorObjects.remove(resultMapId);                foundValues = lazyLoader.size() > 0 || foundValues;                resultObject = foundValues?resultObject:null;            }            if(combinedKey != CacheKey.NULL_CACHE_KEY) {                this.nestedResultObjects.put(combinedKey, resultObject);            }        }        return resultObject;    }private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {        return resultMap.getAutoMapping() != null?resultMap.getAutoMapping().booleanValue():(isNested?AutoMappingBehavior.FULL == this.configuration.getAutoMappingBehavior():AutoMappingBehavior.NONE != this.configuration.getAutoMappingBehavior());    } private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {        List autoMapping = this.createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);        boolean foundValues = false;        if(autoMapping.size() > 0) {            Iterator i$ = autoMapping.iterator();            while(true) {           //这里使用了内部类对参数和结果集进行映射               DefaultResultSetHandler.UnMappedColumAutoMapping mapping;                Object value;                do {                    if(!i$.hasNext()) {                        return foundValues;                    }                    mapping = (DefaultResultSetHandler.UnMappedColumAutoMapping)i$.next();                    value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);                } while(value == null && !this.configuration.isCallSettersOnNulls());                if(value != null || !mapping.primitive) {                    metaObject.setValue(mapping.property, value);                }                foundValues = true;            }        } else {            return foundValues;        }    } private static class UnMappedColumAutoMapping {        private final String column;        private final String property;        private final TypeHandler> typeHandler;        private final boolean primitive;    //此处才类型器对结果进行映射        public UnMappedColumAutoMapping(String column, String property, TypeHandler> typeHandler, boolean primitive) {            this.column = column;            this.property = property;            this.typeHandler = typeHandler;            this.primitive = primitive;        }    }

JAVA进阶架构程序员福利:我这里还总结整理了比较全面的JAVA相关的面试资料,都已经整理成了

PDF版,这些都可以分享给大家,关注私信我:【806】,免费领取!

mybatis 原理_Mybatis的工作原理相关推荐

  1. 计算机输入输出设备说课稿,信息技术七年级西交大版 第三节 计算机系统的组成与工作原理计算机系统及工作原理说课稿 (共15张PPT)...

    <信息技术七年级西交大版 第三节 计算机系统的组成与工作原理计算机系统及工作原理说课稿 (共15张PPT)>由会员分享,可在线阅读,更多相关<信息技术七年级西交大版 第三节 计算机系 ...

  2. c语言智能插座多线程原理,智能插座原理—智能插座的工作原理是什么

    近年来,物联技术有了一个更高层次的发展,并且物联技术深入到智能家居领域当中,与智能化家电结合,设计制造出很多非常棒的智能电子产品.其中,很多插座厂商也朝着智能化的方向发展,智能插座产生.那么智能插座的 ...

  3. 现在使用计算机器工作原理,计算机显示器工作原理(17页)-原创力文档

    计算机显示器工作原理 计算机显示器工作原理 显示器技术 长宽比和可视区域 多频扫描显示器 显示器连接 显示器的颜色深度 LCD显示器 LCD的功能和特性 CRT显示器 CRT的功能和特性 LCD和CR ...

  4. Linux运维系列总结-Linux系统启动过程、WEB工作原理、DHCP工作原理、DNS解析原理、NFS网络文件系统、FTP文件传输协议、PXE+KICKSTART自动安装系统

    Linux运维系列总结-Linux系统启动过程.WEB工作原理.DHCP工作原理.DNS解析原理.NFS网络文件系统.FTP文件传输协议.PXE+KICKSTART自动安装系统 1.Linux系统的启 ...

  5. MyBatis 核心对象,工作原理及源码解读

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) Mybatis工作原理 InputStream inputStream = Resources.getResourceAsStream(reso ...

  6. MyBatis核心流程以及工作原理

    MyBatis核心对象 根据以下这四大核心对象,我们就能理清MyBatis的工作原理. SqlSession对象,该对象中包含了执行SQL语句的所有方法.类似于JDBC里面的Connection. E ...

  7. 计算机光盘工作原理,CD-ROM的工作原理

    CD-ROM的工作原理 笔记本外置CD-ROMCD-ROM光盘由碳酸脂做成,中心带有直径15mm的孔洞.在盘基上浇铸了一个螺旋状的物理磁道,从光盘的内部一直螺旋到最外圈,磁道内部排列着一个个蚀刻的&q ...

  8. 计算机生活工作原理,计算机基本工作原理是什么?

    在介绍计算机的工作原理之前,首先了解一下什么是计算机指令. 简单的理解,指令指的是能被计算机识别并执行的二进制代码,它规定了计算机能够完成的某一种操作.一条指令通常由 2 部分组成,分别是操作码和操作 ...

  9. java jsp 原理_jsp的工作原理是什么

    JSP全名为Java Server Pages,其根本是一个简化的Servlet.一种动态网页技术标准.它实现了Html语法中的java扩展(以 形式).JSP与Servlet一样,是在服务器端执行的 ...

最新文章

  1. Joda-Time中两个日期之间的天数
  2. Python列表解析式总结
  3. 哈尔滨工业大学计算机学院官网,哈尔滨工业大学计算机学院 唐好选 tanghx@hope.hit...
  4. [Tips]Torch功能点记录
  5. POJ 3984 迷宫问题
  6. instrumentation模拟很多activity的操作
  7. 详解深度语义匹配模型DSSM和他的兄弟姐妹
  8. python全栈开发_day25_面向对象的接口,多态,鸭子类型,抽象父类,析构格式化,反射和断言...
  9. Serv-U FTP - v15.3.1.155特别版
  10. 首席CEO乔布斯他是如何拉住了苹果快破产的边缘?他的运营方法是什么呢?
  11. 1: 上山拜师--ARM简介
  12. (附源码)springboot大学生就业质量调查分析系统 毕业设计161457
  13. 福昕阅读器给pdf创建目录方法
  14. 矿物质饲料补充剂的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  15. 尺寸有点太大了吧,我已经受不鸟了
  16. 如何在ESP8266中使用深度睡眠模式降低功耗
  17. Unity 从零开始的2D游戏开发 —— 角色移动脚本
  18. 艺术品经营单位备案申报材料和艺术品经营单位备案申请表格式
  19. 计算机专业的学生答辩稿,计算机专业毕业论文答辩自述稿范文
  20. JAVA——勾股定理

热门文章

  1. 谁与争锋,2020腾讯广告算法大赛初赛正式启动
  2. python爬虫外快_利用Python爬虫轻松挣外快的几个方法(值得收藏)
  3. 视频号、抖音、海外Tiktok到底该选择那个平台更好
  4. 算法题解题方法技巧及典例汇总
  5. 吴恩达|机器学习作业3.1前馈神经网络
  6. Java并发(三)——线程池
  7. Linux编程(9)_进程
  8. 用Kubeadm安装K8s后,kube-flannel-ds一直CrashLoopBackOff
  9. table中添加下拉框
  10. dataframe drop_Pandas数据结构Series和DataFrame基础详解