这篇文章具体来看看mapper.xml的解析过程
mappers配置方式
mappers 标签下有许多 mapper 标签,每一个 mapper 标签中配置的都是一个独立的映射配置文件的路径,配置方式有以下几种。
接口信息进行配置
注意:这种方式必须保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。因为是通过获取mapper中的class属性,拼接上.xml来读取UserMapper.xml,如果xml文件名不同或者不在同一个包中是无法读取到xml的。
相对路径进行配置
注意:这种方式不用保证同接口同包同名。但是要保证xml中的namespase和对应的接口名相同。
绝对路径进行配置
接口所在包进行配置
这种方式和第一种方式要求一致,保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。
注意:以上所有的配置都要保证xml中的namespase和对应的接口名相同。 我们以packae属性为例详细分析一下:
mappers解析入口方法
接上一篇文章最后部分,我们来看看mapperElement方法:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//包扫描的形式
if ("package".equals(child.getName())) {
// 获取 节点中的 name 属性
String mapperPackage = child.getStringAttribute("name");
// 从指定包中查找 所有的 mapper 接口,并根据 mapper 接口解析映射配置
configuration.addMappers(mapperPackage);
} else {
//读取中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
String resource = child.getStringAttribute("resource");
//读取mapper节点的url属性
String url = child.getStringAttribute("url");
//读取mapper节点的class属性
String mapperClass = child.getStringAttribute("class");
// resource 不为空,且其他两者为空,则从指定路径中加载配置
if (resource != null && url == null && mapperClass == null) {
//根据rusource加载mapper文件
ErrorContext.instance().resource(resource);
//读取文件字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//实例化mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析映射文件
mapperParser.parse();
// url 不为空,且其他两者为空,则通过 url 加载配置
} else if (resource == null && url != null && mapperClass == null) {
//从网络url资源加载mapper文件
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 解析映射文件
mapperParser.parse();
// mapperClass 不为空,且其他两者为空,则通过 mapperClass 解析映射配置
} else if (resource == null && url == null && mapperClass != null) {
//使用mapperClass加载文件
Class mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
//resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
在 MyBatis 中,共有四种加载映射文件或信息的方式。
1、从文件系统中加载映射文件。
2、通过 URL 的方式加载和解析映射文件。
3、通过 mapper 接口加载映射信息,映射信息可以配置在注解中,也可以配置在映射文件中。
4、最后一种是通过包扫描的方式获取到某个包下的所有类,并使用第三种方式为每个类解析映射信息。
我们先看下以packae扫描的形式,看下configuration.addMappers(mapperPackage)方法
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
我们看一下MapperRegistry的addMappers方法:
public void addMappers(String packageName) {
//传入包名和Object.class类型
addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class superType) {
ResolverUtil> resolverUtil = new ResolverUtil>();
/*
* 查找包下的父类为 Object.class 的类。
* 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。上一篇文章我们已经看过这部分的源码,不再累述了
*/
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取查找结果
Set>> mapperSet = resolverUtil.getClasses();
for (Class mapperClass : mapperSet) {
//这个方法后面重点讲
addMapper(mapperClass);
}
}
其实就是通过 VFS(虚拟文件系统)获取指定包下的所有文件的Class,也就是所有的Mapper接口,然后遍历每个Mapper接口进行解析,接下来就和第一种配置方式(接口信息进行配置)一样的流程了,接下来我们来看看 基于 XML 的映射文件的解析过程,可以看到先创建一个XMLMapperBuilder,再调用其parse()方法,跟进mapperParser.parse():
public void parse() {
// 检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到“已解析资源集合”中
configuration.addLoadedResource(resource);
// 通过命名空间绑定 Mapper 接口
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
该方法重点关注第5行和第9行的逻辑,也就是configurationElement和bindMapperForNamespace方法。
解析映射文件
在 MyBatis 映射文件中,可以配置多种节点。比如 ,, 以及 等。下面我们来看一个映射文件配置示例。
user
select * from WHERE id = #{id}
接着来看看configurationElement解析mapper.xml中的内容。
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private void configurationElement(XNode context) {
try {
// 获取 mapper 命名空间,如 mapper.UserMapper
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置命名空间到 builderAssistant 中
builderAssistant.setCurrentNamespace(namespace);
// 解析 节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析 节点
cacheElement(context.evalNode("cache"));
// 已废弃配置,这里不做分析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 、、、 节点
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);
}
}
}
接下来我们就对其中关键的方法进行详细分析
解析 cache 节点
MyBatis 提供了一、二级缓存,其中一级缓存是 SqlSession 级别的,默认为开启状态。二级缓存配置在映射文件中,使用者需要显示配置才能开启。如下:
也可以使用第三方缓存
其中有一些属性可以选择
1、根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
2、缓存的容量为 512 个对象引用。
3、缓存每隔60秒刷新一次。
4、缓存返回的对象是写安全的,即在外部修改对象不会影响到缓存内部存储对象。
下面我们来分析一下缓存配置的解析逻辑,如下:
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// 获取type属性,如果type没有指定就用默认的PERPETUAL(早已经注册过的别名的PerpetualCache)
String type = context.getStringAttribute("type", "PERPETUAL");
// 根据type从早已经注册的别名中获取对应的Class,PERPETUAL对应的Class是PerpetualCache.class
// 如果我们写了type属性,如type="org.mybatis.caches.redis.RedisCache",这里将会得到RedisCache.class
Class typeClass = typeAliasRegistry.resolveAlias(type);
//获取淘汰方式,默认为LRU(早已经注册过的别名的LruCache),最近最少使用到的先淘汰
String eviction = context.getStringAttribute("eviction", "LRU");
Class evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
// 获取子节点配置
Properties props = context.getChildrenAsProperties();
// 构建缓存对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
}
public class TypeAliasRegistry {
private final Map> TYPE_ALIASES = new HashMap>();
public Class resolveAlias(String string) {
try {
if (string == null) {
return null;
}
// issue #748
// 转换成小写
String key = string.toLowerCase(Locale.ENGLISH);
Class value;
// 如果没有设置type属性,则这里传过来的是PERPETUAL,能从别名缓存中获取到PerpetualCache.class
if (TYPE_ALIASES.containsKey(key)) {
value = (Class) TYPE_ALIASES.get(key);
} else {
//如果是设置了自定义的type,则在别名缓存中是获取不到的,直接通过类加载,加载自定义的type,如RedisCache.class
value = (Class) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
}
缓存的构建封装在 BuilderAssistant 类的 useNewCache 方法中,我们来看看
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private Cache currentCache;
public Cache useNewCache(Class typeClass,
Class evictionClass, Long flushInterval,
Integer size, boolean readWrite, boolean blocking,Properties props) {
// 使用建造模式构建缓存实例
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 添加缓存到 Configuration 对象中
configuration.addCache(cache);
// 设置 currentCache 属性,即当前使用的缓存
currentCache = cache;
return cache;
}
}
上面使用了建造模式构建 Cache 实例,我们跟下去看看。
public class CacheBuilder {
private final String id;
private Class implementation;
private final List> decorators;
public Cache build() {
// 设置默认的缓存类型(PerpetualCache)和缓存装饰器(LruCache)
setDefaultImplementations();
// 通过反射创建缓存
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
// 仅对内置缓存 PerpetualCache 应用装饰器
if (PerpetualCache.class.equals(cache.getClass())) {
// 遍历装饰器集合,应用装饰器
for (Class decorator : decorators) {
// 通过反射创建装饰器实例
cache = newCacheDecoratorInstance(decorator, cache);
// 设置属性值到缓存实例中
setCacheProperties(cache);
}
// 应用标准的装饰器,比如 LoggingCache、SynchronizedCache
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
// 应用具有日志功能的缓存装饰器
cache = new LoggingCache(cache);
}
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
//设置默认缓存类型为PerpetualCache
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
private Cache newBaseCacheInstance(Class cacheClass, String id) {
//获取构造器
Constructor cacheConstructor = getBaseCacheConstructor(cacheClass);
try {
//通过构造器实例化Cache
return cacheConstructor.newInstance(id);
} catch (Exception e) {
throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
}
}
}
如上就创建好了一个Cache的实例,然后把它添加到Configuration中,并且设置到currentCache属性中,这个属性后面还要使用,也就是Cache实例后面还要使用,我们后面再看。
解析 resultMap 节点
resultMap 主要用于映射结果。通过 resultMap 和自动映射,可以让 MyBatis 帮助我们完成 ResultSet → Object 的映射。下面开始分析 resultMap 配置的解析过程。
即resultMapElements(context.evalNodes("/mapper/resultMap"))
public class XMLMapperBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private void resultMapElements(List list) throws Exception {
// 遍历 节点列表
for (XNode resultMapNode : list) {
try {
// 解析 resultMap 节点
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections. emptyList());
}
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获取 id 和 type 属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 获取 extends 和 autoMapping
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 获取 type 属性对应的类型
Class typeClass = resolveClass(type);
Discriminator discriminator = null;
//创建ResultMapping集合,对应resultMap子节点的id和result节点
List resultMappings = new ArrayList();
resultMappings.addAll(additionalResultMappings);
// 获取并遍历 的子节点列表
List resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List flags = new ArrayList();
if ("id".equals(resultChild.getName())) {
// 添加 ID 到 flags 集合中
flags.add(ResultFlag.ID);
}
// 解析 id 和 result 节点,将id或result节点生成相应的 ResultMapping,将ResultMapping添加到resultMappings集合中
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
//创建ResultMapResolver对象
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 根据前面获取到的信息构建 ResultMap 对象
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
}
解析 id 和 result 节点
在 节点中,子节点 和 都是常规配置,比较常见。我们来看看解析过程。 即buildResultMappingFromContext(resultChild, typeClass, flags)
public class XMLMapperBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception {
String property;
// 根据节点类型获取 name 或 property 属性
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
// 获取其他各种属性
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
/*
* 解析 resultMap 属性,该属性出现在 和 节点中。
* 若这两个节点不包含 resultMap 属性,则调用 processNestedResultMappings 方法,递归调用resultMapElement解析 和 的嵌套节点,生成resultMap,并返回resultMap.getId();
* 如果包含resultMap属性,则直接获取其属性值,这个属性值对应一个resultMap节点
*/
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections. emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class> typeHandlerClass = (Class>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
// 构建 ResultMapping 对象
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
}
看processNestedResultMappings解析 和 节点中的子节点,并返回ResultMap.id
public class XMLMapperBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private String processNestedResultMappings(XNode context, List resultMappings) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}
}
只要此节点是(association或者collection)并且select为空,就说明是嵌套查询,那如果select不为空呢?那说明是延迟加载此节点的信息,并不属于嵌套查询,但是有可能有多个association或者collection,有一个设置为延迟加载也就是select属性不为空,有一个没有设置延迟加载,那说明resultMap中有嵌套查询的ResultMapping,也有延迟加载的ResultMapping,这个在后面结果集映射时会用到。
下面以 节点为例,演示该节点的两种配置方式,分别如下:
第一种配置方式是通过 resultMap 属性引用其他的 节点,配置如下:
第二种配置方式是采取 resultMap 嵌套的方式进行配置,如下:
第二种配置, 的子节点是一些结果映射配置,这些结果配置最终也会被解析成 ResultMap。
下面分析 ResultMapping 的构建过程。
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMapping buildResultMapping(Class resultType, String property,
String column, Class javaType, JdbcType jdbcType, String nestedSelect,
String nestedResultMap, String notNullColumn, String columnPrefix,
Class> typeHandler, List flags,
String resultSet, String foreignColumn, boolean lazy) {
// resultType:即 中的 type 属性
// property:即 中的 property 属性
Class javaTypeClass = resolveResultJavaType(resultType, property, javaType);
TypeHandler typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
List composites = parseCompositeColumnName(column);
// 通过建造模式构建 ResultMapping
return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
.jdbcType(jdbcType)
.nestedQueryId(applyCurrentNamespace(nestedSelect, true))
.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
.resultSet(resultSet)
.typeHandler(typeHandlerInstance)
.flags(flags == null ? new ArrayList() : flags)
.composites(composites)
.notNullColumns(parseMultipleColumnNames(notNullColumn))
.columnPrefix(columnPrefix)
.foreignColumn(foreignColumn)
.lazy(lazy)
.build();
}
private Class resolveResultJavaType(Class resultType, String property, Class javaType) {
if (javaType == null && property != null) {
try {
//获取ResultMap中的type属性的元类,如 中User的元类
MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
//,如果result中没有设置javaType,则获取元类属性对那个的类型
javaType = metaResultType.getSetterType(property);
} catch (Exception e) {
//ignore, following null check statement will deal with the situation
}
}
if (javaType == null) {
javaType = Object.class;
}
return javaType;
}
}
public class ResultMapping {
public ResultMapping build() {
// lock down collections
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites = Collections.unmodifiableList(resultMapping.composites);
resolveTypeHandler();
validate();
return resultMapping;
}
}
我们来看看ResultMapping类
public class ResultMapping {
private Configuration configuration;
private String property;
private String column;
private Class javaType;
private JdbcType jdbcType;
private TypeHandler typeHandler;
private String nestedResultMapId;
private String nestedQueryId;
private Set notNullColumns;
private String columnPrefix;
private List flags;
private List composites;
private String resultSet;
private String foreignColumn;
private boolean lazy;
ResultMapping() {
}
//......略
}
我们看到ResultMapping中有属性nestedResultMapId表示嵌套查询和nestedQueryId表示延迟查询。 ResultMapping就是和ResultMap中子节点id和result对应
ResultMap 对象构建
前面的分析我们知道了, 等节点最终都被解析成了 ResultMapping。并且封装到了resultMappings集合中,紧接着要做的事情是构建 ResultMap,关键代码在resultMapResolver.resolve():
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
public class MapperBuilderAssistant extends BaseBuilder {
public ResultMap addResultMap(String id, Class type, String extend,
Discriminator discriminator, List resultMappings, Boolean autoMapping) {
// 为 ResultMap 的 id 和 extend 属性值拼接命名空间
id = applyCurrentNamespace(id, false);
extend = applyCurrentNamespace(extend, true);
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
List extendedResultMappings = new ArrayList(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
Iterator extendedResultMappingsIter = extendedResultMappings.iterator();
while (extendedResultMappingsIter.hasNext()) {
if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
extendedResultMappingsIter.remove();
}
}
}
resultMappings.addAll(extendedResultMappings);
}
// 构建 ResultMap
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 将创建好的ResultMap加入configuration中
configuration.addResultMap(resultMap);
return resultMap;
}
}
我们先看看ResultMap
public class ResultMap {
private Configuration configuration;
private String id;
private Class type;
private List resultMappings;
//用于存储 节点对应的 ResultMapping 对象
private List idResultMappings;
private List constructorResultMappings;
//用于存储 和 节点对应的 ResultMapping 对象
private List propertyResultMappings;
//用于存储 所有、 节点 column 属性
private Set mappedColumns;
private Set mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
private ResultMap() {
}
//......略
}
再来看看通过建造模式构建 ResultMap 实例
public class ResultMap {
private Configuration configuration;
private String id;
private Class type;
private List resultMappings;
//用于存储 节点对应的 ResultMapping 对象
private List idResultMappings;
private List constructorResultMappings;
//用于存储 和 节点对应的 ResultMapping 对象
private List propertyResultMappings;
//用于存储 所有、 节点 column 属性
private Set mappedColumns;
private Set mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
public static class Builder {
private static final Log log = LogFactory.getLog(Builder.class);
private ResultMap resultMap = new ResultMap();
public ResultMap build() {
if (resultMap.id == null) {
throw new IllegalArgumentException("ResultMaps must have an id");
}
resultMap.mappedColumns = new HashSet();
resultMap.mappedProperties = new HashSet();
resultMap.idResultMappings = new ArrayList();
resultMap.constructorResultMappings = new ArrayList();
resultMap.propertyResultMappings = new ArrayList();
final List constructorArgNames = new ArrayList();
for (ResultMapping resultMapping : resultMap.resultMappings) {
/*
* resultMapping.getNestedQueryId()不为空,表示当前resultMap是中有需要延迟查询的属性
* resultMapping.getNestedResultMapId()不为空,表示当前resultMap是一个嵌套查询
* 有可能当前ResultMapp既是一个嵌套查询,又存在延迟查询的属性
*/
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
} else if (resultMapping.isCompositeResult()) {
for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
final String compositeColumn = compositeResultMapping.getColumn();
if (compositeColumn != null) {
// 将 colum 转换成大写,并添加到 mappedColumns 集合中
resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
}
}
}
// 添加属性 property 到 mappedProperties 集合中
final String property = resultMapping.getProperty();
if(property != null) {
resultMap.mappedProperties.add(property);
}
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
resultMap.constructorResultMappings.add(resultMapping);
if (resultMapping.getProperty() != null) {
constructorArgNames.add(resultMapping.getProperty());
}
} else {
// 添加 resultMapping 到 propertyResultMappings 中
resultMap.propertyResultMappings.add(resultMapping);
}
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
// 添加 resultMapping 到 idResultMappings 中
resultMap.idResultMappings.add(resultMapping);
}
}
if (resultMap.idResultMappings.isEmpty()) {
resultMap.idResultMappings.addAll(resultMap.resultMappings);
}
if (!constructorArgNames.isEmpty()) {
final List actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
if (actualArgNames == null) {
throw new BuilderException("Error in result map '" + resultMap.id
+ "'. Failed to find a constructor in '"
+ resultMap.getType().getName() + "' by arg names " + constructorArgNames
+ ". There might be more info in debug log.");
}
Collections.sort(resultMap.constructorResultMappings, new Comparator() {
@Override
public int compare(ResultMapping o1, ResultMapping o2) {
int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
return paramIdx1 - paramIdx2;
}
});
}
// lock down collections
// 将以下这些集合变为不可修改集合
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
return resultMap;
}
}
}
主要做的事情就是将 ResultMapping 实例及属性分别存储到不同的集合中。
解析 sql 节点
节点用来定义一些可重用的 SQL 语句片段,比如表名,或表的列名等。在映射文件中,我们可以通过 节点引用 节点定义的内容。
user
SELECT * FROM WHERE id = #{id}
下面分析一下 sql 节点的解析过程, 如下: 即sqlElement(context.evalNodes("/mapper/sql"))
public class XMLMapperBuilder extends BaseBuilder {
private void sqlElement(List list) throws Exception {
if (configuration.getDatabaseId() != null) {
// 调用 sqlElement 解析 节点
sqlElement(list, configuration.getDatabaseId());
}
// 再次调用 sqlElement,不同的是,这次调用,该方法的第二个参数为 null
sqlElement(list, null);
}
private void sqlElement(List list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
// 获取 id 和 databaseId 属性
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// id = currentNamespace + "." + id
id = builderAssistant.applyCurrentNamespace(id, false);
// 检测当前 databaseId 和 requiredDatabaseId 是否一致
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 将 键值对缓存到XMLMapperBuilder对象的 sqlFragments 属性中,以供后面的sql语句使用
sqlFragments.put(id, context);
}
}
}
}
解析select|insert|update|delete节点
、、 以及 等节点统称为 SQL 语句节点,其解析过程在buildStatementFromContext方法中: 即buildStatementFromContext(context.evalNodes("select|insert|update|delete"))
public class XMLMapperBuilder extends BaseBuilder {
private void buildStatementFromContext(List list) {
if (configuration.getDatabaseId() != null) {
// 调用重载方法构建 Statement
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
for (XNode context : list) {
// 创建 XMLStatementBuilder 建造类
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
/*
* 解析sql节点,将其封装到 Statement 对象中,并将解析结果存储到 configuration 的 mappedStatements 集合中
*/
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
}
我们继续看 statementParser.parseStatementNode();
public class XMLStatementBuilder extends BaseBuilder {
private final MapperBuilderAssistant builderAssistant;
private final XNode context;
public void parseStatementNode() {
// 获取 id 和 databaseId 属性
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 获取各种属性
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String parameterType = context.getStringAttribute("parameterType");
Class parameterTypeClass = resolveClass(parameterType);
String resultMap = context.getStringAttribute("resultMap");
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 通过别名解析 resultType 对应的类型
Class resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 解析 Statement 类型,默认为 PREPARED
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// 解析 ResultSetType
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
// 获取节点的名称,比如 节点名称为 select
String nodeName = context.getNode().getNodeName();
// 根据节点名称解析 SqlCommandType
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 解析 节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
// 解析 SQL 语句
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
/*
* 构建 MappedStatement 对象,并将该对象存储到 Configuration 的 mappedStatements 集合中
*/
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
我们主要来分析下面几个重要的方法:
1、解析 节点
2、解析 SQL,获取 SqlSource
3、构建 MappedStatement 实例
解析 节点
先来看一个include的例子
user
SELECT * FROM WHERE id = #{id}
节点的解析逻辑封装在 applyIncludes 中,该方法的代码如下: 即includeParser.applyIncludes(context.getNode())
public class XMLIncludeTransformer {
public void applyIncludes(Node source) {
Properties variablesContext = new Properties();
Properties configurationVariables = configuration.getVariables();
if (configurationVariables != null) {
// 将 configurationVariables 中的数据添加到 variablesContext 中
variablesContext.putAll(configurationVariables);
}
// 调用重载方法处理 节点
applyIncludes(source, variablesContext, false);
}
}
继续看 applyIncludes 方法
public class XMLIncludeTransformer {
private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
// 第一个条件分支
if (source.getNodeName().equals("include")) {
//获取 节点。
Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
Properties toIncludeContext = getVariablesContext(source, variablesContext);
applyIncludes(toInclude, toIncludeContext, true);
if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
toInclude = source.getOwnerDocument().importNode(toInclude, true);
}
// 将 节点中的 节点替换为 节点
source.getParentNode().replaceChild(toInclude, source);
while (toInclude.hasChildNodes()) {
// 将 中的内容插入到 节点之前
toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
}
/*
* 前面已经将 节点的内容插入到 dom 中了,
* 现在不需要 节点了,这里将该节点从 dom 中移除
*/
toInclude.getParentNode().removeChild(toInclude);
// 第二个条件分支
} else if (source.getNodeType() == Node.ELEMENT_NODE) {
if (included && !variablesContext.isEmpty()) {
// replace variables in attribute values
NamedNodeMap attributes = source.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attr = attributes.item(i);
// 将 source 节点属性中的占位符 ${} 替换成具体的属性值
attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
}
}
NodeList children = source.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
// 递归调用
applyIncludes(children.item(i), variablesContext, included);
}
// 第三个条件分支
} else if (included && source.getNodeType() == Node.TEXT_NODE
&& !variablesContext.isEmpty()) {
// replace variables in text node
// 将文本(text)节点中的属性占位符 ${} 替换成具体的属性值
source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
}
}
}
我们先来看一下 applyIncludes 方法第一次被调用时的状态,source为 节点,节点类型:ELEMENT_NODE,此时会进入第二个分支,获取到获取 子节点列表,遍历子节点列表,将子节点作为参数,进行递归调用applyIncludes ,此时可获取到的子节点如下:
编号
子节点
类型
描述
1
SELECT * FROM
TEXT_NODE
文本节点
2
ELEMENT_NODE
普通节点
3
WHERE id = #{id}
TEXT_NODE
文本节点
接下来要做的事情是遍历列表,然后将子节点作为参数进行递归调用。第一个子节点调用applyIncludes方法,source为 SELECT * FROM 节点,节点类型:TEXT_NODE,进入分支三,没有${},不会替换,则节点一结束返回,什么都没有做。第二个节点调用applyIncludes方法,此时source为 节点,节点类型:ELEMENT_NODE,进入分支一,通过refid找到 sql 节点,也就是toInclude节点,然后执行source.getParentNode().replaceChild(toInclude, source);,直接将节点的父节点,也就是 节点中的当前节点替换成 节点,然后调用toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);,将 中的内容插入到 节点之前,也就是将user插入到 节点之前,现在不需要 节点了,最后将该节点从 dom 中移除。
创建SqlSource
创建SqlSource在createSqlSource方法中。 即SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)
public class XMLLanguageDriver implements LanguageDriver {
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
}
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class parameterType;
// -☆- XMLScriptBuilder
public SqlSource parseScriptNode() {
// 解析 SQL 语句节点
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
// 根据 isDynamic 状态创建不同的 SqlSource
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
}
继续跟进parseDynamicTags
public class XMLScriptBuilder extends BaseBuilder {
private final XNode context;
private boolean isDynamic;
private final Class parameterType;
/**
* 该方法用于初始化 nodeHandlerMap 集合,该集合后面会用到
*/
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
protected MixedSqlNode parseDynamicTags(XNode node) {
List contents = new ArrayList();
NodeList children = node.getNode().getChildNodes();
// 遍历子节点
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
//如果节点是TEXT_NODE类型
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// 获取文本内容
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
// 若文本中包含 ${} 占位符,会被认为是动态节点
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
// 设置 isDynamic 为 true
isDynamic = true;
} else {
// 创建 StaticTextSqlNode
contents.add(new StaticTextSqlNode(data));
}
// child 节点是 ELEMENT_NODE 类型,比如 、 等
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
// 获取节点名称,比如 if、where、trim 等
String nodeName = child.getNode().getNodeName();
// 根据节点名称获取 NodeHandler,也就是上面注册的nodeHandlerMap
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
// 处理 child 节点,生成相应的 SqlNode
handler.handleNode(child, contents);
// 设置 isDynamic 为 true
isDynamic = true;
}
}
return new MixedSqlNode(contents);
}
}
对于if、trim、where等这些动态节点,是通过对应的handler来解析的,如下
handler.handleNode(child, contents);
该代码用于处理动态 SQL 节点,并生成相应的 SqlNode。下面来简单分析一下 WhereHandler 的代码。
public class XMLScriptBuilder extends BaseBuilder {
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List targetContents);
}
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
// 调用 parseDynamicTags 解析 节点
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
// 创建 WhereSqlNode
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
// 添加到 targetContents
targetContents.add(where);
}
}
}
我们已经将 XML 配置解析了 SqlSource,下面我们看看MappedStatement的构建。
构建MappedStatement
SQL 语句节点可以定义很多属性,这些属性和属性值最终存储在 MappedStatement 中。
public class MapperBuilderAssistant extends BaseBuilder {
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class parameterType,
String resultMap,
Class resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 拼接上命名空间,如 ,则id=java.mybaits.dao.UserMapper.findOne
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建建造器,设置各种属性
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);//这里用到了前面解析节点时创建的Cache对象,设置到MappedStatement对象里面的cache属性中
// 获取或创建 ParameterMap
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 构建 MappedStatement
MappedStatement statement = statementBuilder.build();
// 添加 MappedStatement 到 configuration 的 mappedStatements 集合中
// 通过UserMapper代理对象调用findOne方法时,就可以拼接UserMapper接口名java.mybaits.dao.UserMapper
//和findOne方法找到id=java.mybaits.dao.UserMapper的MappedStatement,然后执行对应的sql语句
configuration.addMappedStatement(statement);
return statement;
}
}
这里我们要注意,MappedStatement对象中有一个cache属性,将前面解析节点时创建的Cache对象,设置到MappedStatement对象里面的cache属性中,以备后面二级缓存使用,我们后面专门来讲这一块。
我们还要注意一个地方,.resultMaps(getStatementResultMaps(resultMap, resultType, id)),设置MappedStatement的resultMaps,我们来看看是怎么获取resultMap的
public class MapperBuilderAssistant extends BaseBuilder {
private List getStatementResultMaps(
String resultMap, Class resultType, String statementId) {
//拼接上当前nameSpace
resultMap = applyCurrentNamespace(resultMap, true);
//创建一个集合
List resultMaps = new ArrayList();
if (resultMap != null) {
//通过,分隔字符串,一般resultMap只会是一个,不会使用逗号
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
try {
//从configuration中通过resultMapName获取ResultMap对象加入到resultMaps中
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("Could not find result map " + resultMapName, e);
}
}
} else if (resultType != null) {
ResultMap inlineResultMap = new ResultMap.Builder(
configuration,
statementId + "-Inline",
resultType,
new ArrayList(),
null).build();
resultMaps.add(inlineResultMap);
}
return resultMaps;
}
}
从configuration中获取到ResultMap并设置到MappedStatement中,当查询结束后,就可以拿到ResultMap进行结果映射,这个在后面讲
Mapper 接口绑定
映射文件解析完成后,我们需要通过命名空间将绑定 mapper 接口,看看具体绑定的啥
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private void bindMapperForNamespace() {
// 获取映射文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
// 根据命名空间解析 mapper 类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// 检测当前 mapper 类是否被绑定过
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 绑定 mapper 类
configuration.addMapper(boundType);
}
}
}
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMapper(Class type) {
// 通过 MapperRegistry 绑定 mapper 类
mapperRegistry.addMapper(type);
}
}
public class MapperRegistry {
private final Map, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>();
public void addMapper(Class type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/*
* 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
*/
knownMappers.put(type, new MapperProxyFactory(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解中的信息
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
其实就是获取当前映射文件的命名空间,并获取其Class,也就是获取每个Mapper接口,然后为每个Mapper接口创建一个代理类工厂,new MapperProxyFactory(type),并放进 knownMappers 这个HashMap中,我们来看看这个MapperProxyFactory
public class MapperProxyFactory {
//存放Mapper接口Class
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return mapperInterface;
}
public Map getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
//生成mapperInterface的代理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这一块我们后面文章再来看是如何调用的。
参考: https://www.cnblogs.com/java-chen-hao/p/11743442.html
你可能感兴趣的:(Mybatis源码分析——Mapper映射的解析过程)
【排序算法】——交换排序
code monkey.
排序算法算法排序算法c++
前言排序(Sorting)是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个关键字有序的序列。所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。排序算法,就是如何使得记录按照要求排列的方法。排序算法在很多领域得到相当地重视,尤其是在大量数据的处理方面。一个优秀的算法可以节省大量的资源。简介所谓排序算法,即通过特定的算
Windows和AD域提权枚举脚本及工具介绍
vortex5
网络安全安全windows
在Windows和ActiveDirectory(AD)域环境中,权限提升和枚举是渗透测试的关键部分。为了有效进行这些操作,使用特定的工具和脚本可以帮助你识别和利用系统中的漏洞。以下是一些常用于Windows和AD域环境中的权限提升和枚举脚本工具:Windows环境中的提权枚举脚本PowerUp描述:PowerUp是一个PowerShell脚本,专注于在Windows系统中查找权限提升漏洞。它能够
上位机知识篇---Linux中RPM包管理工具&PATH环境变量
Ronin-Lotus
上位机知识篇linux运维服务器RPM包管理工具PATH环境变量
文章目录前言一、RPM(RedHatPackageManager)1.RPM的核心功能(1)安装软件包(2)查询软件信息(3)卸载软件(4)验证软件包2.RPM常用命令与示例(1)安装软件包(2)查询软件包(3)卸载软件包(4)验证软件包3.RPM的优缺点(1)优点(2)缺点注意二、PATH环境变量1.PATH的作用(1)简化命令输入(2)自定义路径优先级2.查看与操作PATH变量(1)查看当前P
解决:npm : 无法加载文件 D:\Node\node_global\npm.ps1,因为在此系统上禁止运行脚本
_大鱼_
npm前端node.js
1.原因有一次下载了pnpm并配置环境后,不知道是不是配置环境的时候操作错了还是其他什么原因。再次打开一个项目使用npm或pnpm命令的时候就报错了。首先理解一下报错信息的意思,无法加载npm下载时文件存放的路径(D:\Node\node_global)下的npm.ps1这个文件,打开一看是关于执行在powershell上,node环境下执行npm相关命令的脚本,当我们使用npm命令的时候,操作系
【模块】Non-local Neural
dearr__
扒网络模块深度学习pytorchpython
论文《Non-localNeuralNetworks》作用非局部神经网络通过非局部操作捕获长距离依赖,这对于深度神经网络来说至关重要。这些操作允许模型在空间、时间或时空中的任何位置间直接计算相互作用,从而捕获长距离的交互和依赖关系。这种方法对于视频分类、对象检测/分割以及姿态估计等任务表现出了显著的改进。机制非局部操作通过在输入特征图的所有位置上计算响应的加权和来实现,其中权重由位置之间的关系(如
【模块】GNConv卷积模块
dearr__
扒网络模块深度学习pythonpytorch
论文《GCNet:Non-localNetworksMeetSqueeze-ExcitationNetworksandBeyond》1、作用GCNet(GlobalContextNetwork)结合了非局部网络(Non-LocalNetwork,NLNet)的长距离依赖捕捉能力和Squeeze-ExcitationNetwork(SENet)的轻量级特性,有效地建模全局上下文信息。通过简化非局部块
C++错误Call to implicitly-deleted default constructor of ‘SerialPortConfig‘
唯瑞主义
疑问篇c++开发语言QTqt
error:Calltoimplicitly-deleteddefaultconstructorof‘SerialPortConfig’原因分析:该条报错的原因是无法调用默认构造函数,原因是在结构体中,有一个引用的成员变量,而引用的成员变量必须要初始化,因此我们没办法直接默认构造出SerialPortConfig的变量。解决方法:将SerialPortConfig中的QString&strSN,改
Python解决安装包报错4.0.0-unsupported
zhou_x_b
python开发语言
1、安装open3d一直报错,提示pip没有最新2、更新pip报错4.0.0-unsupported解决思路:pip含多个(wherepip命令可以在cmd中查看pip的位置),卸载pippipuninstallpip根据提示如果有多个都卸载了在安装pip命令:easy_installpip关掉软件在打开(否则没用)如果通过pycharm设置安装包,得选择python安装路径对应的pip
【Java 基础】-- Java static 关键字详解
oo寻梦in记
Javajava开发语言
目录Javastatic关键字详解1.static关键字的作用2.static的应用场景2.1静态变量(类变量)vs.实例变量2.2静态方法vs.实例方法2.3静态代码块vs.构造方法2.4静态内部类vs.非静态内部类3.static关键字的注意事项4.static的常见使用场景5.结论Javastatic关键字详解static关键字是Java中的一个重要概念,广泛应用于类的变量、方法、代码块和内
自动化测试的价值重构:软件质量保障的效率革命与理性抉择
北陌宝宝
行业研究发展#检验检测行业重构人工智能pythonwindows
引言:测试范式的数字化跃迁在DevOps与敏捷开发主导的软件工程时代,软件测试正经历从"质量验证"到"效率赋能"的深刻变革。根据Gartner最新报告,全球自动化测试市场规模预计2025年将突破300亿美元,年复合增长率达21.3%。这场技术革命背后,是企业对测试效率与质量的双重诉求——自动化测试究竟是降本增效的利器,还是投入不菲的鸡肋?本文将从技术价值、经济成本、场景适配三个维度深度剖析,为软件
嵌入式开发:嵌入式软件开发和编程
粤嵌教育
嵌入式嵌入式开发嵌入式培训嵌入式软件嵌入式硬件嵌入式
每天,人们都要面对和使用数十种设备,这些设备的功能依赖于微芯片和电路板,这些是带有内置软件的小工具,例如照相机、健身追踪器、咖啡机等。由于许多在生活的某些领域执行关键功能,因此在嵌入式开发中嵌入式软件仍然是一个热门话题。如果你想找到一家可靠的嵌入式系统公司,可以提供软件开发服务来实现你对智能数字设备的想法,你需要了解有关嵌入式软件编程的基本事实。介绍首先,应该注意的是,我们安装在笔记本电脑或智能手
RabbitMQ系列(二)基本概念之Publisher
yyueshen
RabbitMQ#RabbitMQ概念rabbitmq分布式
在RabbitMQ中,Publisher(发布者)是负责向RabbitMQ服务器发送消息的客户端角色,通常被称为“生产者”。以下是其核心功能与工作机制的详细解析:一、核心定义与作用消息发送者Publisher将消息发送到RabbitMQ的Exchange(交换机),而非直接发送到队列。Exchange根据消息的RoutingKey(路由键)和绑定规则将消息路由到对应的队列(Queue)。解耦生产与
使用arxiv提供的API爬取文章信息
ye6
爬虫python
大致阅读了一下arxiv提供的文档,里面4.1.SimpleExamples部分提供了4种语言的API请求样例。我的需求是使用Python,所以直接复制粘贴了。网址:https://arxiv.org/help/api/user-manualimporturlliburl='http://export.arxiv.org/api/query?search_query=all:electron&st
JSON-to-Excel v2.0.0发布,可以在Excel内部,把JSON转换成Excel格式,嵌套的JSON也能转
wtsolutions
excel与json互相转换jsonexcel转换json-to-excel
本文是JSON-to-Excel插件的官方文档https://json-to-excel.wtsolutions.cn简化浓缩翻译的中文版,仅供参考。详细的还请查看官方文档。插件简介JSON-to-Excel是一款强大的MicrosoftExcel插件,专门用于将JSON数据转换为Excel表格格式。这款插件能够帮助用户轻松处理和转换JSON数据,提高数据处理效率。插件版本20250228发布v2
【华为OD机试真题E卷】 27、计算最大乘积 | 机试真题+思路参考+代码解析(C++、Java、Py)
KFickle
JavaPy)华为odc++java华为OD机试真题计算最大乘积
文章目录一、题目题目描述输入输出样例1二、代码与思路参考C++代码Java代码Python代码订阅本专栏后即可解锁在线OJ刷题权限个人博客首页:KFickle专栏介绍:最新的华为OD机试真题D、E卷,每题都使用C++,Java,Python语言进行解答,每个题目的思路分析都非常详细,持续更新,支持在线OJ刷题,订阅后评论获取权限,有代码问题随时解答,代码仅供学习参考一、题目题目描述给定一个元素类型
python爬取arXiv论文元数据
小孔不爱coding
python开发语言网络爬虫
需要相关学科、主题的论文元数据做一些分析,arXiv开源,容易获取。显示选择了考虑用arXiv的api去实现,相关手册见arXiv-api但貌似每次我都被卡在3000条数据就停止了,所以我选择用传统的lxml,bs4直接爬取,但仍然被限制爬取10000条。直接上完整代码,后面解释fromlxmlimporthtmlimportrequestsimportreimportmathimportcsvf
三分钟掌握 TCP/IP:揭示互联网飞速互联的奥秘!
跑起来总会有风
tcp/ip网络
TCP/IP模型详解本文基于经典的TCP/IP模型原理,结合常见实践进行深入解读,适用于对计算机网络或互联网协议栈感兴趣的读者。希望能帮助大家更好地理解现代网络通信的核心框架。一、什么是TCP/IP?TCP/IP是互联网的基础协议栈,英文全称为TransmissionControlProtocol/InternetProtocol。它由美国国防部(DoD)在20世纪60年代发展起来,并在ARPAN
使用宝塔大家Java项目遇到的问题
LOVE_DDZ
JAVASpring-Bootjavaspringboot开发语言
记录一下使用宝塔大家Java项目遇到的问题:1.没有那个文件或目录/var/tmp/springboot/vhost/scripts/system-service.sh:没有那个文件或目录Feb2811:13:01hadoop05spring_system-service:/bin/bash:/var/tmp/springboot/vhost/scripts/system-service.sh:没
Mac通过homebrew安装 jdk1.8 并配置环境
LOVE_DDZ
Mac系统macosjavajvm
Mac通过homebrew安装jdk1.8更新jdk,这个命令由于依旧是从国外的源更新,所以需要多试几次。brewtapAdoptOpenJDK/openjdk安装jdk1.8,二选一brewinstalladoptopenjdk8或者brewinstall--caskadoptopenjdk8配置环境变量sudo/usr/bin/vim/etc/profileJAVA_HOME=/Library
芯片测试:WAT、CP、FT
TrustZone_
数字IC1024程序员节IC量产测试CPFT
Perface最近部门来了一个日本回来的同事,虽然他尽量用非常Poor的中文给我解释一些东西,其中还夹杂着一些英文让我很受挫,于是最近来学一下WAT中的常用的单词含义。直接去查缩写、查单词很难去记住,要把具体的东西放在具体的场景,引发知识的连接才会在脑子里留下一定的映像。感兴趣的一起来学习一下这篇不错的文章吧。注释是我查询记录,如有不妥,欢迎指出修正。正文CP是把坏的Die挑出来,可以减少封装和测
通用无线设备对码软件_通用软件无线电平台USRP-LW N310
weixin_39793319
通用无线设备对码软件
USRP-LWN310http://www.luowave.com产品综述:USRP-LWN310是一种网络的软件定义无线电(SDR),它提供了部署大规模的可靠的和容错性的分布式无线系统。USRP-LWN310通过引入远程执行任务的能力简化了对SDR系统的控制和管理,如更新软件,重新启动,工厂复位、自检,主机/ARM调试以及监控系统运行。USRP-LWN310是目前SDR市场上通道数量最为密集的产
嵌入式linux系统中压力测试的方法
嵌入式开发星球
linux压力测试运维
在Linux环境下,确保系统各项资源充分且稳定地运行对任何系统管理员来说都至关重要。特别是在生产环境中,理解如何对系统资源进行基准测试和压力测试可以帮助预防未来的问题,同时也能够优化现有系统的性能。在本文中,我们将探讨如何使用命令行工具来对Linux系统的CPU、内存、网络和I/O资源进行压力测试,抛砖引玉,希望大家线下详细学习并加以练习。CPU压力测试在Linux系统中,对CPU进行压力测试有助
列表推导式与生成器表达式(深度实战版)
Bruce_xiaowei
笔记编程总结经验python数据结构
列表推导式与生成器表达式(深度实战版)一、列表推导式:不只是语法糖1.底层原理剖析#编译后的字节码对比deftraditional_loop():result=[]foriinrange(5):result.append(i*2)returnresultdeflist_comprehension():return[i*2foriinrange(5)]#使用dis模块查看字节码差异importdis
mysql数据推荐算法_Mahout推荐算法基础
爱看书的小兔纸
mysql数据推荐算法
转载自(http://www.geek521.com/?p=1423)Mahout推荐算法分为以下几大类GenericUserBasedRecommender算法:1.基于用户的相似度2.相近的用户定义与数量特点:1.易于理解2.用户数较少时计算速度快GenericItemBasedRecommender算法:1.基于item的相似度特点:1.item较少时就算速度更快2.当item的外部概念易于
软件无线电硬件平台USRP简介
byte轻骑兵
嵌入式智慧开发探索#软件定义无线电新视界fpga开发USRP软件无线电GNURadio
目录一、平台概述1.1.设计理念1.2.应用场景1.3.常见型号1.3.1.N系列1.3.2.E系列1.3.3.B系列二、硬件组成2.1.母板2.2.子板2.3.天线三、主要特点3.1.灵活性3.2.高性能3.3.可扩展性3.4.开源支持3.5.广泛的软件支持四、软件支持4.1.GNURadio4.1.1.主要特点4.1.2.应用领域4.2.UHD(USRPHardwareDriver)4.2.1
【Pandas】pandas Series ffill
liuweidong0802
PandasSeriespandas
Pandas2.2SeriesComputationsdescriptivestats方法描述Series.backfill(*[,axis,inplace,limit,…])用于填充Series中缺失值(NaN)的方法Series.bfill(*[,axis,inplace,limit,…])用于填充Series中缺失值(NaN)的方法Series.dropna(*[,axis,inplace,
RabbitMQ介绍以及基本使用
web13765607643
面试学习路线阿里巴巴rabbitmqqtruby
文章目录一、什么是消息队列?二、消息队列的作用(优点)1、解耦2、流量削峰3、异步4、顺序性三、RabbitMQ基本结构四、RabbitMQ队列模式1、简单队列模式2、工作队列模式3、发布/订阅模式4、路由模式5、主题模式6、RPC模式7、发布者确认模式五、RabbitMQ相关属性描述总结一、什么是消息队列?消息队列是一种用于在分布式系统中进行通信的技术。它是一种存储和转发消息的中间件,可以用于将
Spring Boot 配置ObjectMapper处理JSON序列化
凉宫二萌
springbootspringboot1024程序员节
添加配置类importcom.fasterxml.jackson.annotation.JsonInclude;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.SerializationFeature;importcom.fasterxml.jackson.datatype
大模型WebUI:Gradio全解12——LangChain原理、架构和组件(2)
龙焰智能
langchainopenaideepseekintergrationsapireference操作指南教程
大模型WebUI:Gradio全解12——LangChain原理、架构和组件(2)前言12.LangChain原理及agents构建GradioUI12.2学习资料12.2.1学习文档12.2.2用途示例12.2.3OpenAI和DeepSeek例程1.OpenAI示例2.DeepSeek例程参考文献前言本系列文章主要介绍WEB界面工具Gradio。Gradio是HuggingFace发布的简易W
FFmpeg av_read_frame 和iOS系统提供的 AVAudioRecorder 实现音频录制的区别
陈皮话梅糖@
FFmpeg音视频高阶技术讲解ffmpegios音视频
1.第一种方式:使用FFmpeg的av_read_frame特点底层实现:基于FFmpeg,这是一个强大的多媒体处理库,直接操作音频流。灵活性:非常灵活,可以处理多种音频格式、编解码器和输入设备。复杂性:需要手动管理音频流、数据包(AVPacket)、内存释放等,代码复杂度较高。跨平台性:FFmpeg是跨平台的,可以在macOS、Linux、Windows等多个平台上运行。依赖性:需要集成FFmp
遍历dom 并且存储(将每一层的DOM元素存在数组中)
换个号韩国红果果
JavaScripthtml
数组从0开始!!
var a=[],i=0;
for(var j=0;j<30;j++){
a[j]=[];//数组里套数组,且第i层存储在第a[i]中
}
function walkDOM(n){
do{
if(n.nodeType!==3)//筛选去除#text类型
a[i].push(n);
//con
Android+Jquery Mobile学习系列(9)-总结和代码分享
白糖_
JQuery Mobile
目录导航
经过一个多月的边学习边练手,学会了Android基于Web开发的毛皮,其实开发过程中用Android原生API不是很多,更多的是HTML/Javascript/Css。
个人觉得基于WebView的Jquery Mobile开发有以下优点:
1、对于刚从Java Web转型过来的同学非常适合,只要懂得HTML开发就可以上手做事。
2、jquerym
impala参考资料
dayutianfei
impala
记录一些有用的Impala资料
1. 入门资料
>>官网翻译:
http://my.oschina.net/weiqingbin/blog?catalog=423691
2. 实用进阶
>>代码&架构分析:
Impala/Hive现状分析与前景展望:http
JAVA 静态变量与非静态变量初始化顺序之新解
周凡杨
java静态非静态顺序
今天和同事争论一问题,关于静态变量与非静态变量的初始化顺序,谁先谁后,最终想整理出来!测试代码:
import java.util.Map;
public class T {
public static T t = new T();
private Map map = new HashMap();
public T(){
System.out.println(&quo
跳出iframe返回外层页面
g21121
iframe
在web开发过程中难免要用到iframe,但当连接超时或跳转到公共页面时就会出现超时页面显示在iframe中,这时我们就需要跳出这个iframe到达一个公共页面去。
首先跳转到一个中间页,这个页面用于判断是否在iframe中,在页面加载的过程中调用如下代码:
<script type="text/javascript">
//<!--
function
JAVA多线程监听JMS、MQ队列
510888780
java多线程
背景:消息队列中有非常多的消息需要处理,并且监听器onMessage()方法中的业务逻辑也相对比较复杂,为了加快队列消息的读取、处理速度。可以通过加快读取速度和加快处理速度来考虑。因此从这两个方面都使用多线程来处理。对于消息处理的业务处理逻辑用线程池来做。对于加快消息监听读取速度可以使用1.使用多个监听器监听一个队列;2.使用一个监听器开启多线程监听。
对于上面提到的方法2使用一个监听器开启多线
第一个SpringMvc例子
布衣凌宇
spring mvc
第一步:导入需要的包;
第二步:配置web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi=
我的spring学习笔记15-容器扩展点之PropertyOverrideConfigurer
aijuans
Spring3
PropertyOverrideConfigurer类似于PropertyPlaceholderConfigurer,但是与后者相比,前者对于bean属性可以有缺省值或者根本没有值。也就是说如果properties文件中没有某个bean属性的内容,那么将使用上下文(配置的xml文件)中相应定义的值。如果properties文件中有bean属性的内容,那么就用properties文件中的值来代替上下
通过XSD验证XML
antlove
xmlschemaxsdvalidationSchemaFactory
1. XmlValidation.java
package xml.validation;
import java.io.InputStream;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schem
文本流与字符集
百合不是茶
PrintWrite()的使用字符集名字 别名获取
文本数据的输入输出;
输入;数据流,缓冲流
输出;介绍向文本打印格式化的输出PrintWrite();
package 文本流;
import java.io.FileNotFound
ibatis模糊查询sqlmap-mapping-**.xml配置
bijian1013
ibatis
正常我们写ibatis的sqlmap-mapping-*.xml文件时,传入的参数都用##标识,如下所示:
<resultMap id="personInfo" class="com.bijian.study.dto.PersonDTO">
<res
java jvm常用命令工具——jdb命令(The Java Debugger)
bijian1013
javajvmjdb
用来对core文件和正在运行的Java进程进行实时地调试,里面包含了丰富的命令帮助您进行调试,它的功能和Sun studio里面所带的dbx非常相似,但 jdb是专门用来针对Java应用程序的。
现在应该说日常的开发中很少用到JDB了,因为现在的IDE已经帮我们封装好了,如使用ECLI
【Spring框架二】Spring常用注解之Component、Repository、Service和Controller注解
bit1129
controller
在Spring常用注解第一步部分【Spring框架一】Spring常用注解之Autowired和Resource注解(http://bit1129.iteye.com/blog/2114084)中介绍了Autowired和Resource两个注解的功能,它们用于将依赖根据名称或者类型进行自动的注入,这简化了在XML中,依赖注入部分的XML的编写,但是UserDao和UserService两个bea
cxf wsdl2java生成代码super出错,构造函数不匹配
bitray
super
由于过去对于soap协议的cxf接触的不是很多,所以遇到了也是迷糊了一会.后来经过查找资料才得以解决. 初始原因一般是由于jaxws2.2规范和jdk6及以上不兼容导致的.所以要强制降为jaxws2.1进行编译生成.我们需要少量的修改:
我们原来的代码
wsdl2java com.test.xxx -client http://.....
修改后的代
动态页面正文部分中文乱码排障一例
ronin47
公司网站一部分动态页面,早先使用apache+resin的架构运行,考虑到高并发访问下的响应性能问题,在前不久逐步开始用nginx替换掉了apache。 不过随后发现了一个问题,随意进入某一有分页的网页,第一页是正常的(因为静态化过了);点“下一页”,出来的页面两边正常,中间部分的标题、关键字等也正常,唯独每个标题下的正文无法正常显示。 因为有做过系统调整,所以第一反应就是新上
java-54- 调整数组顺序使奇数位于偶数前面
bylijinnan
java
import java.util.Arrays;
import java.util.Random;
import ljn.help.Helper;
public class OddBeforeEven {
/**
* Q 54 调整数组顺序使奇数位于偶数前面
* 输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半
从100PV到1亿级PV网站架构演变
cfyme
网站架构
一个网站就像一个人,存在一个从小到大的过程。养一个网站和养一个人一样,不同时期需要不同的方法,不同的方法下有共同的原则。本文结合我自已14年网站人的经历记录一些架构演变中的体会。 1:积累是必不可少的
架构师不是一天练成的。
1999年,我作了一个个人主页,在学校内的虚拟空间,参加了一次主页大赛,几个DREAMWEAVER的页面,几个TABLE作布局,一个DB连接,几行PHP的代码嵌入在HTM
[宇宙时代]宇宙时代的GIS是什么?
comsci
Gis
我们都知道一个事实,在行星内部的时候,因为地理信息的坐标都是相对固定的,所以我们获取一组GIS数据之后,就可以存储到硬盘中,长久使用。。。但是,请注意,这种经验在宇宙时代是不能够被继续使用的
宇宙是一个高维时空
详解create database命令
czmmiao
database
完整命令
CREATE DATABASE mynewdb USER SYS IDENTIFIED BY sys_password USER SYSTEM IDENTIFIED BY system_password LOGFILE GROUP 1 ('/u01/logs/my/redo01a.log','/u02/logs/m
几句不中听却不得不认可的话
datageek
1、人丑就该多读书。
2、你不快乐是因为:你可以像猪一样懒,却无法像只猪一样懒得心安理得。
3、如果你太在意别人的看法,那么你的生活将变成一件裤衩,别人放什么屁,你都得接着。
4、你的问题主要在于:读书不多而买书太多,读书太少又特爱思考,还他妈话痨。
5、与禽兽搏斗的三种结局:(1)、赢了,比禽兽还禽兽。(2)、输了,禽兽不如。(3)、平了,跟禽兽没两样。结论:选择正确的对手很重要。
6
1 14:00 PHP中的“syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM”错误
dcj3sjt126com
PHP
原文地址:http://www.kafka0102.com/2010/08/281.html
因为需要,今天晚些在本机使用PHP做些测试,PHP脚本依赖了一堆我也不清楚做什么用的库。结果一跑起来,就报出类似下面的错误:“Parse error: syntax error, unexpected T_PAAMAYIM_NEKUDOTAYIM in /home/kafka/test/
xcode6 Auto layout and size classes
dcj3sjt126com
ios
官方GUI
https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/Introduction/Introduction.html
iOS中使用自动布局(一)
http://www.cocoachina.com/ind
通过PreparedStatement批量执行sql语句【sql语句相同,值不同】
梦见x光
sql事务批量执行
比如说:我有一个List需要添加到数据库中,那么我该如何通过PreparedStatement来操作呢?
public void addCustomerByCommit(Connection conn , List<Customer> customerList)
{
String sql = "inseret into customer(id
程序员必知必会----linux常用命令之十【系统相关】
hanqunfeng
Linux常用命令
一.linux快捷键
Ctrl+C : 终止当前命令
Ctrl+S : 暂停屏幕输出
Ctrl+Q : 恢复屏幕输出
Ctrl+U : 删除当前行光标前的所有字符
Ctrl+Z : 挂起当前正在执行的进程
Ctrl+L : 清除终端屏幕,相当于clear
二.终端命令
clear : 清除终端屏幕
reset : 重置视窗,当屏幕编码混乱时使用
time com
NGINX
IXHONG
nginx
pcre 编译安装 nginx
conf/vhost/test.conf
upstream admin {
server 127.0.0.1:8080;
}
server {
listen 80;
&
设计模式--工厂模式
kerryg
设计模式
工厂方式模式分为三种:
1、普通工厂模式:建立一个工厂类,对实现了同一个接口的一些类进行实例的创建。
2、多个工厂方法的模式:就是对普通工厂方法模式的改进,在普通工厂方法模式中,如果传递的字符串出错,则不能正确创建对象,而多个工厂方法模式就是提供多个工厂方法,分别创建对象。
3、静态工厂方法模式:就是将上面的多个工厂方法模式里的方法置为静态,
Spring InitializingBean/init-method和DisposableBean/destroy-method
mx_xiehd
javaspringbeanxml
1.initializingBean/init-method
实现org.springframework.beans.factory.InitializingBean接口允许一个bean在它的所有必须属性被BeanFactory设置后,来执行初始化的工作,InitialzingBean仅仅指定了一个方法。
通常InitializingBean接口的使用是能够被避免的,(不鼓励使用,因为没有必要
解决Centos下vim粘贴内容格式混乱问题
qindongliang1922
centosvim
有时候,我们在向vim打开的一个xml,或者任意文件中,拷贝粘贴的代码时,格式莫名其毛的就混乱了,然后自己一个个再重新,把格式排列好,非常耗时,而且很不爽,那么有没有办法避免呢? 答案是肯定的,设置下缩进格式就可以了,非常简单: 在用户的根目录下 直接vi ~/.vimrc文件 然后将set pastetoggle=<F9> 写入这个文件中,保存退出,重新登录,
netty大并发请求问题
tianzhihehe
netty
多线程并发使用同一个channel
java.nio.BufferOverflowException: null
at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:183) ~[na:1.7.0_60-ea]
at java.nio.ByteBuffer.put(ByteBuffer.java:832) ~[na:1.7.0_60-ea]
Hadoop NameNode单点问题解决方案之一 AvatarNode
wyz2009107220
NameNode
我们遇到的情况
Hadoop NameNode存在单点问题。这个问题会影响分布式平台24*7运行。先说说我们的情况吧。
我们的团队负责管理一个1200节点的集群(总大小12PB),目前是运行版本为Hadoop 0.20,transaction logs写入一个共享的NFS filer(注:NetApp NFS Filer)。
经常遇到需要中断服务的问题是给hadoop打补丁。 DataNod
按字母分类:
ABCDEFGHIJKLMNOPQRSTUVWXYZ其他
首页 -
关于我们 -
站内搜索 -
Sitemap -
侵权投诉
版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.