资讯专栏INFORMATION COLUMN

解密Mybatis,手写Mybatis框架(二)

chuyao / 2507人阅读

摘要:三大巨头结果集再通过反射机制映射到对象上面,便做好了数据的映射关于映射具体内容可查阅资料及源码到这我们已经完成了一个简易的框架了通过手写一个简单的框架,我们就可以看得懂源码了,学习框架设计的思路并且增强我们的内功

简化版Mybatis实现思路

1.创建SqlSessionFactory实例.

2.实例化过程中,加载配置文件创建configuration对象.

3.通过factory创建SqlSession对象,把configuaration传入SqlSession.

4.通过SqlSession获取mapper接口动态代理

5.通过代理对调sqlsession中查询方法;

6.sqlsession将查询方法转发给executor;

7.executor基于JDBC访问数据库获取数据;

8.executor通过反射将数据转换成POJO并返回给sqlsession;

9.数据返回给调用者

上节讲到快速入门mybatis的demo三大阶段

// 1.读取mybatis配置文件创SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
//-------------第二阶段-------------
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

//-------------第三阶段-------------
// 4.执行查询语句并返回结果
TUser user = mapper.selectByPrimaryKey(1);
System.out.println(user.toString());
第一阶段:

第一阶段先把配置文件加载到内存,包括数据库信息和mapper.xml。

针对mapper.xml我们定义一个MappedStatement类来存入相应信息.

public class MappedStatement {
    //此处忽略getset方法
    private String namespace;//xml里面的namespace即mapper接口路径
    
    private String sourceId;//mapper接口路径+xml里面的每一个id
    
    private String sql;//sql语句
    
    private String resultType;//返回类型    

}

再定义一个全局配置信息即Configuration存放所有配置信息:

public class Configuration {
    //记录mapper xml文件存放的位置
    public static final String MAPPER_CONFIG_LOCATION = "config";
    //记录数据库连接信息文件存放位置
    public static final String DB_CONFIG_FILE = "db.properties";
    
    private String dbUrl;

    private String dbUserName;

    private String dbPassword;

    private String dbDriver;

    //mapper xml解析完以后select节点的信息存放在mappedStatements,key为MappedStatement里面 
     //的sourceId
    protected final Map mappedStatements = new HashMap();
    
    //为mapper接口生成动态代理的方法
public  T getMapper(Class type, SqlSession sqlSession) {
    return MapperProxyFactory.getMapperProxy(sqlSession, type);
}
}

SqlSessionFactory实例化,并加载configuaration对象信息,这样就把所有的配置信息加载到内存里

public class SqlSessionFactory {
    //配置对象全局唯一 加载数据库信息和mapper文件信息
    private Configuration conf = new Configuration();

    public SqlSessionFactory() {
        //加载数据库信息
         loadDbInfo();
         //加载mapper文件信息
         loadMappersInfo();
    }
    
    private void loadMappersInfo() {
        URL resources =null;
        resources = SqlSessionFactory.class.getClassLoader().getResource(conf.MAPPER_CONFIG_LOCATION);
        File mappers = new File(resources.getFile());
        if(mappers.isDirectory()){
            File[] listFiles = mappers.listFiles();
            for (File file : listFiles) {
                loadMapperInfo(file);
            }
        }
    }

    private void loadMapperInfo(File file) {
        // 创建saxReader对象  
        SAXReader reader = new SAXReader();  
        // 通过read方法读取一个文件 转换成Document对象  
        Document document=null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  
        //获取根节点元素对象  
        Element node = document.getRootElement();
        //获取命名空间
        String namespace = node.attribute("namespace").getData().toString();
        //获取select子节点列表
        List selects = node.elements("select");
        for (Element element : selects) {//遍历select节点,将信息记录到MappedStatement对象,并登记到configuration对象中
            MappedStatement mappedStatement = new MappedStatement();
            String id = element.attribute("id").getData().toString();
            String resultType = element.attribute("resultType").getData().toString();
            String sql = element.getData().toString();
            String sourceId = namespace+"."+id;
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            mappedStatement.setNamespace(namespace);
            conf.getMappedStatements().put(sourceId, mappedStatement);//登记到configuration对象中
        }
    }

    private void loadDbInfo() {
        InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(conf.DB_CONFIG_FILE);
         Properties p = new Properties();
         try {
            p.load(dbIn);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         conf.setDbDriver(p.get("jdbc.driver").toString());
         conf.setDbPassword(p.get("jdbc.password").toString());
         conf.setDbUrl(p.get("jdbc.url").toString());
         conf.setDbUserName(p.get("jdbc.username").toString());
    }
    public SqlSession openSession(){
        SqlSession sqlSession  = new DefaultSqlSession(conf);
        return sqlSession;
    }
}

第二阶段

第二阶段为获取Sqlsession并且从sqlsession获取mapper动态代理.

Sqlsession

mybatis暴露给外部的接口,实现增删改查的能力

1.对外提供数据访问的api

2.对内将请求转发给executor

3.executor基于JDBC访问数据库

public class DefaultSqlSession implements SqlSession {
       
       //配置对象全局唯一 加载数据库信息和mapper文件信息
       private Configuration conf;
       
       //真正提供数据库访问能力的对象
       private Executor executor;
       
   
       public DefaultSqlSession(Configuration conf) {
           super();
           this.conf = conf;
           executor = new SimpleExecutor(conf);
       }
   
       public  T selectOne(String statement, Object parameter) {
           List selectList = this.selectList(statement, parameter);
           if(selectList==null||selectList.size()==0){
               return null;
           }
           if(selectList.size()==1){
               return (T) selectList.get(0);
           }else {
               throw new RuntimeException("Too Many Result!");
           }
   
       }
   
       public  List selectList(String statement, Object parameter) {
           MappedStatement mappedStatement = conf.getMappedStatement(statement);
           try {
               return executor.query(mappedStatement, parameter);
           } catch (SQLException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }
           return null;
       }
   
         @Override
         //获取当前mapper接口的动态代理
         public  T getMapper(Class type) {
           return conf.getMapper(type, this);
         }
   
   }

Executor是Mybatis核心接口定义了数据库操作的基本方法,Sqlsession都是基于它来实现的

 public interface Executor {


  List query(MappedStatement ms, Object parameter) throws SQLException;


  T selectOne(String statement,Object parameter);

}

Executor实现类:

public class SimpleExecutor implements Executor {

        
        private Configuration conf;
    
    
        public SimpleExecutor(Configuration conf) {
            this.conf = conf;
        }

        public  List query(MappedStatement ms, Object parameter)
                throws SQLException {
            //获取mappedStatement对象,里面包含sql语句和目标对象等信息;
            MappedStatement mappedStatement = conf.getMappedStatement(ms.getSourceId());
            //1.获取Connection对象
            Connection conn = getConnect();
            //2.实例化StatementHandler对象,准备实例化Statement
            StatementHandler statementHandler = new DefaultStatementHandler(mappedStatement);
            //3.通过statementHandler和Connection获取PreparedStatement
            PreparedStatement prepare = statementHandler.prepare(conn);
            //4.实例化ParameterHandler对象,对Statement中sql语句的占位符进行处理
            ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
            parameterHandler.setParameters(prepare);
            //5.执行查询语句,获取结果集resultSet
            ResultSet resultSet = statementHandler.query(prepare);
            //6.实例化ResultSetHandler对象,对resultSet中的结果集进行处理,转化成目标对象
            ResultSetHandler resultSetHandler = new DefaultResultSetHandler(mappedStatement);
            return resultSetHandler.handleResultSets(resultSet);
        }
    
        @Override
        public  T selectOne(String statement, Object parameter) {
            MappedStatement mappedStatement =conf.getMappedStatements().get(statement);
            return null;
        }
    
    
        private Connection getConnect() {
            Connection conn =null;
            try {
                Class.forName(conf.getDbDriver());
                conn = DriverManager.getConnection(conf.getDbUrl(), conf.getDbUserName(), conf.getDbPassword());    
            } catch (ClassNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return conn;
        }
    
        public Configuration getConf() {
            return conf;
        }
    
        public void setConf(Configuration conf) {
            this.conf = conf;
        }
        
    }

mapper接口在我们工程里面没有实现类,是通过动态代理来执行方法的.

/**
 * mapper接口生成动态代理的工程类
 * 
 */
public class MapperProxyFactory {

  
  public static  T getMapperProxy(SqlSession sqlSession,Class mapperInterface){
      MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface);
      return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

InvocationHandler实现类:

public class MapperProxy implements InvocationHandler {

    private SqlSession sqlSession;
    
    private final Class mapperInterface;
    

    public MapperProxy(SqlSession sqlSession, Class mapperInterface) {
        super();
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    private  boolean isCollection(Class type) {
        return Collection.class.isAssignableFrom(type);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {// 如果是Object本身的方法不增强
            return method.invoke(this, args);
        }

        Class returnType = method.getReturnType();// 获取方法的返回参数class对象
        Object ret = null;
        if (isCollection(returnType)) {// 根据不同的返回参数类型调用不同的sqlsession不同的方法
            ret = sqlSession.selectList(mapperInterface.getName()+"."+ method.getName(), args);
        } else {
            ret = sqlSession.selectOne(mapperInterface.getName()+"."+ method.getName(), args);
        }

        return ret;
    }

}



第三阶段

第三阶段执行查询并返回结果.刚刚讲过我们执行数据库操作实际上是executor基于jdbc执行的。

jdbc三大巨头,Connection,PreparedStatement,ResultSet,

结果集Result再通过反射机制映射到对象上面,便做好了数据的映射(关于映射具体内容可查阅资料及源码),到这我们已经完成了一个简易的Mybatis框架了.

通过手写一个简单的Mybatis框架,我们就可以看得懂源码了,学习框架设计的思路并且增强我们Java的内功.

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

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

相关文章

  • 解密Mybatis,手写Mybatis框架(一)

    摘要:核心流程三大阶段缕清思路也就是核心流程之后,我们就开始写代码了,详见下节 Mybatis快速入门 步骤: 1.加入mybatis的依赖 2.添加Mybatis的配置文件 3.场景介绍 4.编写实体类丶mapper接口及mapper.xml文件 5.编写测试代码 demo: public class TUser { private Integer id; priva...

    SunZhaopeng 评论0 收藏0
  • Web开发框架推导

    摘要:边界清晰,有利于理解开发测试和部署。前后端分离考虑到目前开发流行前后端分离,为了适应潮流,引入前后端分离的约束。该请求被接受处理,但是该处理是不完整的。 本文欲回答这样一个问题:在 「特定环境 」下,如何规划Web开发框架,使其能满足 「期望 」? 假设我们的「特定环境 」如下: 技术层面 使用Java语言进行开发 通过Maven构建 基于SpringBoot 使用Intelli...

    vpants 评论0 收藏0
  • 分布式软件架构整合(一)

    摘要:通过整合及可以实现数据库查询后将数据持久化。但是可能出现幻像读这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 所需技术:spring、mybatis、druid、flyway、logback、nodejs、html、css3 ;目标:创建一个业务框架,后端采用spring+mybatis,中间层采用node,前端html5,css3等; showImg(https:/...

    mochixuan 评论0 收藏0
  • 开源框架解析,手写MyBatis细节思路

    摘要:基本纲要组成动态配置配置核心源码分析源码解析源码解析源码解析源码解析手写框架是什么本质是一种半自动的框架,前身是其源于和的组合,除了和映射关系之外,还需要编写语句映射三要素映射规则快速入门加入的依赖添加的配置文件场景介绍编写实体类接口以及文 showImg(https://segmentfault.com/img/bVblrnC); Mybatis基本纲要 Mybatis组成 · 动态...

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

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

    antyiwei 评论0 收藏0

发表评论

0条评论

chuyao

|高级讲师

TA的文章

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