千家信息网

Mybatis工作原理

发表于:2025-12-03 作者:千家信息网编辑
千家信息网最后更新 2025年12月03日,作者:wuxinliulei链接:https://www.zhihu.com/question/25007334/answer/266187562来源:知乎著作权归作者所有。商业转载请联系作者获得授权
千家信息网最后更新 2025年12月03日Mybatis工作原理

作者:wuxinliulei
链接:https://www.zhihu.com/question/25007334/answer/266187562
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Mybatis原名Ibatis,在2011年从Ibatis2.x升级到Mybatis 3.X,并将项目地址从Apache迁移到了Google code,事实上我们看MyBatis的类全路径名,还是保留了Apache和Ibatis的的包前缀

import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;

不过MyBatis的配置文件以及操作类和实现方式都有了很大变化,这里我们重点讲述的是Mybatis,不是Ibatis;


Mybatis的配置文件一共由两类:

一类用于指定数据源、事务属性以及其他一些参数配置信息(通常是一个独立的文件,可以称之为全局配置文件);

另一类则用于 指定数据库表和程序之间的映射信息(可能不止一个文件,我们称之为映射文件)

这些文件的名字并没有确定的要求;只是要最从特定的dtd的xml文件约束,即xml标签需要符合要求;

                                                                                                                                                                

上述就是MyBatis的数据源,事务属性,以及映射文件的索引;

        

上面是数据库表与程序之间的映射文件,定义了一个根据id来获取User对象的sql

package com.test.domain;/** * users表所对应的实体类 */public class User {    //实体类的属性和表的字段名称一一对应    private int id;    private String name;    private int age;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    @Override    public String toString() {        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";    }}

问题:

mybatis是怎么在程序中顺利的找到sqlmapper的,这个的流程是怎么样??

// mybatis的配置文件String resource = "conf.xml";// 使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);// 构建sqlSession的工厂SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

题主问的sqlmapper可以理解为两种组件,一种是mapping映射文件,通过id名来获取相应的sql语句,操作数据库;一种是sql的返回对象,

resultType="com.test.domain.User"

这个就是返回的sql结果映射成为具体的POJO(Plain Ordinary Java Object)对象;

两个重要的类即:

org.apache.ibatis.session.SqlSessionFactory;

org.apache.ibatis.session.SqlSession;

package org.apache.ibatis.session;import java.sql.Connection;public interface SqlSessionFactory {  SqlSession openSession();  SqlSession openSession(boolean autoCommit);  SqlSession openSession(Connection connection);  SqlSession openSession(TransactionIsolationLevel level);  SqlSession openSession(ExecutorType execType);  SqlSession openSession(ExecutorType execType, boolean autoCommit);  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);  SqlSession openSession(ExecutorType execType, Connection connection);  Configuration getConfiguration();}

在构建SqlSessionFactory类的时候,将会对数据源及事务配置进行解析,具体在

org.apache.ibatis.builder.xml.XMLConfigBuilder类

org.apache.ibatis.builder.BaseBuilder类

XMLConfigBuilder类是解析产生org.apache.ibatis.Session.Configuration类的的具体类,Configuration类中将保存中所有的配置;

mybatis的源代码解析(1)--xml文件解析 - 王久勇 - 博客园

这篇博客介绍了一些xml文件解析的基本;

具体mybatis的xml解析使用到了XPath方式,具体解析过程参看

https:// zhuanlan.zhihu.com/p/31 418285

其实一般各种轮子都会有一个解析XML后信息的专用存储类,比如Config.Java,xxxConf.java,都是在启动组件时解析XML配置以用作程序中使用的。

引用网络上的一段源代码

public class Test1 {    public static void main(String[] args) throws IOException {        //mybatis的配置文件        String resource = "conf.xml";        //使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)        InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);        //构建sqlSession的工厂        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);        //使用MyBatis提供的Resources类加载mybatis的配置文件(它也加载关联的映射文件)        //Reader reader = Resources.getResourceAsReader(resource);         //构建sqlSession的工厂        //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);        //创建能执行映射文件中sql的sqlSession        SqlSession session = sessionFactory.openSession();        /**         * 映射sql的标识字符串,         * me.gacl.mapping.userMapper是userMapper.xml文件中mapper标签的namespace属性的值,         * getUser是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL         */        String statement = "me.gacl.mapping.userMapper.getUser";//映射sql的标识字符串        //执行查询返回一个唯一user对象的sql        User user = session.selectOne(statement, 1);        System.out.println(user);    }}

通过跟踪源代码可以看到SqlSession通过mapper映射的id来查找数据的方法;

org.apache.ibatis.session.defaults.DefaultSqlSession类

public  List selectList(String statement, Object parameter, RowBounds rowBounds){     try     {         MappedStatement ms = configuration.getMappedStatement(statement);         List result = executor. query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);         return result;     }     catch (Exception e)     {        throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);     }     finally     {        ErrorContext.instance().reset();       }}

org.apache.ibatis.session.Configuration类

public MappedStatement getMappedStatement(String id){        return this.getMappedStatement(id, true);}


protected final Map mappedStatements = new StrictMap("Mapped Statements collection");


public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements){        if (validateIncompleteStatements)        {                buildAllStatements();        }        return mappedStatements.get(id);}

其实就是根据一个map映射,key就是定义mapping时候的id来拿到的;

至此,

------------------------------



上述org.apache.ibatis.session.defaults.DefaultSqlSession类对象中的 selectList方法中的executor对象,

在默认情况下,即没有设置settings的cache和executor属性时,默认使用的

org.apache.ibatis.executor.CachingExecutor类

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit){                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, autoCommit);                }                executor = (Executor) interceptorChain.pluginAll(executor);                return executor;}


所以调用到了

public  List 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);}

在真正查询时先查询cache,可以看到这个cache层级在MappedStatement上,也就是在单个Sql上;若查到,则直接返回,无则通过jdbc查询,且返回结果

public  List 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, key, parameterObject, boundSql);                        if (!dirty)                        {                                cache.getReadWriteLock().readLock().lock();                                try                                {                                        @SuppressWarnings("unchecked")                                        List cachedList = (List) cache.getObject(key);                                        if (cachedList != null)                                                return cachedList;                                }                                finally                                {                                        cache.getReadWriteLock().readLock().unlock();                                }                        }                        List list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);                        tcm.putObject(cache, key, list); // issue #578. Query must be                                                                                                // not synchronized to                                                                                                // prevent deadlocks                        return list;                }        }        return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

上述的使用方式是未使用代理的方式,这样需要我们自行openSession并且关闭Session;

SqlSession session = null;try{        session = sessionFactory.openSession();        /**         * 映射sql的标识字符串, com.test.mapping.userMapper是userMapper.         * xml文件中mapper标签的namespace属性的值,         * getUser是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL         */        String statement = "com.test.mapping.userMapper.getUser";// 映射sql的标识字符串        // 执行查询返回一个唯一user对象的sql        User user = session.selectOne(statement, 1);        System.out.println(user);}catch (Exception e){        // TODO: handle exception}finally{        if (session != null)        {                session.close();        }}

事实上如果我们使用SqlSessionManager来管理,那么开启和关闭Session操作都不用我们来处理了。

final SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sessionFactory);String statement = "com.test.mapping.userMapper.getUser";// 映射sql的标识字符串User user = sqlSessionManager.selectOne(statement, 1);System.out.println(user);

下面是Interceptor类实现,开启和关闭操作都交由了

private class SqlSessionInterceptor implements InvocationHandler{        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable        {                final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();                if (sqlSession != null)                {                        try                        {                                return method.invoke(sqlSession, args);                        }                        catch (Throwable t)                        {                                throw ExceptionUtil.unwrapThrowable(t);                        }                }                else                {                        final SqlSession autoSqlSession = openSession();                        try                        {                                final Object result = method.invoke(autoSqlSession, args);                                autoSqlSession.commit();                                return result;                        }                        catch (Throwable t)                        {                                autoSqlSession.rollback();                                throw ExceptionUtil.unwrapThrowable(t);                        }                        finally                        {                                autoSqlSession.close();                        }                }        }}

如果使用Mapper方式来操作SQL,就是利用动态代理,可以避免我们手写mapper的id字符串,将查找sql过程和执行sql过程放到了代理处理中,更优雅些,不过大体流程就是这些,改变了查找sql的步骤,通过Mapper的方法名来查找对应的sql的,


文件 配置 属性 对象 数据 标签 字符 字符串 就是 方式 标识 查询 程序 事务 作者 信息 工厂 数据库 数据源 方法 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 计算机软件开发的方式有 2017年网络安全发展报告 squad设置服务器密码 空间数据库挖掘 五角大楼网络安全问题 鞋类排版软件开发公司 论坛的数据库怎么写 网络安全责任谁运营谁负责 苏州百世网络技术有限公司 酒店管理系统软件开发公司 ssh登录服务器配置 网络安全大队是干什么用的 网络技术与应用就业方向 中外标准数据库网址 网络安全电视剧美国 mapgis软件连接数据库 360服务器安全维护 网络经营者违反网络安全规定 数据库开头加11位 广州成美互联网科技有限公司 涉密信息系统软件开发业务范围 cmd中查询所有的数据库 电脑绝地求生亚服怎么设置服务器 盈科思泰网络技术有限公司 末日之刃服务器人口普查2022 系统软件开发创新服务 应用数据库案例设计编辑 如何安装服务器系统安装 软件开发公司初步上班做什么 软件开发用什么代码
0