Mybatis源码阅读(三):结果集映射3.2 —— 嵌套映射

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************
前言

这段时间疫情原因躺在家做咸鱼,代码也没怎么敲,源码也没怎么看,博客拖更了一个月,今天心血来潮继续读了点源码,晚上正好抽空发个博客,证明我还活着。

关于结果集映射,在一个月前的博客中已经将简单映射给讲述完毕,在实际应用中,除了单表查询以外,还可能通过连表查询多张表的记录,这些记录需要映射成多个java对象,而对象之间存在一对一、一对多等复杂的关联关系,这时候就需要嵌套映射。
handleRowValues

在前面的一篇博客中提到,结果集映射的核心方法是handleRowValues,在这个方法中,会先判断ResultMap是否存在嵌套映射,如不存在就视为简单结果集映射,简单映射的处理在上一篇博客已经讲解完毕,本篇博客讲述的是嵌套映射

/*** 结果集映射核心方法** @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();// 嵌套映射handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {// 简单结果集映射(单表)handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}

handleRowValuesForNestedResultMap

该方法是处理嵌套映射的核心方法,有以下主要步骤

通过skipRows方法定位到指定的记录行
通过shouldProcessMoreRows方法检测是否能够继续映射结果集中剩余的记录行
调用resolveDiscriminatedResultMap方法,根据ResultMap中记录的Discriminator对象以及参与映射的记录行中相应的列值,决定映射使用的ResultMap对象。
通过createRowKey方法为该行记录生成CacheKey,CacheKey作为缓存中的key值,同时在嵌套映射中也作为key唯一标识一个结果集对象。
根据上面步骤生成的CacheKey查询DefaultRe.nestedResultObjects集合,这个字段是一个HashMap,在处理嵌套映射过程中生成的所有结果对象,都会生成相应的CacheKey并保存到该集合。
检测<select>节点中resultOrdered属性的配置,该设置仅对嵌套映射有效。当Ordered属性为true时,则认为返回一个主结果行
通过getRowValue,完成当前记录行的映射操作并返回结果对象,其中还会讲结果对象添加到nestedResultObjects集合中。
通过storeObject方法将生成的结果对象保存在ResultHandler中。

handleRowValuesForNestedResultMap方法代码如下。、

/*** 处理嵌套映射* @param rsw* @param resultMap* @param resultHandler* @param rowBounds* @param parentMapping* @throws SQLException*/
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {final DefaultResultContext<Object>  resultContext = new DefaultResultContext<>();// 获取结果集ResultSet resultSet = rsw.getResultSet();// 定位到指定的行skipRows(resultSet, rowBounds);Object rowValue = previousRowValue;// 检测在定位到指定行之后,是否还有需要映射的数据while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {// 得到本次查询使用的ResultMapfinal ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);// 为该行记录生成CacheKey,作为缓存中的key值final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);// 根据缓存key先获取映射缓存Object partialObject = nestedResultObjects.get(rowKey);// 检测select节点中的resultOrder属性。该属性只针对嵌套映射有效。// 当true时则认为返回一个主结果行时,不会记录nestedResultObjectif (mappedStatement.isResultOrdered()) {// 主结果对象发生变化if (partialObject == null && rowValue != null) {// 清空缓存集合nestedResultObjects.clear();// 保存主结果对象storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}// 获取映射结果rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);} else {rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);if (partialObject == null) {// 将生成结果保存到ResultHandlerstoreObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}}}if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);previousRowValue = null;} else if (rowValue != null) {previousRowValue = rowValue;}
}

前面一部分代码的分析在简单映射中已经描述过,不记得的朋友可以查看一下上一篇源码阅读文章,这里从createRowKey方法开始。
createRowKey

createRowKey方法主要负责生成CacheKey,该方法构建CacheKey的过程如下。

尝试使用<idArg>节点或者<id>节点中定义的列名以及该列在当前记录行中对应的列值生成CacheKey
如果ResultMap中没有定义这两个节点,则有ResultMap中明确要映射的列名以及它们在当前记录行中对应的列值一起构成CacheKey对象
经过上面两个步骤后如果依然查不到相关的列名和列值,且ResultMap的type属性明确指明了结果对象为Map类型,则有结果集中所有列名以及改行记录行的所有列值一起构成CacheKey
如果映射的结果对象不是Map,则由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey

createRowKey代码如下

/*** 创建一个CacheKey,作为缓存中的key值,在嵌套映射中也作为key唯一标识一个结果对象* @param resultMap* @param rsw* @param columnPrefix* @return* @throws SQLException*/
private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {final CacheKey cacheKey = new CacheKey();// 将resultMap的id属性作为CacheKey的一部分cacheKey.update(resultMap.getId());// 查找ResultMapping集合List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);// 没找到if (resultMappings.isEmpty()) {if (Map.class.isAssignableFrom(resultMap.getType())) {// 由结果集中的所有列名以及当前记录行的所有列值一起构成CacheKeycreateRowKeyForMap(rsw, cacheKey);} else {// 由结果集中未映射的列名以及它们在当前记录行中的对应列值一起构成CacheKey对象createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);}} else {// 由ResultMapping集合中的列名以及它们在当前记录行中相应的列值一起构成CacheKeycreateRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);}// 如果在上面的过程没有找到任何列参与构成CacheKey对象,则返回NullCacheKeyif (cacheKey.getUpdateCount() < 2) {return CacheKey.NULL_CACHE_KEY;}return cacheKey;
}

其中,getResultMappingsForRowKey方法首先检查ResultMap中是否定义了idArg或者id节点,如果是则返回idResultMappings集合,否则返回propertyResultMappings集合

/*** 获取ResultMapping集合* @param resultMap* @return*/
private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {//首先检查resultMap中是否定义了idArg节点或者id节点List<ResultMapping> resultMappings = resultMap.getIdResultMappings();if (resultMappings.isEmpty()) {// propertyResultMappings集合记录了除id和constructor节点以外的ResultMapping对象resultMappings = resultMap.getPropertyResultMappings();}return resultMappings;
}

createRowKeyForMap、createRowKeyForUnmappedProperties和createRowKeyForMappedProperties三个方法核心逻辑都是通过CacheKey的update方法,将指定的列名以及它们在当前记录行中相应的列值添加到CacheKey,使之成为CacheKey对象的一部分。

这里只介绍createRowKeyForMappedProperties

/*** 核心逻辑是通过CacheKey.update方法,将指定的列名以及它们在当前记录行中相应的列值添加到CacheKey* @param resultMap* @param rsw* @param cacheKey* @param resultMappings* @param columnPrefix* @throws SQLException*/
private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {for (ResultMapping resultMapping : resultMappings) {// 如果存在嵌套映射,并且resultSet不为空if (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null) {// 如果存在嵌套映射,递归调用该方法处理final ResultMap nestedResultMap = configuration.getResultMap(resultMapping.getNestedResultMapId());createRowKeyForMappedProperties(nestedResultMap, rsw, cacheKey, nestedResultMap.getConstructorResultMappings(),prependPrefix(resultMapping.getColumnPrefix(), columnPrefix));} else if (resultMapping.getNestedQueryId() == null) {// 忽略嵌套查询// 获取该列名称final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);// 获取该列相应的TypeHandlerfinal TypeHandler<?> th = resultMapping.getTypeHandler();// 获取映射的列名List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {// 获取列值final Object value = th.getResult(rsw.getResultSet(), column);if (value != null || configuration.isReturnInstanceForEmptyRow()) {// 将列值和列名添加到CacheKey中cacheKey.update(column);cacheKey.update(value);}}}}
}

getRowValue方法

getRowValue方法主要负责对数据集中的一行记录进行映射。在处理嵌套映射的过程中,会调用getRowValue方法,完成对记录行的映射,步骤如下。

检测外层对象是否已经存在

如果外层对象不存在

调用createRowObject方法创建外层对象
将外层对象添加到DefaultResultSetHandler.ancestorObject集合中,其中key是ResultMap的id,value为外层对象。
通过通过applyNestedResultMappings方法处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应的属性中。
将外层的ancestorObject集合中移除
将外层对象保存到nestedResultObjects集合中。

如果外层对象已存在

将外层对象添加到ancestorObjects集合中
通过applyNestedResultMappings方法处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应属性中
将外层对象从ancestorObjects集合中移除。

getRowValue方法代码如下

/*** 完成对嵌套查询记录的映射* @param rsw* @param resultMap* @param combinedKey* @param columnPrefix* @param partialObject* @return* @throws SQLException*/
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {final String resultMapId = resultMap.getId();Object rowValue = partialObject;if (rowValue != null) {// 外层对象存在final MetaObject metaObject = configuration.newMetaObject(rowValue);// 将外层对象添加到ancestorObjectsputAncestor(rowValue, resultMapId);// 处理嵌套映射,其中会将生成的结果对象设置到外层对象的相应属性中applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);// 将外层对象从ancestorObjects移除ancestorObjects.remove(resultMapId);} else {// 外层对象不存在final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 创建外层对象rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 检测是否开启自动映射if (shouldApplyAutomaticMappings(resultMap, true)) {// 自动映射foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 处理ResultMap找那个明确需要映射的列foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;putAncestor(rowValue, resultMapId);// 处理嵌套映射,将生成的结果对象设置到外层对象的相应的属性中foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;// 将外层对象从ancestorObjects集合中移除ancestorObjects.remove(resultMapId);foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}if (combinedKey != CacheKey.NULL_CACHE_KEY) {// 将外层对象添加到nestedResultObjectsnestedResultObjects.put(combinedKey, rowValue);}}return rowValue;
}

applyNestedResultMappings方法

处理嵌套逻辑的核心在这个方法中,该方法会遍历ResultMap.propertyResultMappings集合中记录的ResultMapping对象,并处理其中的嵌套映射。该方法步骤如下。

获取ResultMapping.nestedResultMapId字段值,该值不为空则表示存在相应的嵌套映射要处理。同时还会检测ResultMapping.resultSet字段,它指定了要映射的结果及名称,该属性的映射在前面的handleResultSets方法中完成。
通过resolveDiscriminatedResultMap方法确定嵌套映射使用的ResultMap对象
处理循环引用的场景,如果不存在循环引用的情况,则继续后面的映射流程。如果存在循环引用,则不在创建新的对象,而是重用前面的对象
通过createRowKey方法为嵌套对象创建CacheKey。该过程除了根据嵌套对象的信息创建CacheKey,还会与外层对象的CacheKey合并,得到全局唯一的CacheKey
如果外层对象中用于记录当前嵌套对象的属性为Collection并且未初始化,则会通过instantiateCollectionPropertyIfAppropriate方法初始化该对象
根据association、collection等节点的notNullColumn属性,检测结果集中相应的列是否为空
调用getRowValue方法完成嵌套映射,并生成嵌套对象。嵌套对象可以嵌套多层,也就可以产生多层递归。
通过linkObjects方法,将上一步骤得到的嵌套对象保存到外层对象。

applyNestedResultMappings方法代码如下

/*** 处理嵌套映射的核心代码* @param rsw* @param resultMap* @param metaObject* @param parentPrefix* @param parentRowKey* @param newObject* @return*/
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {boolean foundValues = false;for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {// 获取引用其他的ResultMap的idfinal String nestedResultMapId = resultMapping.getNestedResultMapId();// 如果指定了嵌套映射的id,并且尚未映射if (nestedResultMapId != null && resultMapping.getResultSet() == null) {try {// 获取列前缀final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);// 根据上面获取到的嵌套映射id去从配置中找到对应的ResultMapfinal ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);// 列前缀为空的情况下处理,一般不去用if (resultMapping.getColumnPrefix() == null) {Object ancestorObject = ancestorObjects.get(nestedResultMapId);if (ancestorObject != null) {if (newObject) {linkObjects(metaObject, resultMapping, ancestorObject);}continue;}}// 为嵌套对象创建CacheKey,该过程创建的CacheKey还会与外层对象的CacheKey合并final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);// 合并CacheKeyfinal CacheKey combinedKey = combineKeys(rowKey, parentRowKey);Object rowValue = nestedResultObjects.get(combinedKey);boolean knownValue = rowValue != null;// 如果嵌套对象是集合,并且没有初始化,会调用该方法对其进行初始化instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);// 根据notNullColumn属性,检测结果集中相应的列是否为空if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {// 获取映射结果rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);if (rowValue != null && !knownValue) {linkObjects(metaObject, resultMapping, rowValue);foundValues = true;}}} catch (SQLException e) {throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);}}}return foundValues;
}

结语

距离上一篇源码分析的博客已经间隔了一个多月,最近在家闲够了就着手继续写博客了,关于这块的内容不会弃坑,只是偶尔会拖更一下下。。

*************************************优雅的分割线 **********************************

分享一波:程序员赚外快-必看的巅峰干货

如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程

请关注微信公众号:HB荷包
在这里插入图片描述
一个能让你学习技术和赚钱方法的公众号,持续更新
*************************************优雅的分割线 **********************************

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/536973.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gridview获取选中行数据_Word转Excel,不想熬夜加班,那就掌握这个数据清洗方法...

私信回复关键词【福利】~获取丰富办公资源&#xff0c;助你高效办公早下班&#xff01;小伙伴们&#xff0c;大家好&#xff0c;我是专治各种疑难杂「数」的农夫~今天&#xff0c;我就为大家介绍一种高效的数据清洗方法&#xff0c;助你告别熬夜加班&#xff0c;拥抱美好的夜晚…

Mybatis源码阅读(三):结果集映射3.3 —— 主键生成策略

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

list最大容量_Java 基础(四)集合源码解析 List

List 接口前面我们学习了Iterator、Collection&#xff0c;为集合的学习打下了基础&#xff0c;现在我们来学习集合的第一大体系 List。List 是一个接口&#xff0c;定义了一组元素是有序的、可重复的集合。List 继承自 Collection&#xff0c;较之 Collection&#xff0c;List…

Mybatis源码阅读(四):核心接口4.1——StatementHandler

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Shell学习之结合正则表达式与通配符的使用(五)

Shell学习之结合正则表达式与通配符的使用 目录 通配符 正则表达式与通配符通配符通配符的使用正则表达式 正则表达式正则表达式的使用通配符 正则表达式与通配符 正则表达式用来在文件中匹配符合条件的字符串&#xff0c;正则是包含匹配。grep、awk、sed等命令可以支持正则表达…

Mybatis源码阅读(四):核心接口4.2——Executor(上)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

接收xml参数_SpringBoot实战(二):接收xml请求

强烈推荐一个大神的人工智能的教程&#xff1a;http://www.captainbed.net/zhanghan【前言】最近在对接一个第三方系统&#xff0c;需要接收第三方系统的回调&#xff0c;而且格式为XML形式&#xff0c;之前自己一般接收的参数是Json形式&#xff0c;于是乎做个实验验证一下使用…

报错 插入更新_window如何解决mysql数据量过大导致的报错

window如何解决报错“The total number of locks exceeds the lock table size”第一大步&#xff0c;查看mysql配置信息在CMD中输入mysql -hlocalhost -uroot -p #如果设置了密码直接接在p 后面 show variables like %storage_engine%以下为结果可以看到InnoDB是MySQL的默认引…

Mybatis源码阅读(四):核心接口4.2——Executor(下)

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Mybatis源码阅读(五 ):接口层——SqlSession

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

插入公式_一个小工具,彻底帮你搞定在Markdown中插入公式的问题

在编辑Markdown文档时&#xff0c;插入公式是一个挺麻烦的活儿。需要掌握LaTex语法。我自己看完语法后&#xff0c;直接放弃&#xff0c;这绝对是反人类的语法。&#xff08;好吧&#xff0c;是我不会用...&#xff09;但是&#xff0c;我相信你看了这篇文章后&#xff0c;绝对…

Mybatis源码阅读(一):Mybatis初始化1.2 —— 解析别名、插件、对象工厂、反射工具箱、环境

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Google 修改 Chrome API,防止隐身模式检测

开发四年只会写业务代码&#xff0c;分布式高并发都不会还做程序员&#xff1f; 在使用 Chrome 浏览网页时&#xff0c;某些网站会使用某种方法来确定访问者是否处于隐身模式&#xff0c;这是一种隐私泄漏行为。Google 目前正在考虑修改 Chrome 的相关 API&#xff0c;来杜绝…

Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

JavaScript异步基础

唯一比不知道代码为什么崩溃更可怕的事情是&#xff0c;不知道为什么一开始它是工作的&#xff01;在 ECMA 规范的最近几次版本里不断有新成员加入&#xff0c;尤其在处理异步的问题上&#xff0c;更是不断推陈出新。然而&#xff0c;我们在享受便利的同时&#xff0c;也应该了…

Flutter、ReactNative、uniapp对比

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

多线程中ThreadLocal的使用

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

注解版poi操作工具

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

Kali Linux 2019.1 发布,Metasploit 更新到 5.0 版本

百度智能云 云生态狂欢季 热门云产品1折起>>> Kali Linux 2019.1 发布了&#xff0c;Kali 前身 BackTrack&#xff0c;它是一个基于 Debian 的 Linux 发行版&#xff0c;主要用于信息安全行业&#xff0c;其包含了一系列安全、渗透测试和取证工具。此版本 Linux 内核…

peewee mysql_scrapy中利用peewee插入Mysql

前两天老大布置一个任务&#xff0c;说爬下来的数据要存入数据库中&#xff0c;丢给我一个peewee&#xff0c;说用这个。当时的我两眼一抹黑&#xff0c;这是个什么东西呀&#xff0c;我知道scrapy的数据存入数据库是在pipelines中进行设置但是peewee是什么东西呢。经过两天不懈…