写一个简单的mybatis

       此篇记录自定义的一个简易mybatis,也算是架构师入门了吧

什么是mybatis

       mybatis是一个框架。通常我们自己写dao层,都会把数据库的信息以及sql语句直接写到代码中,后期维护起来代价是相当昂贵的,于是mybatis就帮我们简化dao的开发复杂度,将代码和sql语句等进行解耦。我们只需要进行一些配置,就可以很方便的操作数据库,更多精力集中在数据库的语句编写当中,提升查询效率,而不再需要纠结返回结果集的处理、驱动安装等等工作。

mybatis为什么可以解耦

       为了我们阅读方便,我们会用一个或多个xml用来记录sql语句以及返回值类型。我们通常命名为XXXMapper.xml。
       mybatis从一个xml文件中读取相关配置信息,你的数据库url、用户名、密码以及数据库驱动,为了程序读取的时候只需要找一个xml配置文件,我们会把XXXMapper.xml也一起放到这个配置文件中,mybatis会自动将我们的sql语句装载到一个叫Configuration的pojo集合中。我们通常命名为该配置文件为SqlMapConfig.xml。
       有了这两个配置文件,就把sql语句和配置信息从代码当中解耦出来,以后要修改数据库的字段或者说将mysql改为orcale,就不需要重新编译代码,只需要修改两个xml的相关信息即可。
在这里插入图片描述

源码分析阶段

接下来,先贴上使用mybatis的简单案例代码

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;

public class TestMybatis {

    @Test
    public void testFindAll() {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = build.openSession();
        List list = sqlSession.selectList("com.itheima.dao.UserDao.findAll");
        for (Object o : list) {
            System.out.println(o);
        }
        sqlSession.close();
    }
}

       从上面代码可以看到,首先需要一个SqlSessionFactoryBuilder来传入一个InputStream创建SqlSessionFactory。InputStream就是读取的配置文件。使用构建者模式来创建工厂。创建出来之后调用SqlSessionFactory的openSession方法,来加载配置文件,将配置文件信息存放到Configuration对象中,然后返回一个SqlSession。
       Sqlsession的作用就类似于我们自己写的dao层,用来处理各种sql语句。
       由于整个机制必须采用反射来搞定sql语句、返回值、参数等内容,直接把sql的执行语句放到Sqlsession中必定会很使代码的可读性很差,于是又创建了一个Executor工具类,用了执行sql语句,SqlSession的作用就是将sql语句进一步解析,具体的待会儿看代码再详细解释。
在这里插入图片描述
       根据上图,首先我们看看SqlSessionFactoryBuilder.java的代码。这里有三个不同参数的build,当然都是为了获取流文件,然后返回一个工厂对象。

package cn.itclass.factory;

import java.io.InputStream;

public class SqlSessionFactoryBuilder {

	// 如果给的是一个inputStream当然是最好不过
    public SqlSessionFactory build(InputStream inputStream){
        return new SqlSessionFactory(inputStream);
    }

    // 如果直接传过来一个文件地址,就根据地址去获得一个工厂
    public SqlSessionFactory build(String path){
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
        return new SqlSessionFactory(inputStream);
    }

    // 如果用户什么都不给,就到默认地址找默认名称的配置文件,再创建一个流,
    public SqlSessionFactory build(){
        String path = "SqlMapConfig.xml";
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
        return new SqlSessionFactory(inputStream);
    }
}

       接下来是SQLSessionFactory.java获得一个工厂对象之后,工厂对象开始对我们的流文件进行解析,并且返回一个SqlSession对象供我们调用sql执行方法。此方法需要loadConfiguration()来解析SqlMapConfig.xml文件,loadXmlConfiguration()解析XXXMapper.xml文件。使用dom4j来解析xml。

package cn.itclass.factory;

import cn.itclass.dao.SqlSession;
import cn.itclass.dao.impl.SqlSessionImpl;
import cn.itclass.domain.Configuration;
import cn.itclass.domain.Mapper;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

public class SqlSessionFactory {

    private InputStream inputStream;
    private Configuration cfg;
	// 创建工厂必须传入一个inputStream,否则无法进行解析
    public SqlSessionFactory(InputStream inputStream) {
        this.inputStream = inputStream;
    }

	// 要调用sql的执行方法,首先就必须要获取到配置信息,所以创建一个Configuration对象,来存储配置信息
    public SqlSession openSession() {
        cfg = new Configuration();
        // 开始解析基础配置文件
        loadConfiguration();
        SqlSession sqlSession = new SqlSessionImpl(cfg);
        return sqlSession;
    }

    public void loadConfiguration() {
        SAXReader reader = new SAXReader();
        Document doc = null;
        try {
            doc = reader.read(inputStream);
            Element root = doc.getRootElement();
            List<Element> list = root.selectNodes("//property");
            for (Element element : list) {
                String value = element.attributeValue("value");
                String name = element.attributeValue("name");
                if (name.equals("driver")) {
                    cfg.setDriver(value);
                }
                if (name.equals("url")) {
                    cfg.setUrl(value);
                }
                if (name.equals("username")) {
                    cfg.setUsername(value);
                }
                if (name.equals("password")) {
                    cfg.setPassword(value);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

		//	 除了获取基础配置信息,还需要获取所有的sql语句相关信息,并且存到一个list当中,以方便使用
        Element root = doc.getRootElement();
        Element mappers = root.element("mappers");
        List<Element> mapperList = mappers.elements("mapper");
        for (Element element : mapperList) {
            String path = element.attributeValue("resource");
            loadXmlConfiguration(path);
        }
    }

	// 解析XXXMapper.xml文件
    public void loadXmlConfiguration(String path) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(path);
        SAXReader reader = new SAXReader();
        try {
            Document doc = reader.read(inputStream);
            Element root = doc.getRootElement();
            String namespace = root.attributeValue("namespace");
            List<Element> elements = root.elements();
            for (Element element : elements) {
                String resultType = element.attributeValue("resultType");
                String id = element.attributeValue("id");
                String sql = element.getTextTrim();
                String key = namespace + "." + id;
                Mapper value = new Mapper();
                value.setResultType(resultType);
                value.setSql(sql);
                cfg.getXmlMap().put(key, value);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

       由第一张图可以看出,我们的sql和resultType是另外写了一个Mapper类来存储的,Configuration用一个map来存储,这样我们要用哪一个语句就能很方便的找到。Map的key用namespace+"."+id的形式来存,value就用Mapper即可。
       当配置文件读取完毕之后,就会将Configuration传给SqlSessionImpl(SqlSession的实现类)来创建一个SqlSession对象,然后执行sql语句。
       为了关闭时关闭的是同一个链接,这里必须要定义一个构造函数接受Configuration对象。然后调用执行器来执行sql语句。

package cn.itclass.dao.impl;

import cn.itclass.dao.SqlSession;
import cn.itclass.domain.Configuration;
import cn.itclass.domain.Mapper;
import cn.itclass.utils.Executor;
import java.util.List;

public class SqlSessionImpl implements SqlSession {

    private Configuration cfg;
    private Executor executor;

    public SqlSessionImpl(Configuration cfg) {
        this.cfg = cfg;
        executor = new Executor(cfg);
    }

    @Override
    public List selectList(String mapperId) {
        Mapper mapper = cfg.getXmlMap().get(mapperId);
        String sql = mapper.getSql();
        String resultType = mapper.getResultType();
        return executor.executeQuery(sql,resultType);
    }

    @Override
    public void close() {
        executor.close();
    }
}

       为了阅读性,将sql的执行单独拿出来写,创建了一个Executor执行器,因为内容都在Configuration当中,需要采用反射机制来完成,所以会比较复杂。

package cn.itclass.utils;

import cn.itclass.domain.Configuration;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class Executor {
    private Configuration cfg;
    private Connection conn = null;
    private PreparedStatement pst = null;
    private ResultSet rs = null;
    List list = new ArrayList();

    public Executor(Configuration cfg) {
        this.cfg = cfg;
    }

    public List executeQuery(String sql, String resultType) {
        // 1、获取驱动
        try {
            Class.forName(cfg.getDriver());
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            // 2、获取连接
            conn = DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
            // 3、创建statement对象
            pst = conn.prepareStatement(sql);
            // 4、执行sql
            rs = pst.executeQuery();
            // 5、处理结果集
            List<String> columnNames = new ArrayList<>();
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();
            for (int i = 1; i <= columnCount; i++) {
                String columnName = metaData.getColumnName(i);
                columnNames.add(columnName);
            }
            Class clazz = Class.forName(resultType);
            while (rs.next()) {
                Object o = clazz.newInstance();
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    for (String columnName : columnNames) {
                        if (("set" + columnName).equalsIgnoreCase(method.getName())) {
                            Object columnValue = rs.getObject(columnName);
                            method.invoke(o, columnValue);
                        }
                    }
                }
                list.add(o);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

    public void close() {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (pst != null) {
            try {
                pst.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

整体总结执行流程:
在这里插入图片描述

刚刚入门,如有错误请大神指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值