百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Mybatis源码框架核心-宏观(mybatis 源码分析)

bigegpt 2024-08-03 11:47 8 浏览

Mybatis源码

下载地址:github.com/mybatis/myb…

我的版本:3.5.4分支,IDEA:2018.3.6

Mybatis编译:

先下载mybatis的依赖工程:github.com/mybatis/par…

然后切换到该依赖目录下执行:mvn clean install

然后到该目录下查看版本pom.xml:


然后进入mybatis目录下,修改pom.xml中的版本号,以及依赖的parent相对路径

这两个要对应,只要一样即可,无所谓谁对应谁,不过如果是改动parent的版本号记得重新编译一下,不改会不会报错就不知道了。还要注意保证同级目录

然后编译报错:

这是本身pom.xml中的pdf插件导致,注释掉它



再次编译

撒花成功。

1. 传统JDBC的弊端

  1. JDBC底层没有使用连接池,操作数据库需要频繁的创建和关联链接,在并发很大的场景中,资源开销很大;
  2. 写原生的jdbc代码在java中,一旦修改某个sql,需要重新编译java,不利于系统的维护;
  3. 使用PreparedStatement预编的方式对sql中的变量复制,通过下标设置1,2,3,一旦sql语句变量很多,这样的序号不利于维护;
  4. 返回result结果集也需要硬编码

2. ORM框架Mybatis介绍

MyBatis is a first class persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.

MyBatis 是一款优秀的持久层框架(Object Relational Mapping),它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

3. Mybatis链接

  1. 官网参考:mybatis.org/mybatis-3/g… 直接用mybatis中test的测试用例

1. Mybatis连接数据库

  1. 前置环境: 数据库源: driver:驱动 url:连接ip username:数据库user password:密码 执行语句 select insert update delete 对数据库操作 connection PrepareStatement ResultSet


2. Mybatis是如何获取到数据库源

  1. 测试用例说明参见:www.cnblogs.com/liuzhiyong0…我截图如下:


创建与数据库对应的POJO(数据自己创建一个简单的做测试即可,主要目的是打断点看源码)

package org.apache.ibatis.anzhitestmybatis;

public class MyBook {
  private String username;
  private double balance;


  public MyBook() {
  }

  public MyBook(String username, double balance) {
    this.username = username;
    this.balance = balance;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public double getBalance() {
    return balance;
  }

  public void setBalance(double balance) {
    this.balance = balance;
  }

  @Override
  public String toString() {
    return "MyBook{" +
      "username='" + username + '\'' +
      ", balance=" + balance +
      '}';
  }
}
复制代码
  • 接口 package org.apache.ibatis.anzhitestmybatis; import org.apache.ibatis.annotations.Param; public interface MyBookMapper { public MyBook selectBook(double balance); } 复制代码
  • mybatis-config.xml全局配置文件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/demo"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml"/> </mappers> </configuration> 复制代码
  • 映射文件配置: 注意几个点:namespace是我们映射的mapper接口的全路径 id是方法,resultType是返回的类型 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.anzhitestmybatis.MyBookMapper"> <select id="selectBook" resultType="org.apache.ibatis.anzhitestmybatis.MyBook"> select * from account where balance = #{balance} </select> </mapper> 复制代码
  • 测试类 package org.apache.ibatis.anzhitestmybatis; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.jupiter.api.Test; import java.io.InputStream; import java.io.Reader; public class MainTest { public static void main(String[] args) throws Exception{ //1、创建SqlSessionFactory String resource = "org/apache/ibatis/anzhitestmybatis/mybatis-config.xml"; InputStream stream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream); stream.close(); //2、获取sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); System.out.println(sqlSession); //3、获取mapper MyBookMapper mapper = sqlSession.getMapper(MyBookMapper.class); //4、执行数据库操作,并处理结果集 MyBook goods = mapper.selectBook(200); System.out.println(goods); } } 复制代码
  • 开始测试: //这是jdk中的IO流,与我们的mybatis框架无关 InputStream stream = Resources.getResourceAsStream(resource); //从这里开始,mybatis开动 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream); 复制代码 SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(stream); //进入该方法中 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //创建XMLConfigBuilder对象解析xml配置文件 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. } } } 复制代码 build(parser.parse()); //开始解析 public Configuration parse() { //保证单线程访问 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //解析后封装成一个 parseConfiguration(parser.evalNode("/configuration")); return configuration; } 复制代码 parser.evalNode("/configuration")返回一个XNode类型,交给parseConfiguration进一步解析,即xml中的标签属性 public XNode evalNode(String expression) { return evalNode(document, expression); } 复制代码 parseConfiguration,可以看到xml各标签的属性,也知道为什么再xml配置时,标签的约束规则为什么是这样的 private void parseConfiguration(XNode root) { try { //issue #117 read properties first 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")); 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); } } 复制代码 进入这里后,通过Evaluate查看root此时的属性,右键root,然后点击Evaluate,如图:


  • 再右键拷贝value值:

    1. <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/demo"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml"/> </mappers> </configuration> 复制代码 此时mybatis-config.xml已经被解析出来 那么数据源是如何载入的呢? environmentsElement(root.evalNode("environments")); 复制代码 private void environmentsElement(XNode context) throws Exception { ...... //获取dataSource标签的属性,解析数据源,转化为对象 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); ...... } 复制代码 dataSourceElement(child.evalNode("dataSource")); private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); //转化为类对象,一直点resolveClass,往上翻,在TypeAliasRegistry中包含了各种类型 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); } 复制代码 ... registerAlias("float", Float.class); registerAlias("boolean", Boolean.class); registerAlias("byte[]", Byte[].class); registerAlias("long[]", Long[].class); ... 复制代码 至此数据源获取完毕。

    1. 小结:

    获取数据库源过程:

    org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.environmentsElement

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.dataSourceElement

    》org.apache.ibatis.session.Configuration.setEnvironment#######

    4. Mybatis是如何拿到Sql语句

    上面我们知道parseConfiguration可以解析并拿到xml的标签属性,所以再次回到parseConfiguration这里,再次把它贴出来

    private void parseConfiguration(XNode root) {
        try {
    .......
            //数据源获取
            environmentsElement(root.evalNode("environments"));
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            typeHandlerElement(root.evalNode("typeHandlers"));
            //mapper映射
            mapperElement(root.evalNode("mappers"));
    .......
    }
    复制代码

    mapperElement(root.evalNode("mappers"));将MyBookMapper.xml加载出来,那么MyBookMapper加载有几种方式呢? 4种

    <mappers>
        <mapper resource="org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml"/>    
    </mappers>
    ---------------
    <!ELEMENT mappers (mapper*,package*)>
    
    <!ELEMENT mapper EMPTY>
    <!ATTLIST mapper
    resource CDATA #IMPLIED   //1
    url CDATA #IMPLIED        //2
    class CDATA #IMPLIED      //3
    >
    <!ELEMENT package EMPTY>   
    <!ATTLIST package         //4
    name CDATA #REQUIRED
    >
    复制代码

    来看一下mapperElement实现:

    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) { //package优先级第一
                    String mapperPackage = child.getStringAttribute("name");
                    configuration.addMappers(mapperPackage);
                } else {
                    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.");
                    }
                }
            }
        }
    }
    复制代码

    new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());

    mapperParser.parse(); //实现mapper配置文件的解析

    public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
            configurationElement(parser.evalNode("/mapper"));
            configuration.addLoadedResource(resource);
            bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
    }
    复制代码

    configurationElement(parser.evalNode("/mapper")); 此时获取结果如下

    <mapper namespace="org.apache.ibatis.anzhitestmybatis.MyBookMapper">
        <select resultType="org.apache.ibatis.anzhitestmybatis.MyBook" id="selectBook">
            select * from account where balance = #{balance}
        </select>
    </mapper>
    
    复制代码

    那么此时如何解析sql语句呢?

    看一下configurationElement实现

    private void configurationElement(XNode context) {
    .........
        	//sql语句的解析
            buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    ..........
    }
    复制代码

    看一实现

    private void buildStatementFromContext(List<XNode> list) {
      if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
      }
      buildStatementFromContext(list, null); //表明可以有多个sql语句
    }
    复制代码

    buildStatementFromContext实现,sql语句解析

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
      for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
          statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
          configuration.addIncompleteStatement(statementParser);
        }
      }
    }
    复制代码

    statementParser.parseStatementNode()

      public void parseStatementNode() {
        String id = context.getStringAttribute("id");
        String databaseId = context.getStringAttribute("databaseId");
    
        if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
          return;
        }
    
        String nodeName = context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        //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());
    
        String parameterType = context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = resolveClass(parameterType);
    
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        // Parse selectKey after includes and remove them.
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        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;
        }
    
        SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = context.getIntAttribute("fetchSize");
        Integer timeout = context.getIntAttribute("timeout");
        String parameterMap = context.getStringAttribute("parameterMap");
        String resultType = context.getStringAttribute("resultType");
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultMap = context.getStringAttribute("resultMap");
        String resultSetType = context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
        if (resultSetTypeEnum == null) {
          resultSetTypeEnum = configuration.getDefaultResultSetType();
        }
        String keyProperty = context.getStringAttribute("keyProperty");
        String keyColumn = context.getStringAttribute("keyColumn");
        String resultSets = context.getStringAttribute("resultSets");
    
        //最终将select属性在这里填充
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    复制代码

    addMappedStatement

    //构建者设计模式
    public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
    
        ..........
    )
    .........
    
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
    }
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    
    复制代码

    封装成一个类MappedStatement属性:可以发现,一个标签对应一个封装类

    public final class MappedStatement {
    
    ........
      private StatementType statementType;
      private ResultSetType resultSetType;
      private SqlSource sqlSource;
      private Cache cache;
      private ParameterMap parameterMap;
      private List<ResultMap> resultMaps;
      private boolean flushCacheRequired;
      private boolean useCache;
    .........
    复制代码

    来看一下getMappedStatementNames实现:最终实现sql解析完成

    public Collection<String> getMappedStatementNames() {
      buildAllStatements();
      return mappedStatements.keySet();
    }
    复制代码

    至此sql获取完成

    1. 小结

    获取执行语句源码过程:

    org.apache.ibatis.session.SqlSessionFactoryBuilder.build(java.io.InputStream)

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.parse

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration

    》org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement

    》org.apache.ibatis.builder.xml.XMLMapperBuilder.configurationElement

    》org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode

    》org.apache.ibatis.session.Configuration.addMappedStatement######

    5. Mybatis是如何获取结果

    1. sqlSessionFactory.openSession(); public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } 复制代码
    2. openSessionFromDataSource() private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //获取前面设置好的数据源 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(); } } 复制代码 如图:



    1. configuration.newExecutor(tx, execType); 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); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } -------------------- ExecutorType 执行器,类型: public enum ExecutorType { SIMPLE, REUSE, BATCH } 复制代码 最终完成创建一个执行器的过程

    1. 执行查询语句

    sqlSession.selectOne()

    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);
        if (list.size() == 1) {
            return list.get(0);
        } else if (list.size() > 1) { //当结果大于1的时候,抛出异常
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
            return null;
        }
    }
    复制代码

    List list = this.selectList(statement, parameter);

    public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
    }
    复制代码

    selectList(statement, parameter, RowBounds.DEFAULT)

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
      try {
      	//前面添加的addMappedStatement,在这里获取getMappedStatement
        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();
      }
    }
    复制代码

    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        //创建缓存
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    
    复制代码

    query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    @Override
    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);
    }
    复制代码

    delegate.query()

    @Override
    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 #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }
    复制代码

    Executor:

    queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ......
      try {
        list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
      } finally {
        localCache.removeObject(key);
      }
    ......
    }
    复制代码

    doQuery(ms, parameter, rowBounds, resultHandler, boundSql)

    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();
            //判断sql类型
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    
    复制代码

    newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
        switch (ms.getStatementType()) {
            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.getStatementTyp());
        }
    
    }
    复制代码

    prepareStatement(handler, ms.getStatementLog());

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
      Statement stmt;
      //获取连接
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);
      return stmt;
    }
    复制代码

    经过一系列的querry方法处理后最终调用下面这querry,终于活成我们想要的样子,jdbc,最终底层还是由jdbc完成

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
      //jdbc,最终底层还是由jdbc完成
      PreparedStatement ps = (PreparedStatement) statement;
      ps.execute();
      return resultSetHandler.handleResultSets(ps);
    }
    复制代码

    handleResultSets()

    public List<Object> handleResultSets(Statement stmt) throws SQLException {
    .......
      int resultSetCount = 0;
      ResultSetWrapper rsw = getFirstResultSet(stmt);
    .......
    复制代码

    getFirstResultSet

    private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
        ResultSet rs = stmt.getResultSet();
        while (rs == null) {
            // move forward to get the first resultset in case the driver
            // doesn't return the resultset as the first result (HSQLDB 2.1)
            if (stmt.getMoreResults()) {
                rs = stmt.getResultSet();
            } else {
                if (stmt.getUpdateCount() == -1) {
                    // no more results. Must be no resultset
                    break;
                }
            }
        }
        return rs != null ? new ResultSetWrapper(rs, configuration) : null;
    }
    复制代码

    ResultSetWrapper(rs, configuration)

    public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
      super();
      this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
      this.resultSet = rs;
      final ResultSetMetaData metaData = rs.getMetaData();
      //获取数据库信息
      final int columnCount = metaData.getColumnCount();
      for (int i = 1; i <= columnCount; i++) {
        columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
        jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
        classNames.add(metaData.getColumnClassName(i));
      }
    }
    复制代码

    最终体现出mybatis ORM框架


    作者:YuJian
    链接:https://juejin.cn/post/6979169416747581477
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    1. 那么最终是如何拿到这些数据的呢? handleResultSet(rsw, resultMap, multipleResults, null); private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } } # ------------------------------- private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); //获取内容 Object rowValue = getRowValue(rsw, discriminatedResultMap, null); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } 复制代码
    2. getRowValue private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { //赋值 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; } 复制代码 applyPropertyMappings() private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (!autoMapping.isEmpty()) { //对象多个属性循环赋值 for (UnMappedColumnAutoMapping mapping : autoMapping) { final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } return foundValues; } 复制代码 getResult(rsw.getResultSet(), mapping.column); @Override public T getResult(ResultSet rs, String columnName) throws SQLException { try { return getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); } } --------------- @Override public String getNullableResult(ResultSet rs, String columnName){ throws SQLException { return rs.getString(columnName); //最终还是通过JDBC实现 } 复制代码

    2. 小结:


    通过一系列方法解析xml文件(磁盘中)最终生成sqlSession对象(java对象)内存中,然后再通过方法调用查询磁盘,最后将结果返回给sqlSession

    执行操作源码过程:

    org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSession() 》org.apache.ibatis.session.Configuration.newExecutor(org.apache.ibatis.transaction.Transaction,org.apache.ibatis.session.ExecutorType) 》org.apache.ibatis.executor.SimpleExecutor 》org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(java.lang.String, java.lang.Object) 》org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(java.lang.String, java.lang.Object) 》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler) 》org.apache.ibatis.executor.CachingExecutor.query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql) 》org.apache.ibatis.executor.BaseExecutor.queryFromDatabase 》org.apache.ibatis.executor.SimpleExecutor.doQuery 》org.apache.ibatis.executor.statement.PreparedStatementHandler.query

    最终通过handleResultSets返回结果

    3. Annoation注解的方式

    此时不需要MyBookMapper-config.xml配置映射,直接注释

    MyBookMapper-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.apache.ibatis.anzhitestmybatis.MyBookMapper">
    
    <!-- 注释掉,否则会有两个MyBookMapper产生无法区分,报错如下
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for org.apache.ibatis.anzhitestmybatis.MyBookMapper.selectBook. please check org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml and org/apache/ibatis/anzhitestmybatis/MyBookMapper.java (best guess)
    -->
    <!--
      <select id="selectBook" resultType="org.apache.ibatis.anzhitestmybatis.MyBook">
          select * from account where balance = #{balance}
        </select>
    -->
    </mapper>
    复制代码

    mybatis-config.xml配置

    <mappers>
        <!--<mapper resource="org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml"/>-->
        <mapper class="org.apache.ibatis.anzhitestmybatis.MyBookMapper"></mapper>
    </mappers>
    复制代码

    MapperBook

    public interface MyBookMapper {
      @Select("SELECT * FROM account WHERE balance = #{balance}")
      MyBook selectBook(double balance);
    }
    复制代码

    两个问题:

    1. 为什就可以识别MyBookMapper
    2. 调用selectBook与调用Mybatis中的selectone效果一样,怎么实现的? 在MapperAnnotationBuilder中的parse方法中打断点:查看调用栈

    最终通过handleResultSets返回结果

    3. Annoation注解的方式

    此时不需要MyBookMapper-config.xml配置映射,直接注释

    MyBookMapper-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="org.apache.ibatis.anzhitestmybatis.MyBookMapper">
    
    <!-- 注释掉,否则会有两个MyBookMapper产生无法区分,报错如下
    Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for org.apache.ibatis.anzhitestmybatis.MyBookMapper.selectBook. please check org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml and org/apache/ibatis/anzhitestmybatis/MyBookMapper.java (best guess)
    -->
    <!--
      <select id="selectBook" resultType="org.apache.ibatis.anzhitestmybatis.MyBook">
          select * from account where balance = #{balance}
        </select>
    -->
    </mapper>
    复制代码

    mybatis-config.xml配置

    <mappers>
        <!--<mapper resource="org/apache/ibatis/anzhitestmybatis/MyBookMapper.xml"/>-->
        <mapper class="org.apache.ibatis.anzhitestmybatis.MyBookMapper"></mapper>
    </mappers>
    复制代码

    MapperBook

    public interface MyBookMapper {
      @Select("SELECT * FROM account WHERE balance = #{balance}")
      MyBook selectBook(double balance);
    }
    复制代码

    两个问题:

    1. 为什就可以识别MyBookMapper
    2. 调用selectBook与调用Mybatis中的selectone效果一样,怎么实现的? 在MapperAnnotationBuilder中的parse方法中打断点:查看调用栈

    经过多态调用,最终回到了前面提到过的四种级别的判断

    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 {
                    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());
                        //实现mapper配置文件的解析
                        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.");
                    }
                }
            }
        }
    }

    最终得到了和调用selectOne方法一样的结果

    如何知道是MapperAnnotationBuilder中parse方法呢? 两种方式: 1. 逆推法: 因为是使用注解的方法,所以直接到我们自定义的@Select注解语句中,点击@Select

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Select {
      /**
       * Returns an SQL for retrieving record(s).
       *
       * @return an SQL for retrieving record(s)
       */
      String[] value();
    }
    复制代码

    此时然并卵,哈哈,此时再点击Select查看那些方法使用了Select

    点击MapperAnnotationBuilder查看:看一看到有对sql的类型定义

    public class MapperAnnotationBuilder {
        ......
    
            static {
            SQL_ANNOTATION_TYPES.add(Select.class);
            SQL_ANNOTATION_TYPES.add(Insert.class);
            SQL_ANNOTATION_TYPES.add(Update.class);
            SQL_ANNOTATION_TYPES.add(Delete.class);
    
            SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
            SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
            SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
            SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
            ......
                //调用parse方法解析,而xml的解析也是由该方法解析
                public void parse() {
                String resource = type.toString();
                if (!configuration.isResourceLoaded(resource)) {
                    loadXmlResource();
                    configuration.addLoadedResource(resource);
                    assistant.setCurrentNamespace(type.getName());
                    parseCache();
                    parseCacheRef();
                    Method[] methods = type.getMethods();
                    for (Method method : methods) {
                        try {
                            // issue #237
                            if (!method.isBridge()) {
                                parseStatement(method);
                            }
                        } catch (IncompleteElementException e) {
                            configuration.addIncompleteMethod(new MethodResolver(this, method));
                        }
                    }
                }
                parsePendingMethods();
            }
    
        }
    复制代码

    此时在parse的方法的任意一处打断点,debug后,查看调用栈:




    点击去查看,发现该方法是一个多态实现;没办法确定,再看mapperElement,发现又回到了Node解析这一步:此时我们的mybatis配置映射是以class的方式,所以执行if (resource == null && url == null && mapperClass != null)

    //四种标签判断方式
    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 {
            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());
              //实现mapper配置文件的解析
              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.");
            }
          }
        }
      }
    }

    比较快捷的方法:

    在建立sqlSession之后,通过getMapper方法获取映射对象,那么在这个方法中肯定有相关操作,点进去查看:

    <T> T getMapper(Class<T> type);
    复制代码


    点击DefaultSqlSession查看,

    public <T> void addMapper(Class<T> type) {
        mapperRegistry.addMapper(type);  //肯定向里面添加,然后查看那些方法调用了该方法
    }
    @Override
    public <T> T getMapper(Class<T> type) {
      return configuration.getMapper(type, this);  //在这之上还有个addMapper,点击查看
    }


    1. 又找到了XMLMapperBuilder,点击查看 最终同样到了XMLConfigBuilder下的mapperElement方法。

    4. 总结Mybatis xml和annotation优缺点

    Annotaion方式:

    1. 不适合比较复杂的sql 比如关联查询
    2. 不方便(收集)管理sql

    Xml方法:

    1. xml繁琐、麻烦
    2. 条件不确定的查询
    3. 容易出错写错,特殊字符转义 org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations


    作者:YuJian
    链接:https://juejin.cn/post/6979169416747581477
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



    相关推荐

    当Frida来“敲”门(frida是什么)

    0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

    服务端性能测试实战3-性能测试脚本开发

    前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

    Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

    今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

    Linux和Windows下:Python Crypto模块安装方式区别

    一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

    Python 3 加密简介(python des加密解密)

    Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

    怎样从零开始编译一个魔兽世界开源服务端Windows

    第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

    附1-Conda部署安装及基本使用(conda安装教程)

    Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

    如何配置全世界最小的 MySQL 服务器

    配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

    如何使用Github Action来自动化编译PolarDB-PG数据库

    随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

    面向NDK开发者的Android 7.0变更(ndk android.mk)

    订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

    信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

    问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

    OpenSSH 安全漏洞,修补操作一手掌握

    1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

    Linux:lsof命令详解(linux lsof命令详解)

    介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

    幻隐说固态第一期:固态硬盘接口类别

    前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

    新品轰炸 影驰SSD多款产品登Computex

    分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...