从源码角度理解Mybatis原理

mybatis继承自Ibatis

一、提供了Ibatis的编程方式(通过SqlSession对象调用mybatis封装的Api)

分位三步:

1、创建SqlSessionFactoryBuilder对象

2、通过SqlSessionFactoryBuilder创建SqlSessionFactory对象(解析xml配置过程)

3、SqlSessionFactory对象OpenSession方法,返回SqlSession,调用SqlSession的相应Api。

String conf = "Mybatis/SqlMapConfig.xml";
Reader reader = null;
try {
	reader = Resources.getResourceAsReader(conf);
} catch (IOException e) {
    e.printStackTrace();
}
//创建SqlSessionFactoryBuilder
SqlSessionFactoryBuilder sfb = new SqlSessionFactoryBuilder();
//build方法时解析mybatis配置信息。返回SqlSessionFactory
SqlSessionFactory ssf = sfb.build(reader);
//
SqlSession session = ssf.openSession();

SqlSessionFactoryBuilder的build方法的源码如下

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
return var5;

解析配置文件,将jdbc配置、mapper映射接口等封装到Configuration对象中。XMLConfigBuilder的parse方法就是解析xml配置,将xml配置存入configuration对象的过程。解析过程中,mapper里每一条sql都会解析成一个MappedStatement对象。存放sqlId,参数类型,返回值类型等等。

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

调用DefaultSqlSessionFactory的OpenSession方法,得到SqlSession对象

Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);

创建SqlSession对象的时候,创建了一个Executor,符合单一职责原则,查询数据库就是由Executor来执行,底层实现是由原生Jdbc一致。

二、提供了基于接口的编程方式(使用动态代理模式访问SqlSession的Api)

mapper接口映射应该遵循下面的规范:

1、Mapper.xml文件中的nameSpace于mapper接口的类路径相同。

2、Mapper接口方法名称和Mapper.xml中定义的每个sql的Id相同。

3、parameterType和resultType的类型相同。

例如:创建LeaderMapper接口,定义selectLeaderList方法

public interface LeaderMapper {

    List  selectLeaderList();
}

创建LeaderMapper.xml



    

调用时:

LeaderMapper leaderService = session.getMapper(LeaderMapper.class);
List list = leaderService.selectLeaderList();

问题:在我们的工程中,并没有定义接口的实现,但是接口是怎么实现查询数据库的呢?

在mybatis配置文件中配置了TypeAlias扫描包时,解析生成Configuration时会调用MapperRegistry的addMapper方法。

public  void addMapper(Class type) {
    this.mapperRegistry.addMapper(type);
}

public  void addMapper(Class type) {
    if (type.isInterface()) {
        this.knownMappers.put(type, new MapperProxyFactory(type));
    }
}

调用SqlSession的getMapper的方法时,调用MapperRegistry对应的方法getMapper()

public  T getMapper(Class type, SqlSession sqlSession) {
    MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);

    return mapperProxyFactory.newInstance(sqlSession);
}

这个过程其实就是通过MapperProxyFactory动态代理创建对象,每一个对象都会调用SqlSession底层的方法。

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

public T newInstance(SqlSession sqlSession) {
    MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    return this.newInstance(mapperProxy);
}

创建的动态代理对象MapperProxy,我们需要关注它的invoke方法,因为动态代理对象调用都是调用的invoke方法。

MapperProxy的invoke方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
        try {
            return method.invoke(this, args);
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    } else {
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

解释一下这段代码含义:getDeclaringClass方法用来判断当前这个方法是哪个类的方法。接口创建出来的代理对象不仅有实现接口的方法,也有从Object继承过来的方法,如果是Object类继承过来的方法,直接反射调用,否则调用MapperMethod调用。

MapperMethod的execute方法

public Object execute(SqlSession sqlSession, Object[] args) {
    Object param;
    Object result;
    if (SqlCommandType.INSERT == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
    } else if (SqlCommandType.UPDATE == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
    } else if (SqlCommandType.DELETE == this.command.getType()) {
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
    } else {
        if (SqlCommandType.SELECT != this.command.getType()) {
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
        }
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

MapperMethod调用时,根据method的传参,sql语句解析的xml确定调用SqlSession的相应的方法。

文章如描述有差错,可互相交流探讨。

你可能感兴趣的:(java,mybatis)