千家信息网

Mybatis工作原理

发表于:2024-11-11 作者:千家信息网编辑
千家信息网最后更新 2024年11月11日,作者:wuxinliulei链接:https://www.zhihu.com/question/25007334/answer/266187562来源:知乎著作权归作者所有。商业转载请联系作者获得授权
千家信息网最后更新 2024年11月11日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的,


0