MyBatis 作为一个轻量的 ORM 框架,与 Hibernate 相比,它的学习成本相对较低,尤其适合初级开发使用。本篇我们将介绍原生 MyBatis 的工作原理,并从设计模式的角度去分析其设计。

版本说明:本次源码解读基于 3.5.1 版本,请注意区分版本差异。

1. 原生 API 的基本使用

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

2. 源码解读

MyBatis 初始化的过程主要就是对 Configuration 配置文件的解析过程,因此我们先观察一个简单的配置文件的内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!--Copyright 2009-2016 the original author or authors.Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.-->
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="com\ghoostek\main\blog-derby.properties"/><settings><setting name="cacheEnabled" value="true"/><setting name="lazyLoadingEnabled" value="false"/><setting name="multipleResultSetsEnabled" value="true"/><setting name="useColumnLabel" value="true"/><setting name="useGeneratedKeys" value="false"/><setting name="defaultExecutorType" value="SIMPLE"/><setting name="defaultStatementTimeout" value="25"/></settings><typeAliases><typeAlias alias="Author" type="com.ghoostek.domain.blog.Author"/><typeAlias alias="Blog" type="com.ghoostek.domain.blog.Blog"/><typeAlias alias="Comment" type="com.ghoostek.domain.blog.Comment"/><typeAlias alias="Post" type="com.ghoostek.domain.blog.Post"/><typeAlias alias="Section" type="com.ghoostek.domain.blog.Section"/><typeAlias alias="Tag" type="com.ghoostek.domain.blog.Tag"/></typeAliases><typeHandlers><typeHandler javaType="String" jdbcType="VARCHAR" handler="com.ghoostek.main.CustomStringTypeHandler"/></typeHandlers><objectFactory type="com.ghoostek.main.ExampleObjectFactory"><property name="objectFactoryProperty" value="100"/></objectFactory><plugins><plugin interceptor="com.ghoostek.main.ExamplePlugin"><property name="pluginProperty" value="100"/></plugin></plugins><environments default="development"><environment id="development"><transactionManager type="JDBC"><property name="" value=""/></transactionManager><dataSource type="UNPOOLED"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment></environments><mappers><mapper resource="com\ghoostek\main\AuthorMapper.xml"/><mapper resource="com\ghoostek\main\BlogMapper.xml"/><mapper resource="com\ghoostek\main\CachedAuthorMapper.xml"/><mapper resource="com\ghoostek\main\PostMapper.xml"/><mapper resource="com\ghoostek\main\NestedBlogMapper.xml"/></mappers></configuration>

2.1 SqlSessionFactoryBuilder.build()

根据前面章节了解到的知识,我们可以知道解析的逻辑在 SqlSessionFactoryBuilder.build() 中。

  public SqlSessionFactory build(InputStream inputStream) {return build(inputStream, null, null);}public SqlSessionFactory build(InputStream inputStream, String environment) {return build(inputStream, environment, null);}public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);}public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {// 与 XMLConfigBuilder 相对应的 XMLMapperBuilder 负责对 mapper 文件的解析工作,// 他们都继承 BaseBuilder,这里采用的是模板设计模式XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}

2.2 XmlConfigBuilder.parse()

  public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));return configuration;}// 这里用到的是模板方法模式,在 parseConfiguration() 方法中定义解析的步骤private void parseConfiguration(XNode root) {try {//issue #117 read properties first// 解析 properties 节点,将 properties 中配置的信息并入 Configuration 属性中propertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);typeAliasesElement(root.evalNode("typeAliases"));pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));// 设置 setting 配置,未配置的赋默认值settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631// 初始化数据库/数据源environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}

2.2.1 propertiesElement()

本方法主要解析 properties 标签中引用的资源文件以及属性配置,properties 标签形如:

<properties resource="org/mybatis/example/config.properties"><property name="username" value="dev_user"/><property name="password" value="F2Fa3!33TYyg"/>
</properties>
  private void propertiesElement(XNode context) throws Exception {if (context != null) {// 先解析 properties 体中的子节点Properties defaults = context.getChildrenAsProperties();String resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");if (resource != null && url != null) {throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");}if (resource != null) {// 再解析 resource 所指向的资源文件,并覆盖子节点下的值defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}Properties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}parser.setVariables(defaults);configuration.setVariables(defaults);}}

2.2.2 settingsAsProperties()

本方法主要是解析 settings 标签下的属性值,用于设置 Configuration 的对应属性值,settings 标签形如:

<settings><setting name="cacheEnabled" value="true"/><setting name="lazyLoadingEnabled" value="true"/><setting name="multipleResultSetsEnabled" value="true"/><setting name="useColumnLabel" value="true"/><setting name="useGeneratedKeys" value="false"/><setting name="autoMappingBehavior" value="PARTIAL"/><setting name="autoMappingUnknownColumnBehavior" value="WARNING"/><setting name="defaultExecutorType" value="SIMPLE"/><setting name="defaultStatementTimeout" value="25"/><setting name="defaultFetchSize" value="100"/><setting name="safeRowBoundsEnabled" value="false"/><setting name="mapUnderscoreToCamelCase" value="false"/><setting name="localCacheScope" value="SESSION"/><setting name="jdbcTypeForNull" value="OTHER"/><setting name="lazyLoadTriggerMethods"value="equals,clone,hashCode,toString"/>
</settings>
  private Properties settingsAsProperties(XNode context) {if (context == null) {return new Properties();}Properties props = context.getChildrenAsProperties();// Check that all settings are known to the configuration classMetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);// 判断 Configuration 类是否有对应属性for (Object key : props.keySet()) {if (!metaConfig.hasSetter(String.valueOf(key))) {throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");}}return props;}

2.2.3 typeAliasesElement()

本方法主要解析别名配置 typeAliases,并将其注册到别名注册器中,typeAlias 标签形如:

<typeAliases><typeAlias alias="Author" type="domain.blog.Author"/><typeAlias alias="Blog" type="domain.blog.Blog"/><typeAlias alias="Comment" type="domain.blog.Comment"/><typeAlias alias="Post" type="domain.blog.Post"/><typeAlias alias="Section" type="domain.blog.Section"/><typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>
  private void typeAliasesElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String typeAliasPackage = child.getStringAttribute("name");configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {String alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {// 加载对应的 class,同时校验此类是否存在Class<?> clazz = Resources.classForName(type);if (alias == null) {// 注册注解配置的别名,如果未配置注解,则将 class 的 simpleName 作为别名进行注册typeAliasRegistry.registerAlias(clazz);} else {// 注册 xml 配置的别名typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}}}

注意到 MyBatis 中预置了一些别名配置:

  public TypeAliasRegistry() {registerAlias("string", String.class);registerAlias("byte", Byte.class);registerAlias("long", Long.class);registerAlias("short", Short.class);registerAlias("int", Integer.class);registerAlias("integer", Integer.class);registerAlias("double", Double.class);registerAlias("float", Float.class);registerAlias("boolean", Boolean.class);registerAlias("byte[]", Byte[].class);registerAlias("long[]", Long[].class);registerAlias("short[]", Short[].class);registerAlias("int[]", Integer[].class);registerAlias("integer[]", Integer[].class);registerAlias("double[]", Double[].class);registerAlias("float[]", Float[].class);registerAlias("boolean[]", Boolean[].class);registerAlias("_byte", byte.class);registerAlias("_long", long.class);registerAlias("_short", short.class);registerAlias("_int", int.class);registerAlias("_integer", int.class);registerAlias("_double", double.class);registerAlias("_float", float.class);registerAlias("_boolean", boolean.class);registerAlias("_byte[]", byte[].class);registerAlias("_long[]", long[].class);registerAlias("_short[]", short[].class);registerAlias("_int[]", int[].class);registerAlias("_integer[]", int[].class);registerAlias("_double[]", double[].class);registerAlias("_float[]", float[].class);registerAlias("_boolean[]", boolean[].class);registerAlias("date", Date.class);registerAlias("decimal", BigDecimal.class);registerAlias("bigdecimal", BigDecimal.class);registerAlias("biginteger", BigInteger.class);registerAlias("object", Object.class);registerAlias("date[]", Date[].class);registerAlias("decimal[]", BigDecimal[].class);registerAlias("bigdecimal[]", BigDecimal[].class);registerAlias("biginteger[]", BigInteger[].class);registerAlias("object[]", Object[].class);registerAlias("map", Map.class);registerAlias("hashmap", HashMap.class);registerAlias("list", List.class);registerAlias("arraylist", ArrayList.class);registerAlias("collection", Collection.class);registerAlias("iterator", Iterator.class);registerAlias("ResultSet", ResultSet.class);}

2.2.4 pluginElement()

本方法主要解析拦截器的配置,对应如下格式的 plugins 标签:

<plugins><plugin interceptor="org.mybatis.example.ExamplePlugin"><property name="someProperty" value="100"/></plugin>
</plugins>
  private void pluginElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {String interceptor = child.getStringAttribute("interceptor");Properties properties = child.getChildrenAsProperties();// 从别名注册表中查找 class,查不到直接加载 classInterceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}

在 MyBatis 中我们可以对如下的组件进行拦截:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

我们需要实现 Interceptor 来自定义拦截器,并通过注解 @Intercepts 来描述要拦截的方法集合,@Intercepts 注解的值是 @Signature 注解集合,每个 @Signature 注解用来定义一个方法。同时,在 intercept() 方法中实现拦截的具体逻辑,示例如下:

@Intercepts({@Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {private Properties properties = new Properties();@Overridepublic Object intercept(Invocation invocation) throws Throwable {// implement pre-processing if neededObject returnObject = invocation.proceed();// implement post-processing if neededreturn returnObject;}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}
}

这里需要注意官网中给我们的建议:

The details of these classes methods can be discovered by looking at the full method signature of each, and the source code which is available with each MyBatis release. You should understand the behaviour of the method you’re overriding, assuming you’re doing something more than just monitoring calls. If you attempt to modify or override the behaviour of a given method, you’re likely to break the core of MyBatis. These are low level classes and methods, so use plug-ins with caution.

大意是说,如果我们使用拦截器只是为了监控方法的调用的话,可以放心的拦截;如果是要改变原来的行为的话,需要对要拦截的方法有足够深入的了解,因为可以被拦截的这几个组件都处于 MyBatis 的比较底层的位置,总之要小心再小心!!!

2.2.5 objectFactoryElement()

MyBatis 每次根据数据库查询结果构造返回的结果对象时,会调用 ObjectFactory 进行实例化结果对象,默认的 ObjectFactory 实现为 DefaultObjectFactory,它只提供了简单的使用无参和带参构造器来构造结果对象,如果 DefaultObjectFactory 无法满足我们的需求,可以自定义 ObjectFactory 实现,同时配置到配置文件中的 objectFactory 节点下:

  <objectFactory type="com.ghoostek.main.ExampleObjectFactory"><property name="objectFactoryProperty" value="100"/></objectFactory>
  private void objectFactoryElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties properties = context.getChildrenAsProperties();ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();factory.setProperties(properties);configuration.setObjectFactory(factory);}}

一个自定义的 ObjectFactory 简单示例如下:

public class ExampleObjectFactory extends DefaultObjectFactory {@Overridepublic <T> T create(Class<T> type) {// add code block herereturn super.create(type);}@Overridepublic <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {// add code block herereturn super.create(type, constructorArgTypes, constructorArgs);}@Overridepublic void setProperties(Properties properties) {super.setProperties(properties);}@Overridepublic <T> boolean isCollection(Class<T> type) {return Collection.class.isAssignableFrom(type);}
}

2.2.6 environmentsElement()

本方法主要是对数据库连接相关配置的解析,environments 标签下可以配置多个 environment,每个对应一个环境的数据库信息。
注意 transactionManager 节点是对 TransactionFactory 的配置,MyBatis 默认提供了两种类型的 TransactionFactory——JDBC 和 MANAGED。

  • JDBC 类型的事务管理根据 dataSource 节点下配置的数据库连接类型来进行;
  • MANAGED 类型不做任何事务管理,而是将管理权限交给应用容器。
    这里需要注意的是,当我们将 MyBatis 集成到 Spring 时,这里的 transactionManager 配置会被覆盖。

MyBatis 内置了三种类型的 dataSource:UNPOOLED,POOLED,JNDI。

  • UNPOOLED 每次请求建立一个新的连接;
  • POOLED 自建连接池管理连接;
  • JNDI 交给应用容器管理。
<environments default="development"><environment id="development"><transactionManager type="JDBC"><property name="..." value="..."/></transactionManager><dataSource type="POOLED"><property name="driver" value="${driver}"/><property name="url" value="${url}"/><property name="username" value="${username}"/><property name="password" value="${password}"/></dataSource></environment>
</environments>
  private void environmentsElement(XNode context) throws Exception {if (context != null) {if (environment == null) {environment = context.getStringAttribute("default");}for (XNode child : context.getChildren()) {String id = child.getStringAttribute("id");if (isSpecifiedEnvironment(id)) {// <transactionManager type="JDBC">// 根据上面标签指定的类型实例化所需要的 TransactionFactory,// TransactionFactory 默认实现有两种://   JdbcTransactionFactory,ManagedTransactionFactory// 分别对应 JDBC,MANAGED 两种类型TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));// <dataSource type="POOLED">// 根据上面标签指定的类型实例化所需要的 DataSourceFactory,// DataSourceFactory 默认实现有三种://   UnpooledDataSourceFactory,PooledDataSourceFactory,JndiDataSourceFactory// 分别对应 UNPOOLED,POOLED,JNDI 三种类型DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));DataSource dataSource = dsFactory.getDataSource();Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dataSource);configuration.setEnvironment(environmentBuilder.build());}}}}
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type");Properties props = context.getChildrenAsProperties();DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();// 在 UnpooledDataSourceFactory 中,通过反射给 dataSource 设置属性factory.setProperties(props);return factory;}throw new BuilderException("Environment declaration requires a DataSourceFactory.");}

2.2.7 typeHandlerElement()

本方法主要解析类型处理器,并将其注册到类型解析注册器中。类型处理器主要用来对 Java 数据类型和 JDBC 数据类型的映射。

<typeHandlers><typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
  private void typeHandlerElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String typeHandlerPackage = child.getStringAttribute("name");typeHandlerRegistry.register(typeHandlerPackage);} else {String javaTypeName = child.getStringAttribute("javaType");String jdbcTypeName = child.getStringAttribute("jdbcType");String handlerTypeName = child.getStringAttribute("handler");Class<?> javaTypeClass = resolveClass(javaTypeName);JdbcType jdbcType = resolveJdbcType(jdbcTypeName);Class<?> typeHandlerClass = resolveClass(handlerTypeName);if (javaTypeClass != null) {if (jdbcType == null) {typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);} else {typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);}} else {typeHandlerRegistry.register(typeHandlerClass);}}}}}

2.2.8 mapperElement()

本方法主要根据 mappers 节点下指定的 mapper 文件或者接口文件解析相关的 SQL。
对 mapper 文件的解析是 MyBatis 的核心,这里我们暂时不作详细讨论,后续会单独开篇。

<mappers><mapper resource="org/mybatis/builder/AuthorMapper.xml"/><mapper resource="org/mybatis/builder/BlogMapper.xml"/><mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {// 根据指定的包,加载包下所有的接口文件if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {// 根据 resource 或 url 加载并解析 mapper 文件String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}

2.3 DefaultSqlSessionFactory.openSession()

  public SqlSession openSession() {// 默认的 Executor 类型为 SIMPLEreturn openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {// Environment 是根据我们的配置解析出来的final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

Configuration.newExecutor()

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}// 如果开启了全局缓存,包装成缓存类型的 Execotor// 这里用到了包装模式if (cacheEnabled) {executor = new CachingExecutor(executor);}// 层层拦截executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

InterceptorChain.pluginAll()

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}// 解析plugin节点的时候,会调用此方法将拦截器放入拦截器列表中public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}

这里我们重点看一下拦截器的具体原理。

首先在解析 Configuration 配置文件时,当解析到 plugins 标签时,XMLConfigBuilder 会循环解析 plugins 下的 plugin 列表,并将解析出来的实现了 Interceptor 接口的 Plugin 通过调用 Configuration.addInterceptor() 方法添加进 Configuration 类中。在 Configuration 类中持有 InterceptorChain 类型的成员变量,Configuration.addInterceptor() 最终是通过调用 InterceptorChain.addInterceptor() 将拦截器添加进 InterceptorChain 中,在实例化可以被 Interceptor 拦截的四种对象时,会在已得到的对象的基础上调用 InterceptorChain.pluginAll() 方法对被拦截对象进行层层拦截,最终得到代理对象。

上面我们看到在 InterceptorChain.pluginAll() 方法是通过循环调用 interceptor.plugin() 来达到层层拦截的,通常情况下我们自定义的拦截器的实现形如:

@Intercepts({@Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {private Properties properties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}public Properties getProperties() {return properties;}}

在 plugin() 方法中通过调用 Plugin.wrap() 方法进行代理对象的生成,此方法主要是通过 JDK 动态代理来生成代理对象。

public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;// signatureMap中保存的是某个类中被拦截的方法列表private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {return interceptor.intercept(new Invocation(target, method, args));}return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {// 拦截器的注解如下:// @Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = { Statement.class, ResultHandler.class}) })Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());}Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());try {Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<>();while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}return interfaces.toArray(new Class<?>[interfaces.size()]);}}

2.4 DefaultSqlSession.selectOne()

  public <T> T selectOne(String statement) {return this.selectOne(statement, null);}public <T> T selectOne(String statement, Object parameter) {// Popular vote was to return null on 0 results and throw exception on too many.List<T> list = this.selectList(statement, parameter);// 因为此处调用的是 selectOne() 方法,因此只有当查询结果只有一条记录时,才正常返回// 为空返回 null// 有多条则抛出异常if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}public <E> List<E> selectList(String statement, Object parameter) {// RowBounds 是对查询记录的偏移量及条数的封装// 默认是从第一条开始,查询的总记录数为 Integer.MAX_VALUEreturn this.selectList(statement, parameter, RowBounds.DEFAULT);}public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {// MappedStatement 是对 mapper 文件中配置的一条语句的封装MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}
}

CachingExecutor.query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 解析出的 sql 被封装在 BoundSql 中BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 先查缓存,查不到再执行数据库查询Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {ensureNoOutParams(ms, boundSql);@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

BaseExecutor.query()

public <E> List<E> 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 (closed) {throw new ExecutorException("Executor was closed.");}if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;
}... ...private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {List<E> list;localCache.putObject(key, EXECUTION_PLACEHOLDER);try {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;
}

SimpleExecutor.doQuery()

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 这里的 wrapper 是 CachingExecutor 实例StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);// 准备 Statementstmt = prepareStatement(handler, ms.getStatementLog());return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}

Configuration.newStatementHandler()

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {// RoutingStatementHandler 中采用策略模式创建具体的 StatementHandler 对象// StatementHandler 具体类型由我们在 mapper 文件中指定的 statementType 决定// 这里在实例化 StatementHandler 的过程中也会实例化 ParameterHandler 和 ResultSetHandlerStatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);// 对 StatementHandler 进行拦截statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {switch (ms.getStatementType()) {// 需要注意的是,这里不管最终选择哪种类型的 StatementHandler 进行实例化// 最终都是调用基类 BaseStatementHandler 的构造方法case STATEMENT:delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case PREPARED:delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;case CALLABLE:delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);break;default:throw new ExecutorException("Unknown statement type: " + ms.getStatementType());}

BaseStatementHandler()

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.rowBounds = rowBounds;this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.objectFactory = configuration.getObjectFactory();if (boundSql == null) { // issue #435, get the key before calculating the statementgenerateKeys(parameterObject);boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;// 实例化 ParameterHanlder 和 ResultSetHandlerthis.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

Configuration.newParameterHandler() & newResultSetHandler()

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);// 拦截parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}... ...public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);// 拦截resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;
}

prepareStatement()

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;// 准备数据库连接Connection connection = getConnection(statementLog);// 准备 Statementstmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;
}... ...protected Connection getConnection(Log statementLog) throws SQLException {Connection connection = transaction.getConnection();if (statementLog.isDebugEnabled()) {return ConnectionLogger.newInstance(connection, statementLog, queryStack);} else {return connection;}
}
JdbcTransaction.getConnection()
public Connection getConnection() throws SQLException {if (connection == null) {openConnection();}return connection;
}... ...protected void openConnection() throws SQLException {if (log.isDebugEnabled()) {log.debug("Opening JDBC Connection");}connection = dataSource.getConnection();if (level != null) {connection.setTransactionIsolation(level.getLevel());}setDesiredAutoCommit(autoCommit);
}

UnpooledDataSource.getConnection()

public Connection getConnection() throws SQLException {return doGetConnection(username, password);
}... ...private Connection doGetConnection(String username, String password) throws SQLException {Properties props = new Properties();if (driverProperties != null) {props.putAll(driverProperties);}if (username != null) {props.setProperty("user", username);}if (password != null) {props.setProperty("password", password);}return doGetConnection(props);
}... ...private Connection doGetConnection(Properties properties) throws SQLException {initializeDriver();Connection connection = DriverManager.getConnection(url, properties);configureConnection(connection);return connection;
}... ...private void configureConnection(Connection conn) throws SQLException {if (autoCommit != null && autoCommit != conn.getAutoCommit()) {conn.setAutoCommit(autoCommit);}if (defaultTransactionIsolationLevel != null) {conn.setTransactionIsolation(defaultTransactionIsolationLevel);}
}
BaseStatementHandler.prepare()
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {ErrorContext.instance().sql(boundSql.getSql());Statement statement = null;try {statement = instantiateStatement(connection);setStatementTimeout(statement, transactionTimeout);setFetchSize(statement);return statement;} catch (SQLException e) {closeStatement(statement);throw e;} catch (Exception e) {closeStatement(statement);throw new ExecutorException("Error preparing statement.  Cause: " + e, e);}
}

SimpleStatementHandler.instantiateStatement()

protected Statement instantiateStatement(Connection connection) throws SQLException {if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {return connection.createStatement();} else {return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);}
}

SimpleStatementHandler.query()

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {// 获取解析后得到的最终 sql,其中参数占位符已经被替换String sql = boundSql.getSql();statement.execute(sql);// 查询结果交给 ResultSetHandler 处理return resultSetHandler.handleResultSets(statement);
}

3. 重要组件

3.1 Executor

Executor 定义了数据库的操作。
在这里插入图片描述

3.2 ParameterHandler

ParameterHandler 定义了对原始入参的处理。
在这里插入图片描述

3.3 StatementHandler

StatementHandler 定义了 Statement 的处理操作。
在这里插入图片描述

3.4 ResultSetHandler

ResultSetHandler 定义了查询结果的处理操作。
在这里插入图片描述

4. 设计模式

4.1 工厂方法

使用 SqlSessionFactory 抽象出 SqlSession 的创建。

4.2 建造者

通过 SqlSessionFactoryBuilder 建造 SqlSessionFactory。
通过 MappedStatement.Builder 建造 MappedStatement。
通过 Environment.Builder 建造 Environment。

4.3 模板

XMLConfigBuilder.parseConfiguration() 方法中对全局配置文件的解析,先在模板方法中定义解析的顺序,每个小方法各自解析对应的节点。

4.4 拦截器

实现 Interceptor 接口可以自定义拦截器对 Executor,ParameterHandler,StatementHandler,ResultSetHandler 拦截。

4.5 代理

每个自定义的拦截器,都使用 JDK 动态代理生成代理对象对被拦截对象进行代理。

4.6 委派

CachingExecutor 中将数据库操作的执行委派给被包装的 Executor 类,自己只实现缓存的功能。
RoutingStatementHandler 中将数据库操作的实现委派给它所创建的 StatementHandler 对象。

4.7 策略

TransactionFacotry,DataSourceFactory 对象的创建根据我们在主配置中指定的类型进行选择具体的对象类型。
StatementHandler 对象创建时根据 MappedStatement 中的 statementType (即我们在 mapper 文件中指定的 statementType )选择具体的对象类型。

4.8 包装器

CachingExecutor 中通过对 Executor 的包装,使其具有缓存的功能。

4.9 责任链

InterceptorChain.pluginAll() 方法通过循环调用 Interceptor.plugin() 方法,生成了一条拦截链,链的顺序就是我们在主配置中指定的拦截器的顺序。

5. 其他

最后我们简单介绍一下 MyBatis3 中提供的工具。MyBatis3 提供了 SQL 类供我们方便的生成自定义的 sql 语句,而不用通过拼接字符串的方式,示例如下:

// 匿名内部类风格
public String deletePersonSql() {return new SQL() {{DELETE_FROM("PERSON");WHERE("ID = #{id}");}}.toString();
}// Builder / Fluent 风格
public String insertPersonSql() {String sql = new SQL().INSERT_INTO("PERSON").VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").VALUES("LAST_NAME", "#{lastName}").toString();return sql;
}// 动态条件(注意参数需要使用 final 修饰,以便匿名内部类对它们进行访问)
public String selectPersonLike(final String id, final String firstName, final String lastName) {return new SQL() {{SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");FROM("PERSON P");if (id != null) {WHERE("P.ID like #{id}");}if (firstName != null) {WHERE("P.FIRST_NAME like #{firstName}");}if (lastName != null) {WHERE("P.LAST_NAME like #{lastName}");}ORDER_BY("P.LAST_NAME");}}.toString();
}public String deletePersonSql() {return new SQL() {{DELETE_FROM("PERSON");WHERE("ID = #{id}");}}.toString();
}public String insertPersonSql() {return new SQL() {{INSERT_INTO("PERSON");VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");VALUES("LAST_NAME", "#{lastName}");}}.toString();
}public String updatePersonSql() {return new SQL() {{UPDATE("PERSON");SET("FIRST_NAME = #{firstName}");WHERE("ID = #{id}");}}.toString();
}
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. leetcode 探索 数组和字符串 移动零

    题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 示例:输入: [0,1,0,3,12] 输出: [1,3,12,0,0]说明:必须在原数组上操作,不能拷贝额外的数组。 尽量减少操作次数。分析: 双指针,一个指针a指向当前的0,一个指针b指向不为0的…...

    2024/4/15 3:18:25
  2. ERR! errno 1 npm ERR! koa2-es10@1.0.0 start: `nodemon --exec npm run babel`的解决方法

    npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! koa2-es10@1.0.0 start: nodemon --exec npm run babel npm ERR! errno 1 npm ERR! koa2-es10@1.0.0 start: nodemon --exec npm run babel的解决方法 安装es10-cli 脚手架后,启动项目时遇到时发生的node_modules丢失问题…...

    2024/4/23 12:32:16
  3. 安装Office2010提示缺少MSXML版本6.10.1129.0的解决方法

    1)先根据自己的系统版本下载安装https://www.microsoft.com/zh-cn/download/details.aspx?id=62762)按win+R,运行regsvr32 /u msxml6.dll regsvr32 msxml6.dll3)新建一个文本文件,输入以下内容Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\TypeLib\{F5078F1…...

    2024/5/7 17:48:05
  4. 对象池:commons-pool2源码解析:GenericObjectPool的borrowObject方法

    Apache Commons Pool库提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。commons-pool2可认为完全不同于commons-pool,里面大部分核心逻辑实现都是完全不同的实现代码。 Apache Commons Pool是很多连接池实现的基础,比如DBCP连接池、Jedis连接池等。 bor…...

    2024/4/25 23:53:25
  5. C#类成员变量的各种修饰符,以及他们的访问范围。

    public :公有访问,不受任何限制。 private :私有访问,只限于本类成员访问,子类,实例都 不能访问。 protected :保护访问,只限于本类和子类访问,实例不能访问。 Internal :内部访问,只限于本项目内访问,其他不能访问。...

    2024/4/24 14:18:12
  6. 华为HCNA之配置DR/BDR实验

    导语: 在一个OSPF网络中,选举一个路由器作为指定路由器DR。所有其他路由器只和它一个交换整个网络的一些路由更新信息,再由它对邻居路由器发送更新报文。这样节省网络流量。再指定一个备份指定路由器BDR,当DR出现故障时,BDR起着备份的作用,确保网络的可靠性。 拓扑图:步…...

    2024/4/24 14:18:10
  7. POI删除Word中多余空行

    在POI中,根据换行符,将Word分成一个个的段落,通过获取段落集合,判断段落中的text等属性是否为空,来进行删除话不多说,见代码public static void changeText(XWPFDocument document){//获取文字段落集合List<XWPFParagraph> paragraphs = document.getParagraphs();…...

    2024/4/24 14:18:09
  8. 跟着大佬学html - html篇

    第一篇html(HYPERTEXT Mrakup Language)---------结构 css(Cascading Style Sheets)--------样式/表现 Js(javascript)-----------行为HTML1.0 HTML2.0 HTML3.0 HTML4.0 XHTML1.0 HTML5html框架<!Doctype html>声明文档类列 < html>根文件,表示要写的东西是…...

    2024/4/24 14:18:08
  9. MATLABGRNN广义回归神经网络数据预测编程

    一、理论基础广义回归神经网络是径向基神经网络的一种,GRNN具有很强的非线性映射能力和学习速度,比RBF具有更强的优势,网络最后普收敛于样本量集聚较多的优化回归,样本数据少时,预测效果很好, 网络还可以处理不稳定数据。 广义回归神经网络对x的回归定义不同于径向基函…...

    2024/5/7 10:26:20
  10. springboot之Profile

    文章目录为什么使用使用多个Profile文件yml支持多文档快激活指定profile 为什么使用 实际生产中常常会使用不同的环境来保证数据的稳定性、安全性且不易丢失 使用 创建对应环境的文件 1.application-dev.properties 内容 server.port=80822.application-prod.properties 内容 s…...

    2024/4/24 14:18:06
  11. linux和本地PC之间传输:Xshell

    本地Windows和远程linux之间的传输方式有不少: 安装SSH Secure Shell Client客户端; 通过linux命令sz/rz文件互传;不想敲命令的同学,可以通过xftp,实现图形传输 第一步,安装xshell 百度下载即可xshell的百度下载地址:https://xshell.en.softonic.com/ 第二步,xshell上…...

    2024/4/24 14:18:05
  12. Android studio4.0 Build Output 中文乱码问题

    最近更新Android studio 4.0 ,发现了中文乱码的问题,具体如下:解决方法如下: 双击shift ,输入 Edit Custom VM Options ,如下:点击第一个,如果没有回提示创建;然后输入: #不要有空格 -Dfile.encoding=UTF-8注意!!!前面不要有空格,一定要记住,不然你的 studio 就…...

    2024/4/28 2:17:14
  13. stat

    man 2 stat 查看帮助文档int stat(const char *pathname, struct stat *statbuf); pathname:文件名 statbuf:返回文件属性的结构体信息int fstat(int fd, struct stat *statbuf); fd:已打开文件的文件指针 statbuf:返回文件属性的结构体信息int lstat(const char *pathname,…...

    2024/4/24 14:18:03
  14. 算法概述

    算法概述 1.引言 1.1算法的描述 算法的特征:有穷性,确定性,输入,输出,输出项,可行性 1.2算法的设计 算法设计的整个过程,可以包含对问题需求的说明,数学模型的拟制,算法的详细设计,算法的正确性验证,算法的实现,算法分析,程序测试和文档资料的编制。 算法大致分为…...

    2024/5/2 4:02:25
  15. 转:Java 日志体系

    参见博客: https://www.cnblogs.com/caoweixiong/p/11285748.html...

    2024/4/24 14:18:01
  16. AD模数转换

    u=VREFL+n(VREFH-VREFL)/2^N u AD转换输入模拟量电压 n AD转换结果的数字量 N AD转换的位数 VREFL 参考电压下限 VREFH 参考电压上限 例如 参考电压为0-4V,8位AD结果为200,输入AD引脚的电压为 u=0+200*(4-0)/2^8=3.125...

    2024/5/5 16:42:21
  17. 程序员需掌握的SQL运算符(三)

    1.前言Mysql 支持多种类型的运算符,运算符可以为操作数进行运算。本文从Mysql 5.7版本出发,下面将详细介绍几种常见的运算符。Mysql运算符主要有四大类,它们分别是:算术运算符、比较运算符、逻辑运算符、位操作运算符2 算术运算符 算术运算符包括加(+)、减(—)、乘(*)…...

    2024/4/23 14:47:34
  18. 基于STM32F103的USB学习笔记27 - CustomHID

    基于JoyStickMouse例程修改即可。1. 调整描述符设备描述符可以不用改。配置描述符长度改为41,主要是增加一个端点。#define USB_CONFIG_DESC_LEN 41配置描述符集合中的Interfae描述符修改如下,0x09, /*bLength: Interface Descriptor size*/USB_…...

    2024/5/5 10:26:46
  19. 戴尔E14S服务器磁盘阵列配置实例

    戴尔E14S服务器磁盘阵列配置实例 ●需求分析 现有一台戴尔E14S服务器需要进行硬盘划分以及Centos 7.6系统的安装。要求使用RAID 0的两块硬盘为系统盘,RAID 5的六块硬盘用来存储数据,剩下的一块硬盘作为热备盘。 ●实验环境 戴尔E14S服务器;提前写入Centos 7.6系统的U盘 ●实…...

    2024/4/15 3:18:35
  20. 区块链应用交互与安全

    1、与用户、钱包的交互 私钥保存是否安全,能否保证正确调用私钥的情况下,私钥不被存储和窃取 2、与链的交互 交易发送节点是否足够安全,能否被拦截或篡改。 3、合约安全 智能合约做为Dapp的数据与通讯的基石,合约的开发更应该注重简洁,安全. 智能合约的重要性在于它处于透…...

    2024/4/15 3:18:35

最新文章

  1. VMware虚拟机提示内存不足

    VMware虚拟机&#xff0c;k8s集群搭建内存不足的问题 疑问&#xff1a;我的电脑是8G8G双通道的内存&#xff0c;当我在搭建k8s集群时给master-2G内存&#xff0c;node1-3G内存&#xff0c;node2-3G内存&#xff1b; 当依次打开虚拟机到node2时VM提示“物理内存不足&#xff0c;…...

    2024/5/7 22:33:39
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/7 10:36:02
  3. SpringMVC初始化工程

    SpringMVC初始化工程 本文采用maven作为构建工具,SpringMVC作为主框架。 创建一个maven的web工程,并配置pom文件<!-- pom.xml --> <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0&qu…...

    2024/5/5 20:56:45
  4. Django实现的登录注册功能

    1 前言 在Web开发中&#xff0c;用户登录和注册是最基本且必不可少的功能。Django&#xff0c;作为一个高级的Python Web框架&#xff0c;为我们提供了强大的工具和库来快速实现这些功能。下面&#xff0c;我将详细介绍如何使用Django来实现用户登录和注册功能。 2 功能介绍 …...

    2024/5/5 8:36:26
  5. Mac brew 安装软件

    Mac brew 安装软件 homebrew 速度慢 将brew 切换到国内镜像源 # 速度一般 # 步骤一 cd "$(brew --repo)" git remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git# 步骤二 cd "$(brew --repo)/Library/Taps/homebrew/homebr…...

    2024/5/3 9:32:52
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/7 5:50:09
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/7 9:45:25
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/7 14:25:14
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/7 11:36:39
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/7 9:26:26
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/6 21:42:42
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57