spring-mybatis集成源码分析

简述

1.在IOC容器的注册过程中,将mapper->MapperFactoryBean注册到容器的beanDefinitionMap中
2.配置SqlSessionFactoryBean,完成mybatis的Configuration的配置以及生成SqlSessionFactory
3.然后通过MapperFactoryBean(工厂bean)的getObject创建Mapper的代理对象MapperProxy
4.查询时openSession,执行查询

源码分析

SqlSessionFactory如何构建

SqlSessionFactoryBean实现InitializingBean,因此在bean初始化完成后会调用afterPropertiesSet方法
由以下代码可知,SqlSessionFactoryBean完成SqlSessionFactory的构建并且将它作为自己的成员变量

public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

buildSqlSessionFactory方法解析mybatis的configLocation 和mapperLocations文件

//以下代码为删减后的代码
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration targetConfiguration;
//配置解析
        if (this.configLocation != null) {
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        } 
//解析mapper.xml
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] var24 = this.mapperLocations;
            int var4 = var24.length;

            for(int var5 = 0; var5 < var4; ++var5) {
                Resource mapperLocation = var24[var5];
                if (mapperLocation != null) {
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var19) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                    } finally {
                        ErrorContext.instance().reset();
                    }

                    LOGGER.debug(() -> {
                        return "Parsed mapper file: '" + mapperLocation + "'";
                    });
                }
            }
          }
        return this.sqlSessionFactoryBuilder.build(targetConfiguration);
    }
mapper的代理

mapper的代理由getBean方法作为入口,由于mapper对应的MapperFactoryBean为factoryBean,所有调用它的getObject方法

public T getObject() throws Exception {
        return this.getSqlSession().getMapper(this.mapperInterface);
    }

追踪发现,最终还是调用了mybatis的MapperRegisty的getMapper方法,生成代理对象。最后执行this.factoryBeanObjectCache.put(beanName, object)将生成的代理对象放入容器。

sql执行

mapper被MapperProxy代理,调试发现,最终执行SqlSessionTemplate的selectOne方法

    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }

sqlSessionProxy是在SqlSessionTemplate构造时创建的一个动态代理对象,所以调用SqlSessionInterceptor的invoke方法
由代码可知,首先会获取一个sqlSession,然后执行mybatis的语句执行流程,紧接着进行sqlSession的提交或释放

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            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;
        }

获取session的时候,如果没有开启事务,那么直接调用openSession开启一个新的session,如果是开启事务的情况,那么将当前事务存储在当前的线程变量中,以便事务中其他执行时获取

    public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
        Assert.notNull(executorType, "No ExecutorType specified");
        SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
        SqlSession session = sessionHolder(executorType, holder);
        if (session != null) {
            return session;
        } else {
            LOGGER.debug(() -> {
                return "Creating a new SqlSession";
            });
            session = sessionFactory.openSession(executorType);
            registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
            return session;
        }
    }
对比mybatis

1.session的创建
mybatis中session的创建和sql的执行是分开的,但是spring-mybatis中是在sql的执行调用时,通过代理的方式在sql执行前获取session。因此在不存在事务的情况,spring-mybatis的一级缓存是没有作用的
2.mapper的作用域
mybatis每次获取mapper都会生成它的代理对象,因此mybatis的mapper是session级别的;spring-mybatis在第一次获取后放入factoryBeanObjectCache,以后都是从factoryBeanObjectCache中获取,因此它是全局单例的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353