资讯专栏INFORMATION COLUMN

MyBatis的原理

Yu_Huang / 3265人阅读

摘要:不是线程安全的,所以在使用的时候一定要保证他是局部变量。他对应的类图如下有几种常见的实现是默认的非线程安全的实现是中对的线程安全实现,在内部是使用的的形式来保证线程安全的是的核心。是线程安全的,可以被多个或映射器所共享使用。

MyBatis核心类
SqlSessionFactory

每一个MyBatis应用都是以一个SqlSessionFactory的实例为核心构建的。SqlSessionFactory的核心作用是什么?

从类的名称上可以看出来,SqlSessionFactory是产生SqlSession的工厂。SqlSessionFactory是通过SqlSessionFactoryBuilder这个构建器来构建的。

SqlSessionFactory是一个接口,其中定义了获取SqlSession的方法。

public interface SqlSessionFactory {
    //获取一个SqlSession
    SqlSession openSession();
    //获取一个SqlSession,参数设置事务是否自动提交
    SqlSession openSession(boolean var1);
    //通过指定Connection中的参数获取
    SqlSession openSession(Connection var1);
    //获取SqlSession,设置事务的隔离级别,NONE(0),READ_COMMITTED(2),READ_UNCOMMITTED(1),REPEATABLE_READ(4),SERIALIZABLE(8);
    SqlSession openSession(TransactionIsolationLevel var1);
    //获取SqlSession,同时制定执行的类别,支持三种SIMPLE(这种模式下,将为每个语句创建一个PreparedStatement),REUSE(这个模式下重复使用preparedStatment),BATCH(批量更新,insert时候,如果没有提交,无法获取自增id);
    SqlSession openSession(ExecutorType var1);
    SqlSession openSession(ExecutorType var1, boolean var2);
    SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
    SqlSession openSession(ExecutorType var1, Connection var2);
    //获取所有的配置项
    Configuration getConfiguration();
}

SqlSessionFactory包含两个实现:DefaultSqlSessionFactorySqlSessionManager
SqlSessionFactory的实例如何创建呢?
1、通常我们是在只有MyBatis的项目中是使用下面的代码段来创建的:

String resource = "org/mybatis/example/mybatis-config.xml";
//读取mybatis配置的问题
InputStream inputStream = Resources.getResourceAsStream(resource);
//通过SqlSessionFactoryBuilder的build方式创建
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2、在Spring boot和MyBatis结合的项目中我会使用下面的代码段来创建:

MyBatis-Spring-Boot-Starter依赖将会提供如下

自动检测现有的DataSource

将创建并注册SqlSessionFactory的实例,该实例使用SqlSessionFactoryBean将该DataSource作为输入进行传递

将创建并注册从SqlSessionFactory中获取的SqlSessionTemplate的实例。

自动扫描您的mappers,将它们链接到SqlSessionTemplate并将其注册到Spring上下文,以便将它们注入到您的bean中。

就是说,使用了该Starter之后,只需要定义一个DataSource即可(application.properties中可配置),它会自动创建使用该DataSource的SqlSessionFactory Bean以及SqlSessionTemplate。会自动扫描你的Mappers,连接到SqlSessionTemplate,并注册到Spring上下文中.

@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("druidDataSource") DataSource druidDataSource) throws Exception {
    //先创建一个SqlSessionFactoryBean
    SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
    //在这个bean里设置需要的参数
    fb.setDataSource(this.dynamicDataSource(druidDataSource));
    fb.setTypeAliasesPackage(env.getProperty("mybatis.type-aliases-package"));
    fb.setTypeHandlersPackage(env.getProperty("mybatis.type-handlers-package"));
    fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
    //通过这个方法获取一个SqlSessionFactory
    return fb.getObject();
}

在代码里一直向下查找的时候就会发现:这个过程其实和上面的过程一样。
SqlSessionFactoryBean会将一系列的属性封装成一个Configuration对象,然后调用
this.sqlSessionFactoryBuilder.build(configuration) 来创建。而在sqlSessionFactoryBuilder里主要就是解析资源的内容,然后进行创建。

在这里有分别使用了两个设计模式:

工厂模式

SqlSessionFactory就是一个工厂模式——简单工厂模式的变形实现。
通过SqlSessionFactory中重载的不同openSession()方法来获取不同类型的实例。

建造者模式(build模式)

SqlSessionFactoryBuilder创建SqlSessionFactory就是建造者模式的实现。在创建的过程中需要解析很多的文件,生成对象,进行缓存等操作,所以一个方法是很难直接写完,所以其中应用了大量的build模式:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration configuration;
    if (this.configuration != null) {
       ......
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        ......
    }
    ......

    if (xmlConfigBuilder != null) {
        try {
            //builder解析
            xmlConfigBuilder.parse();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Parsed configuration file: "" + this.configLocation + """);
            }
        } catch (Exception var22) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
        } finally {
            ErrorContext.instance().reset();
        }
    }
    ......
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] var29 = this.mapperLocations;
        var27 = var29.length;

        for(var5 = 0; var5 < var27; ++var5) {
            Resource mapperLocation = var29[var5];
            if (mapperLocation != null) {
                try {
                    //Mapper文件build
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception var20) {
                    
                } finally {
                    //单例模式
                    ErrorContext.instance().reset();
                }
            }
        }
    } 
    //build一个对象返回
    return this.sqlSessionFactoryBuilder.build(configuration);
}
SqlSession

SqlSessionFactory创建完成之后,就可以通过SqlSessionFactory对象来获取一个SqlSession实例.SqlSession是一次与数据库的会话.在他的接口中定义了一些列的CRUD和事务的操作接口。SqlSession是暴露给用户使用的API,一个SqlSession对应着一次数据库会话。SqlSession不是线程安全的,所以在使用的时候一定要保证他是局部变量。
他对应的类图如下:

SqlSession有几种常见的实现:DefaultSqkSession是默认的非线程安全的实现,SqlSessionManager是Mybatis中对SqlSession的线程安全实现,在内部是使用的private ThreadLocal localSqlSession = new ThreadLocal();的形式来保证线程安全的,SqlSessionTemplate是MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。

因为SqlSessionTemplate是线程安全的,所以当SqlSessionTemplate是单例的时候,多线程调用SqlSessionTemplate仍然使用的是同一个SqlSession,接下来看一下SqlSessionTemplate是如何保证线程安全的呢?

首先我们看一下SqlSessionTemplate的创建过程:

/**
 * 构造函数1,需要传入参数SqlSessionFactory
 */
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sqlSessionFactory, "Property "sqlSessionFactory" is required");
    Assert.notNull(executorType, "Property "executorType" is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //创建代理类的实例,该代理类实现了SqlSession接口,这里使用的是基于JDK的动态代理,SqlSessionInterceptor也是实现了InvocationHandler接口,最后代理对象的操作都会经过invoke执行
    //class SqlSessionInterceptor implements InvocationHandler
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

invoke的实现是实现线程安全的核心:

private class SqlSessionInterceptor implements InvocationHandler {
    private SqlSessionInterceptor() {
    }
  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     //获取真实的SqlSession,这个是真实使用的,是MyBatis生成的DefaultSqlSession,获取是线程安全实现的核心
        SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

        Object unwrapped;
        try {
            //执行操作
            Object result = method.invoke(sqlSession, args);
            //如果不是事务类型的,那么设置为自动提交
            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                sqlSession.commit(true);
            }
            //执行结果包装
            unwrapped = result;
        } catch (Throwable var11) {
            unwrapped = ExceptionUtil.unwrapThrowable(var11);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                sqlSession = null;
                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }

            throw (Throwable)unwrapped;
        } finally {
            if (sqlSession != null) {
                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }

        }
        //返回执行结果
        return unwrapped;
    }
}

接着看getSqlSession()的代码:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    //SqlSessionHolder是对SqlSession的一个功能包装,TransactionSynchronizationManager是一个事务同步管理器,维护当前线程事务资源,信息以及TxSync集合,getResource会从 ThreadLocal> resources 中获取当前线程SqlSessionHolder实例
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    } else {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Creating a new SqlSession");
        }
        //如果没有获取成功,那么开启一个SqlSession
        session = sessionFactory.openSession(executorType);
        //注册SessionHolder
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

registerSessionHolder()实现?

好难啊~~~~~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74713.html

相关文章

  • 带你深入浅出MyBatis技术原理与实战(PDF实战实践)

    摘要:目录其中每个章节知识点都是相关连由浅入深的一步步全面分析了技术原理以及实战由于文案较长想深入学习以及对于该文档感兴趣的朋友们可以加群免费获取。这些场景在大量的编码中使用,具备较强的实用价值,这些内容都是通过实战得来的,供读者们参考。 前言系统掌握MyBatis编程技巧已经成了用Java构建移动互联网网站的必要条件 本文主要讲解了Mybatis的应用,解析了其原理,从而形成一个完整的知识...

    MoAir 评论0 收藏0
  • Mybatis常见面试题

    摘要:执行没有,批处理不支持,将所有都添加到批处理中,等待统一执行,它缓存了多个对象,每个对象都是完毕后,等待逐一执行批处理。 Mybatis常见面试题 #{}和${}的区别是什么? #{}和${}的区别是什么? 在Mybatis中,有两种占位符 #{}解析传递进来的参数数据 ${}对传递进来的参数原样拼接在SQL中 #{}是预编译处理,${}是字符串替换。 使用#{}可以有效的防止...

    liuchengxu 评论0 收藏0
  • 面试官都会问Mybatis面试题,你会这样回答吗?

    摘要:最终能和面试官聊的开心愉快投缘的叫面霸。能够与很好的集成提供映射标签,支持对象与数据库的字段关系映射提供对象关系映射标签,支持对象关系组件维护。使用可以有效的防止注入,提高系统安全性。 showImg(https://segmentfault.com/img/bVbsSlt?w=358&h=269); 一、概述 面试,难还是不难?取决于面试者的底蕴(气场+技能)、心态和认知及沟通技巧。...

    seanHai 评论0 收藏0
  • 手撕面试官系列(二):开源框架面试题Spring+SpringMVC+MyBatis

    摘要:跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来期间也没有准备充分,到底是因为技术原因影响自己的发展,偏移自己规划的轨迹,还是钱给少了,不受重视。 跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来(期间也没有准备充分),到底是因为技...

    Flink_China 评论0 收藏0
  • 教你手写Mybatis框架

    摘要:前言嗨,小伙伴们,这篇博文将带大家手写,让大家对的核心原理以及工作流程有更加深刻的理解。模块顾名思义,就是框架配置类,用于解析配置文件加载相关环境。配置模块这里的对框架的配置使用了简单的,主要原因还是简单易懂然后节省时间。 前言 (。・∀・)ノ゙嗨,小伙伴们,这篇博文将带大家手写mybatis,让大家对mybaits的核心原理以及工作流程有更加深刻的理解。在上篇Spring-Mybat...

    antyiwei 评论0 收藏0
  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<