为了账号安全,请及时绑定邮箱和手机立即绑定

Mybatis核心原理

简介
Mybatis是一款流行的持久层框架,基于ORM(Object-Relation Mapper)思想,对针对JDBC的封装,通过xml配置支持灵活复杂的SQL查询。

框架组件架构图
Mybatis核心成员数据流
image-20210604132936811
核心成员说明
图片描述
核心代码流程
1)Mybatis通过SqlSessionFactory获取sqlSession,然后有sqlSession完成数据库的交互。SqlSessionFactory默认接口实现是是DefaultSqlSessionFactory。

//默认new DefaultSqlSessionFactory()
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
}

2) SqlSessionFactory有多个openSession方法,以无参的方法为例。

public SqlSession openSession() {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}

//executor 默认有多个实现,根据ExecutorType可知有以下几种
public enum ExecutorType {
    SIMPLE,
    REUSE,
    BATCH;
    private ExecutorType() {
    }
}

针对Dao层的定义的接口,MapperRegistry维护了Dao层接口的代理工厂,并由工厂生成具体的代理去处理sqlSession,并交底层Executor调度器去执行。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    //MapperMethod对应Dao层接口的方法
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    //根据接口类型生成具体代理
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

Executor调度器与StatementHandler等处理器交互,完成SQL操作

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

并发场景的思考:DefaultSqlSessionFactory是线程安全的么?如何做到线程安全?

是不线程安全的,这个会在后面解释。

缓存机制
mybatis一级、二级缓存
一级缓存核心类是PerpetualCache,本质是一个hashMap

二级缓存默认不开启。

Spring 与Mybatis的整合
Spring bean 生命周期

SpringBean生命周期
MapperScannerConfigurer
MapperScannerConfigurer的主要工作是扫描basePackage包下所有的mapper接口类,并将mapper接口类封装成为BeanDefinition对象,注册到spring的BeanFactory容器中核心类图如下:
MapperScannerConfigurer
以上知道了Spring的bean注册到容器的核心流程,通过理解Spring的核心流程,可以梳理出Dao层接口Mapper通过MapperScannerConfigurer整合到spring的流程:

MapperScannerConfigurer 改
SqlSessionFactoryBean
类定义如下:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
   //省略一些详细代码 
   
   public void setDataSource(DataSource dataSource) {
      if (dataSource instanceof TransactionAwareDataSourceProxy) {
          this.dataSource = ((TransactionAwareDataSourceProxy)dataSource).getTargetDataSource();
      } else {
          this.dataSource = dataSource;
      }
    }
    public void setMapperLocations(Resource[] mapperLocations) {
        this.mapperLocations = mapperLocations;
    }
}

SqlSessionFactoryBean
从这个类的定义可以看出SqlSessionFactoryBean与DataSource和Mapper有关。

在bean被创建的中,通过接口InitializingBean中的afterPropertiesSet方法设置属性。

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方法主要完成SqlSession的初始化操作,完成在上面讲的Mybatis核心代码流程。

SqlSessionFactoryBean整合进Spring的流程
SqlSessionFactory初始化
Mybatis整合Spring的整体流程
以下面简单的代码做主要流程说明

@Service
class AService{
   @Autowire
   private BDao bDao;
}

Spring在初始化的过程中@Service注解的类,AService类初始化完成之后,会进行属性赋值,bDao接口就是AService的一个属性,

1)首先根据这个bDao的名字或者类型从spring的BeanFactory中获取它的BeanDefinition,再从BeanDefinition中获取BeanClass,bDao对应的BeanClass就是MapperFactoryBean,这在创建MapperScannerConfigurer对象的时候设置的。

2)创建MapperFactoryBean对象,创建完成后,对属性进行赋值,其中有一个属性就是SqlSessionFactoryBean

3)MapperFactoryBean对象的属性设置完成之后,就调用它的getObject()方法,来获取bDao对应的实现类,获取的是一个JDK的代理类

public class MapperProxy<T> implements InvocationHandler, Serializable {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            return mapperMethod.execute(this.sqlSession, args);
        }
    }
}

程序在调用AService对象的某个方法的时候,就会调用到MapperProxy对象的invoke()方法,去完成对数据库的操作。

如何解决SqlSession的线程安全问题
MapperFactoryBean.getObject()获取的实例,实际是通过一个SqlSessionTemplate对象创建的,注入的Mapper对象实际上最终都执行的是SqlSessionTemplate方法。
SqlSessionTemplate
关键代码如下:

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;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }

        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;
        }
    }

image-20210527212038675
image-20210527212403567

————————————————
版权声明:本文为CSDN博主「slagsea」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_34147021/article/details/118885110

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消