栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Mybatis技术的本质

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Mybatis技术的本质

1. Mybatis技术的本质

ORM框架:Object Relational Mapping

用于实现面向对象的编程语言里,不同类型系统数据 之间的转换

2.用源码去验证Mybatis如何获取数据库源的:

下面是层层调用结构


然后我们自己从上面打断点的地方dubug开始看源码是否如这样(验证才是检验真理的标准)

调用build方法
1. SqlSessionFactory build(InputStream inputStream)
2.XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());

   public Configuration parse() {
        if (this.parsed) {//刚开始parsed是false 保证xml文件只被加载一次
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true; //设置为true
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

同时通过第四步,你也知道mybatis.xml文件中各个属性设置的顺序,因为他们读取的顺序是受dtd约束的.(比如你把properties放在后面,那么在读environment时候不能用$取其中文件)

4. root其实就是去用来存拿到的配置文件,打断点可以看出来其值
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));//解析map配置文件
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }
5.把上面你的其中一个方法点开,你会发现他会把配置文件的信息里面的东西塞进到configuration中
private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            if (this.environment == null) {
                this.environment = context.getStringAttribute("default");//得到默认注册的环境
            }

            Iterator var2 = context.getChildren().iterator();

            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String id = child.getStringAttribute("id");
                //只有与默认环境匹配才会进去,如果默认环境没有会throw new BuilderException("No environment specified.");
                if (this.isSpecifiedEnvironment(id)) {
                    TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
                    //得到数据源工厂
                    DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                    //数据源工厂去得到数据源
                    DataSource dataSource = dsFactory.getDataSource();
                    Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                    this.configuration.setEnvironment(environmentBuilder.build());//全局配置设置这个属性
                }
            }
        }

    }
先说下最后一行代码build底层是取new Environment把信息啥的传进去掉有参
public Environment build() {
    return new Environment(this.id, this.transactionFactory, this.dataSource);
}
 6.
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            String type = context.getStringAttribute("type");
            //name value以键值对形式取出来  properties.setProperty(name, value);
            Properties props = context.getChildrenAsProperties();
            //resolveClass(type)得到的是一个class对象,通过newInstanc去创建一个dataSource工厂
            DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
            //数据源工厂中设置属性
            factory.setProperties(props);
            return factory;
        } else {
            throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }
    }
7.返回的是个class
protected  Class resolveClass(String alias) {
    if (alias == null) {
        return null;
    } else {
        try {
            //这个alias在这里是POOLED
            return this.resolveAlias(alias);
        } catch (Exception var3) {
            throw new BuilderException("Error resolving class. Cause: " + var3, var3);
        }
    }
}
6. mybatis如何执行sql语句

JD面试题:mybatis解析Mapping有几种方式

4种

前面几步和如何获取数据库源一样

package优先级最高 其次resource 然后 url 最后mapperClass

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator var2 = parent.getChildren().iterator();

        while(true) {
            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String resource;
                if ("package".equals(child.getName())) {
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    XMLMapperBuilder mapperParser;
                    InputStream inputStream;
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        inputStream = Resources.getResourceAsStream(resource);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else {
                        if (resource != null || url != null || mapperClass == null) {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }

                        Class mapperInterface = Resources.classForName(mapperClass);
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }

            return;
        }
    }
}
2.如果是resource引入的mapper.xml那么会走mapperParser.parse();,然后进入下面这个方法
private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.equals("")) {
            this.builderAssistant.setCurrentNamespace(namespace);
       
            
            this.cacheRefElement(context.evalNode("cache-ref"));
            this.cacheElement(context.evalNode("cache"));
         
               this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
            this.sqlElement(context.evalNodes("/mapper/sql"));
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
    }
}
private void buildStatementFromContext(List list) {
    if (this.configuration.getDatabaseId() != null) {
        this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }

    this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List list, String requiredDatabaseId) {
    Iterator var3 = list.iterator();

    while(var3.hasNext()) {
        XNode context = (XNode)var3.next();
        XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException var7) {
            this.configuration.addIncompleteStatement(statementParser);
        }
    }

}

context就是你写在mapper.xml中的sql,在这标签上你可能有各种参数 ,通过context可以拿到,最后拿到后放进addMappedStatement

public void parseStatementNode() {
    String id = this.context.getStringAttribute("id");
    String databaseId = this.context.getStringAttribute("databaseId");
    if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        String nodeName = this.context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
        boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
        boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
        includeParser.applyIncludes(this.context.getNode());
        String parameterType = this.context.getStringAttribute("parameterType");
        Class parameterTypeClass = this.resolveClass(parameterType);
        String lang = this.context.getStringAttribute("lang");
        LanguageDriver langDriver = this.getLanguageDriver(lang);
        this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
        String keyStatementId = id + "!selectKey";
        keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
        Object keyGenerator;
        if (this.configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }

        SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
        StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        Integer fetchSize = this.context.getIntAttribute("fetchSize");
        Integer timeout = this.context.getIntAttribute("timeout");
        String parameterMap = this.context.getStringAttribute("parameterMap");
        String resultType = this.context.getStringAttribute("resultType");
        Class resultTypeClass = this.resolveClass(resultType);
        String resultMap = this.context.getStringAttribute("resultMap");
        String resultSetType = this.context.getStringAttribute("resultSetType");
        ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
        if (resultSetTypeEnum == null) {
            resultSetTypeEnum = this.configuration.getDefaultResultSetType();
        }

        String keyProperty = this.context.getStringAttribute("keyProperty");
        String keyColumn = this.context.getStringAttribute("keyColumn");
        String resultSets = this.context.getStringAttribute("resultSets");
        this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
}

最后都会加入Configuration中,Configuration就是我们的全局配置

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
    if (this.unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
    } else {
        id = this.applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
        ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
            statementBuilder.parameterMap(statementParameterMap);
        }

        MappedStatement statement = statementBuilder.build();
        this.configuration.addMappedStatement(statement);
        return statement;
    }
}
7.Mybatis如何去操作

先说说执行器,主要有三种,默认的是SimpleExecutor,空参构造器new的时候里面赋的默认值

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? this.defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Object 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);
    }
//默认是true,一级缓存开启
    if (this.cacheEnabled) {
        executor = new CachingExecutor((Executor)executor);
    }

    Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
    return executor;
}

mybatis一级缓存默认是开启的,二级缓存默认是关闭的

通过源码看到在new 执行器的时候会默认开启一级缓存,因为cacheEnabled默认值为true,空参构造默认值为true

mybatis是先执行一级缓存还是二级缓存?

先执行二级缓存

调用DefaultSqlSession去赋值

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
}

其实openSession就是创建了一个执行器

干活的还是selectOne方法

selectOne方法会去调用selectList方法

public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var5;
    try {
        //getMapperStatement这里是从里面取,前面我们把sql存在这里了
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        //wrapCollection(parameter)判断参数是什么类型的
        var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception var9) {
        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
    } finally {
        ErrorContext.instance().reset();
    }

    return var5;
}

通过深入源码,这里statement其实就是名字,这个标签的方法名字(可以自己进去点点看看)

public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//得到绑定的sql  这里面sql是:select * from user where id= ? (好比是这条语句,那么他的这里得到绑定sql就有?号)
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //一级缓存中存的key的名字创建
    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
CacheKey的需要四个参数 id(mapper中方法的名字,起始页 每页多少 sql)
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());

阿里有道面试题

A系统给B系统一个sql,如何判断这个sql是一个正确的sql???

  1. 执行sql 去判断肯定不可以,如果是删库跑路的那不是要被裁了

  2. 正则sql 不可以,有些特殊字符和正则有冲突

  3. 语法引擎正确答案

再接着说说上面源码中的缓存创建

    CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);

public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    Cache cache = ms.getCache();
    //cache!=null说明在xml中加了标签开启了二级缓存
    if (cache != null) {
        this.flushCacheIfRequired(ms);
        if (ms.isUseCache() && resultHandler == null) {
            this.ensureNoOutParams(ms, boundSql);
            List list = (List)this.tcm.getObject(cache, key);
            //没有从二级缓存拿到,那么就会接着执行下面的
            if (list == null) {
                list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                this.tcm.putObject(cache, key, list);
            }

            return list;
        }
    }

    return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

query

 try {
                ++this.queryStack;
     //从一级缓存中取,取不到就走else
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            }
private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //先把Object中key放进去,ExecutionPlaceholder.EXECUTION_PLACEHOLDER是个空的
    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;
    try {
        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql); //第一次查询的时候得到list
    } finally {
        //移除之前的
        this.localCache.removeObject(key);
    }
//重新放进这个key
    this.localCache.putObject(key, list);//把list放进去
    if (ms.getStatementType() == StatementType.CALLABLE) {
        this.localOutputParameterCache.putObject(key, parameter);
    }

    return list;
}
public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;

    List var9;
    try {
        Configuration configuration = ms.getConfiguration();
        StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}
//这样调用到java.sql的PreparedStatement,然后调用其execute方法
public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement)statement;
    ps.execute();
    return this.resultSetHandler.handleResultSets(ps);
}
//返回查询结果的
public List handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
    List multipleResults = new ArrayList();
    int resultSetCount = 0;
    ResultSetWrapper rsw = this.getFirstResultSet(stmt);
    List resultMaps = this.mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    this.validateResultMapsCount(rsw, resultMapCount);

    while(rsw != null && resultMapCount > resultSetCount) {
        ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
        this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
        rsw = this.getNextResultSet(stmt);
        this.cleanUpAfterHandlingResultSet();
        ++resultSetCount;
    }

    String[] resultSets = this.mappedStatement.getResultSets();
    if (resultSets != null) {
        while(rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
                this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
            }

            rsw = this.getNextResultSet(stmt);
            this.cleanUpAfterHandlingResultSet();
            ++resultSetCount;
        }
    }

    return this.collapseSingleResultList(multipleResults);
}
 

ORM:用于实现面向对象编程语言,不同类型系统的数据之间的转换

通过下面图片你会发现,mybatis中间做的就是将sql语言与java语言之间进行转换

8.数据库与缓存一致性解决问题
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;//从缓存中取,有了直接返回
if (list != null) {
    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

4个字段用来确保key,

查询条件一样 查询数据库一样 查询分页一样 id(mapper方法的名字)一样

9.来看看getMaper的源码
1. 
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List userList = mapper.orderUser("desc");

DefaultSqlSession中

public  T getMapper(Class type) {
    return this.configuration.getMapper(type, this);
}

MapperRegistry 中的方法

public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }
走到下面执行的还是selectOne方法

下面图片链接

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号