最后
很多程序员,整天沉浸在业务代码的 CRUD 中,业务中没有大量数据做并发,缺少实战经验,对并发仅仅停留在了解,做不到精通,所以总是与大厂擦肩而过。
我把私藏的这套并发体系的笔记和思维脑图分享出来,理论知识与项目实战的结合,我觉得只要你肯花时间用心学完这些,一定可以快速掌握并发编程。
不管是查缺补漏还是深度学习都能有非常不错的成效,需要的话记得帮忙点个赞支持一下
整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~
this.sqlSessionFactory = buildSqlSessionFactory();
}
前面有解析Mybatis-config.xml配置文件的,这里不跟了,]代码太多了,省略了
重点看下怎么解析我们自定义的xml的
循环mapperLocation(xml文件路径),拿到就是单个xml文件
构建XMLMapperBuilder 去解析
//…省略
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> “Property ‘mapperLocations’ was specified but matching resources are not found.”);
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
/**
- 真正的循环我们的mapper.xml文件
*/
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException(“Failed to parse mapping resource: '” + mapperLocation + “'”, e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> “Parsed mapper file: '” + mapperLocation + “'”);
}
}
}
进入xmlMapperBuilder.parse();
public void parse() {
/**
- 判断当前的Mapper是否被加载过
*/
if (!configuration.isResourceLoaded(resource)) {
/**
-
真正的解析我们的
*/
configurationElement(parser.evalNode(“/mapper”));
/**
- 把资源保存到我们Configuration中
*/
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
进入configurationElement(parser.evalNode("/mapper"));
开始解析xml文件,里面的属性都会解析(nameSpace,resultMap,mapper…)
private void configurationElement(XNode context) {
try {
/**
-
解析我们的namespace属性
*/
String namespace = context.getStringAttribute(“namespace”);
if (namespace == null || namespace.equals(“”)) {
throw new BuilderException(“Mapper’s namespace cannot be empty”);
}
/**
- 保存我们当前的namespace 并且判断接口完全类名==namespace
*/
builderAssistant.setCurrentNamespace(namespace);
/**
-
解析我们的缓存引用
-
说明我当前的缓存引用和DeptMapper的缓存引用一致
解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
*/
cacheRefElement(context.evalNode(“cache-ref”));
/**
-
解析我们的cache节点
解析到:org.apache.ibatis.session.Configuration#caches
org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
*/
cacheElement(context.evalNode(“cache”));
/**
- 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
*/
parameterMapElement(context.evalNodes(“/mapper/parameterMap”));
/**
-
解析我们的resultMap节点
-
解析到:org.apache.ibatis.session.Configuration#resultMaps
-
异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
*/
resultMapElements(context.evalNodes(“/mapper/resultMap”));
/**
-
解析我们通过sql节点
-
解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
-
其实等于 org.apache.ibatis.session.Configuration#sqlFragments
-
因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
*/
sqlElement(context.evalNodes(“/mapper/sql”));
/**
-
解析我们的select | insert |update |delete节点
-
解析到org.apache.ibatis.session.Configuration#mappedStatements
*/
buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));
} catch (Exception e) {
throw new BuilderException(“Error parsing Mapper XML. The XML location is '” + resource + "'. Cause: " + e, e);
}
}
重点看下我们自定义的增删改查节点
进入buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List list) {
/**
- 判断有没有配置数据库厂商ID
*/
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
进入buildStatementFromContext(list, null);
循环所有的增删改查节点
private void buildStatementFromContext(List list, String requiredDatabaseId) {
/**
- 循环我们的select|delte|insert|update节点
*/
for (XNode context : list) {
/**
- 创建一个xmlStatement的构建器对象
*/
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
构建XMLStatementBuilder开始解析
public void parseStatementNode() {
/**
- 我们的insert|delte|update|select 语句的sqlId
*/
String id = context.getStringAttribute(“id”);
/**
-
判断我们的insert|delte|update|select 节点是否配置了
-
数据库厂商标注
*/
String databaseId = context.getStringAttribute(“databaseId”);
/**
- 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
*/
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
/**
- 获得节点名称:select|insert|update|delete
*/
String nodeName = context.getNode().getNodeName();
/**
- 根据nodeName 获得 SqlCommandType枚举
*/
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
/**
- 判断是不是select语句节点
*/
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
/**
-
获取flushCache属性
-
默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true
*/
boolean flushCache = context.getBooleanAttribute(“flushCache”, !isSelect);
/**
-
获取useCache属性
-
默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false
*/
boolean useCache = context.getBooleanAttribute(“useCache”, isSelect);
/**
-
resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)
-
可以将比如 30条数据的三组数据 组成一个嵌套的查询结果
*/
boolean resultOrdered = context.getBooleanAttribute(“resultOrdered”, false);
/**
-
解析我们的sql公用片段
-
<select id="qryEmployeeById" resultType="Employee" parameterType="int">
employee where id=#{id}
将 解析成sql语句 放在Node的子节点中
*/
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
/**
- 解析我们sql节点的参数类型
*/
String parameterType = context.getStringAttribute(“parameterType”);
// 把参数类型字符串转化为class
Class<?> parameterTypeClass = resolveClass(parameterType);
/**
-
查看sql是否支撑自定义语言
*/
String lang = context.getStringAttribute(“lang”);
/**
- 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
*/
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
/**
- 解析我们<insert 语句的的selectKey节点, 还记得吧,一般在oracle里面设置自增id
*/
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
/**
- 我们insert语句 用于主键生成组件
*/
KeyGenerator keyGenerator;
/**
-
selectById!selectKey
-
id+!selectKey
*/
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
/**
-
把我们的命名空间拼接到keyStatementId中
-
com.cheng.mapper.User.saveUser!selectKey
*/
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
/**
*
*判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
*/
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
/**
-
若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
-
否者就看我们的mybatis-config.xml配置文件中是配置了
-
默认是false
-
并且判断sql操作类型是否为insert
-
若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
-
否则就是NoKeyGenerator.INSTANCE
*/
keyGenerator = context.getBooleanAttribute(“useGeneratedKeys”,
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
/**
-
通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
-
sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
*/
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
/**
- STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
*/
StatementType statementType = StatementType.valueOf(context.getStringAttribute(“statementType”, StatementType.PREPARED.toString()));
/**
- 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
*/
Integer fetchSize = context.getIntAttribute(“fetchSize”);
/**
- 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
*/
Integer timeout = context.getIntAttribute(“timeout”);
/**
- 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
*/
String parameterMap = context.getStringAttribute(“parameterMap”);
/**
-
从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
-
可以使用 resultType 或 resultMap,但不能同时使用
*/
String resultType = context.getStringAttribute(“resultType”);
/**解析我们查询结果集返回的类型 */
Class<?> resultTypeClass = resolveClass(resultType);
/**
-
外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
-
可以使用 resultMap 或 resultType,但不能同时使用。
*/
String resultMap = context.getStringAttribute(“resultMap”);
String resultSetType = context.getStringAttribute(“resultSetType”);
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
/**
- 解析 keyProperty keyColumn 仅适用于 insert 和 update
*/
String keyProperty = context.getStringAttribute(“keyProperty”);
String keyColumn = context.getStringAttribute(“keyColumn”);
String resultSets = context.getStringAttribute(“resultSets”);
/**
- 为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
最后
小编精心为大家准备了一手资料
以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术
【附】架构书籍
- BAT面试的20道高频数据库问题解析
- Java面试宝典
- Netty实战
- 算法
BATJ面试要点及Java架构师进阶资料
rMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
最后
小编精心为大家准备了一手资料
[外链图片转存中…(img-W2OaiPAN-1715471081046)]
[外链图片转存中…(img-jbEx0iBk-1715471081047)]
以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术
【附】架构书籍
- BAT面试的20道高频数据库问题解析
- Java面试宝典
- Netty实战
- 算法
[外链图片转存中…(img-D2PZrEz4-1715471081047)]
BATJ面试要点及Java架构师进阶资料
[外链图片转存中…(img-Y1v9Uag0-1715471081047)]