Skip to content

SeasonPanPan/minimybatis

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

minimybatis

手写迷你mybatis框架,里面使用了mybatis设计模式和框架 手写思路和框架图详细解释:https://blog.csdn.net/kuailebuzhidao/article/details/88355236

###################### 正文 ####################################### 继上篇手写spring后(https://blog.csdn.net/kuailebuzhidao/article/details/87869279),感觉有必要继续把mybatis框架也手写出来,供深入理解。

网上已经有很多手写框架的博客,但是很多只是按照mybatis流程,面向过程地写:解析xml->代理反射mapper->调用JDBC获取结果。虽然这样理解是对的,但是失去了理解mybatis源码意义。我遵循mybatis源码整体框架和设计,使用源码包名和类名,配合工厂模式和代理模式,写了精简版的mybatis框架。能看懂我这个minmybatis,就能更加容易地理解庞大的源码体系。手写框架的源码已经上传到github上,欢迎下载点星,感谢! github地址:https://github.com/SeasonPanPan/myspring

先看以下spring源码中的类关系图:(CSDN原地址查看)

这个图很清晰地画出了mybatis重要组件类和流程:

从MyBatis源码实现的角度来看,MyBatis的主要的核心部件有以下几个:

Configuration:MyBatis所有的配置信息都维持在Configuration对象之中,核心类;

SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;

Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;

StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。

ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;

ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;

MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;

MapperProxy和MapperProxyFactory:Mapper代理,使用原生的Proxy执行mapper里的方法。

我从测试类main函数说说整体手写过程。

public static void main(String[] args) {

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build("conf.properties");
SqlSession session = factory.openSession();

UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUser("1");
System.out.println("******* " + user);
System.out.println("*******all " + userMapper.getAll());

} SqlSessionFactory factory = new SqlSessionFactoryBuilder().build("conf.properties");

解释:在SqlSessionFactoryBuilder中加载properties文件配置返回一个默认SqlSessionFactory

public SqlSessionFactory build(InputStream inputStream) { try { Configuration.PROPS.load(inputStream);

}
catch (IOException e)
{
    e.printStackTrace();
}
return new DefaultSqlSessionFactory(new Configuration());

} 最后一句默认sqlSession工厂中扫描mapper.xml将解析后的节点信息存到configuration中。

下面的loadMappersInfo方法中使用dom4j解析xml。

public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; loadMappersInfo(Configuration.getProperty(Constant.MAPPER_LOCATION).replaceAll("\.", "/")); }

private void loadMappersInfo(String dirName) { //... XmlUtil.readMapperXml(file, this.configuration); //... }

/** XmlUtil类 **/ public static void readMapperXml(File fileName, Configuration configuration) { //解析xml步骤省略 //... //设置SQL的唯一ID String sqlId = namespace + "." + element.attributeValue(Constant.XML_ELEMENT_ID);

for (Iterator iterator = rootElement.elementIterator(); iterator.hasNext();)
{
    //...
    statement.setSqlId(sqlId);
    statement.setNamespace(namespace);
    statement.setSql(CommonUtis.stringTrim(element.getStringValue()));
    statements.add(statement);

    System.out.println(statement);
    configuration.addMappedStatement(sqlId, statement);

    //这里其实是在MapperRegistry中生产一个mapper对应的代理工厂
    configuration.addMapper(Class.forName(namespace));
}

}

/** MapperRegistry类 **/ public void addMapper(Class type) { this.knownMappers.put(type, new MapperProxyFactory(type)); //这里生成了代理工厂 } 记住上面代码的最后两句设置MappedStatement和Mapper,后面有用。

 

SqlSession session = factory.openSession();

解释:把上面解析的configuration放在DefaultSqlSession中备用。从下面代码中可以看出,configuration一直传递到执行器里。

public SqlSession openSession() { SqlSession session = new DefaultSqlSession(this.configuration); return session; }

public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; this.executor = new SimpleExecutor(configuration);

}

public SimpleExecutor(Configuration configuration) { this.conf = configuration; } UserMapper userMapper = session.getMapper(UserMapper.class);

解释:重点来了,这里开始从session中获取mapper,真正的还是代理工厂返回了mapper实例,获取过程看下面的代码分析。

/** DefaultSqlSession类 **/ @Override public T getMapper(Class type) { return configuration. getMapper(type, this); }

/** Configuration类 **/ public T getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }

/** MapperRegistry类 **/ public T getMapper(Class type, SqlSession sqlSession) { //前面解析xml做了addMapper操作,这里通过mapper类获取代理工厂 MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);

return mapperProxyFactory.newInstance(sqlSession);

}

/** MapperProxyFactory类 **/ public T newInstance(SqlSession sqlSession) { MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface); return newInstance(mapperProxy); }

/**

  • 根据mapper代理返回实例
  • @param mapperProxy
  • @return 代理实例 */ protected T newInstance(MapperProxy mapperProxy) { return (T)Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[] {this.mapperInterface}, mapperProxy); } User user = userMapper.getUser("1");

解释:mapper中具体方法的功能,都是代理的invoke完成的。MapperProxy都实现了InvocationHandler接口。

public class MapperProxy implements InvocationHandler, Serializable { //... public Object invoke(Object proxy, Method method, Object[] args) { //... return this.execute(method, args); }

private Object execute(Method method,  Object[] args)
{
    String statementId = this.mapperInterface.getName() + "." + method.getName();
    MappedStatement ms = this.sqlSession.getConfiguration().getMappedStatement(statementId);
    
    Object result = null;
    switch (ms.getSqlCommandType())
    {
        case SELECT:
        {
            Class<?> returnType = method.getReturnType();
            
            // 如果返回的是list,应该调用查询多个结果的方法,否则只要查单条记录
            if (Collection.class.isAssignableFrom(returnType))
            {
                //ID为mapper类全名+方法名
                result = sqlSession.selectList(statementId, args);
            }
            else
            {
                result = sqlSession.selectOne(statementId, args);
            }
            break;
        }
        //...
    }   
    return result;
}

} 我们查询一条记录,所以使用sqlSession.selectOne方法。代码实现如下

/** DefaultSqlSession类 **/ public T selectOne(String statementId, Object parameter) { //这里跟源码保持一致,源码也是调用selectList,返回第一个结果的 List results = this. selectList(statementId, parameter); return CommonUtis.isNotEmpty(results) ? results.get(0) : null; }

public List selectList(String statementId, Object parameter) { MappedStatement mappedStatement = this.configuration.getMappedStatement(statementId); return this.executor. doQuery(mappedStatement, parameter); //调用执行器 } 执行器最终调用了原生的JDBC操作数据库

/** SimpleExecutor类 **/ public List doQuery(MappedStatement ms, Object parameter) { try { //1.获取数据库连接 Connection connection = getConnect();

    //2.获取MappedStatement信息,里面有sql信息
    MappedStatement mappedStatement = conf.getMappedStatement(ms.getSqlId());
    
    //3.实例化StatementHandler对象,
    StatementHandler statementHandler = new SimpleStatementHandler(mappedStatement);
    
    //4.通过StatementHandler和connection获取PreparedStatement
    PreparedStatement preparedStatement = statementHandler.prepare(connection);
    
    //5.实例化ParameterHandler,将SQL语句中?参数化
    ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
    parameterHandler.setParameters(preparedStatement);
    
    //6.执行SQL,得到结果集ResultSet
    ResultSet resultSet = statementHandler.query(preparedStatement);
    
    //7.实例化ResultSetHandler,通过反射将ResultSet中结果设置到目标resultType对象中
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(mappedStatement);
    return resultSetHandler.handleResultSets(resultSet);
}
catch (Exception e)
{
    e.printStackTrace();
}
return null;

} 执行器中我注释的很详细,大家都能明白,这里就不继续粘贴里面的代码了。想看全部的代码,请到github上下载。

现在代码写完了,我们测试以下结果:

整个mybatis精简版框架的写作流程结束,希望您看完可以有所收获,谢谢!

作者:kuailebuzhidao 来源:CSDN 原文:https://blog.csdn.net/kuailebuzhidao/article/details/88355236 版权声明:本文为博主原创文章,转载请附上博文链接!

欢迎大家关注公众号,有资料有技术文章

欢迎大家关注公众号,有资料有技术文章

About

手写迷你mybatis框架,里面使用了mybatis设计模式和框架

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages