Mybatis-2-Mybatis运行分析,自定义实现一个Mybatis

这篇博客详细分析了Mybatis的运行机制,并指导如何自定义实现一个简易Mybatis。从项目环境搭建到核心组件的创建,包括SqlSessionFactory工厂(构建者模式)、SqlSession对象(工厂模式)和mapper接口代理对象(代理模式)。博主逐步讲解了配置文件的获取、接口与映射文件的编写,以及各个关键类的作用,如Resources、SqlSessionFactoryBuilder、Configuration、Mapper等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、在《Mybatis-1-项目环境搭建(Maven)》案例分析

先看一下代码:

// 1.导入mybatis配置文件,抛出异常
//      使用org.apache.ibatis.io.Resources导入mybatis的xml配置文件
InputStream resource = Resources.getResourceAsStream("mybatis.config.xml");
// 2.创建SqlSessionFactory工厂
//      说明:org.apache.ibatis.session.SqlSessionFactory是一个接口
//      使用org.apache.ibatis.session.SqlSessionFactoryBuilder来创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(resource);
// 3.使用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSesison对象创建mapper接口的代理对象
GuestMapper guestMapper = sqlSession.getMapper(GuestMapper.class);
// 5.通过代理对象来调用selectGuest函数,来访问数据库
List<Guest> guests = guestMapper.selectGuest();
// 6.打印一下结果
for (Guest guest : guests) {
    System.out.println(guest);
}
// 7.关闭资源
sqlSession.close();
resource.close();

执行过程:

  1. 获取mybatis的配置文件
    通过类加载器来加载。
  2. 创建SqlSessionFactory工厂(构建者模式)
    Mybatis使用构建者模式,来创建SqlSessionFactory工厂。
    构建者模式:可以把创建的细节过程隐藏,我们只需要给构建者(SqlSessionFactoryBuilder)一个需求(resource),就可以得到需要的对象(SqlSessionFactory)。
    优势:使用者可以不关注创建的细节过程。
  3. 通过SqlSessionFactory工厂创建SqlSession对象(工厂模式)
    优势:解耦(降低类之间的关系)
  4. 通过SqlSession对象创建mapper接口代理对象(代理模式)
    优势:不修改源码的基础上对已有方法进行增强。
  5. 通过mapper接口代理对象调用函数
  6. 打印结果
  7. 关闭资源

二、自定义实现一个Mybatis

让我们自己写一个简单的Mybatis,实现查询。

首先看一下最后的项目结构:
在这里插入图片描述
下面就要开始写代码了:

准备:
  1. mybatis.config.xml配置文件
    <?xml version="1.0" encoding="UTF-8" ?>
    <!-- 配置mybatis -->
    <configuration>
        <!-- 指定 MyBatis 数据库配置文件 -->
        <properties resource="db.properties"/>
        <!-- 环境配置 -->
        <!-- 默认使用mysql这个环境 -->
        <environments default="mysql">
            <!-- mysql 数据库环境 -->
            <environment id="mysql">
                <!-- 事务管理:JDBC -->
                <transactionManager type="JDBC"/>
                <!-- 数据源配置,POOLED是JDBC连接对象的数据源连接池的实现 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://192.168.169.139:3306/seven?characterEncoding=UTF-8"/>
                    <property name="username" value="root"/>
                    <property name="password" value="tiger"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- mappers告诉了MyBatis去哪里找持久化类的映射文件 注解方式不需要这个 -->
        <mappers>
            <!-- 使用xml方式开发,指定持久化类的映射文件 -->
            <mapper resource="com/seven/mapper/GuestMapper.xml"/>
            <!-- 使用注解方式开发,指定持久化类接口 -->
    <!--        <mapper class="com.seven.mapper.GuestMapper"/>-->
        </mappers>
    
    </configuration>
    
  2. 实体类 Guest
    package com.seven.entity;
    
    import java.util.Date;
    
    public class Guest {
        private Integer gid;
        private String gname;
        private String gpwd;
        private Date gbirthday;
    
        @Override
        public String toString() {
            return "Guest{" +
                    "gid=" + gid +
                    ", gname='" + gname + '\'' +
                    ", gpwd='" + gpwd + '\'' +
                    ", gbirthday=" + gbirthday +
                    '}';
        }
    
        public Integer getGid() {
            return gid;
        }
    
        public void setGid(Integer gid) {
            this.gid = gid;
        }
    
        public String getGname() {
            return gname;
        }
    
        public void setGname(String gname) {
            this.gname = gname;
        }
    
        public String getGpwd() {
            return gpwd;
        }
    
        public void setGpwd(String gpwd) {
            this.gpwd = gpwd;
        }
    
        public Date getGbirthday() {
            return gbirthday;
        }
    
        public void setGbirthday(Date gbirthday) {
            this.gbirthday = gbirthday;
        }
    }
    
    
  3. 映射接口 GuestMapper
    package com.seven.mapper;
    
    import com.seven.entity.Guest;
    
    import java.util.List;
    
    public interface GuestMapper {
        List<Guest> selectGuest();
    }
    
  4. GuestMapper对应的映射文件 GuestMapper.xml
    <?xml version="1.0" encoding="UTF-8" ?>
    <mapper namespace="com.seven.mapper.GuestMapper">
    
        <select id="selectGuest" resultType="com.seven.entity.Guest">
            select * from t_guest
        </select>
    
    </mapper>
    
编写mybatis:
先来看一下步骤:
1. 获取mybatis的配置文件
2. 创建SqlSessionFactory工厂(构建者模式)
3. 通过SqlSessionFactory工厂创建SqlSession对象(工厂模式)
4. 通过SqlSession对象创建mapper接口代理对象(代理模式)
5. 通过mapper接口代理对象调用函数
6. 打印结果
7. 关闭资源

  1. 获取mybatis的配置文件
    需要的类:
    Resources:获取xml文件的字节对象流

    package com.seven.mybatis.io;
    
    import java.io.InputStream;
    
    /**
     * 获取xml文件的字节对象流
     */
    public class Resources {
    
        /**
         * 返回文件的字节对象流
         * @param filePath
         * @return
         */
        public static InputStream getResourceAsStream(String filePath) {
            return Resources.class.getClassLoader().getResourceAsStream(filePath);
        }
    }
    

    SqlSessionFactory接口:
    MySqlSessionFactory类
    XMLConfigParse类:
    Configuration类:
    Mapper类:
    SqlSessionFactoryBuilder类:

    SqlSessionFactory接口:创建MySqlSession对象。

    package com.seven.mybatis.sqsession;
    
    public interface SqlSessionFactory {
        SqlSession openSession();
    }
    

    MySqlSessionFactory类(SqlSessionFactory接口实现类):创建MySqlSession对象,并以SqlSession返回。

    package com.seven.mybatis.sqsession.impl;
    
    import com.seven.mybatis.cfg.Configuration;
    import com.seven.mybatis.sqsession.SqlSession;
    import com.seven.mybatis.sqsession.SqlSessionFactory;
    
    /**
     * 创建MySqlSession对象
     */
    public class MySqlSessionFactory implements SqlSessionFactory {
    
        private Configuration cfg;
    
        /**
         * 构造函数
         * 输入:配置对象
         * @param cfg
         */
        public MySqlSessionFactory(Configuration cfg) {
            this.cfg = cfg;
        }
    
        /**
         * 获取SqlSession
         * @return
         */
        @Override
        public SqlSession openSession() {
            return new MySqlSession(cfg);
        }
    }
    

    XMLConfigParse类:解析xml。

    package com.seven.mybatis.util;
    
    import com.seven.mybatis.cfg.Configuration;
    import com.seven.mybatis.cfg.Mapper;
    import com.seven.mybatis.io.Resources;
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class XMLConfigParse {
    
        public static Configuration loadConfiguration(InputStream inputStream) {
            try {
                //创建Configuration对象
                Configuration cfg = new Configuration();
                //创建SAXReader对象
                SAXReader reader = new SAXReader();
                //通过inputStream获取document对象
                Document document = reader.read(inputStream);
                //通过document获取root节点
                Element rootElement = document.getRootElement();
                //1.解析<property name="" value=""/>节点,获取数据库的四个配置参数
                //  1.1获取所有的property节点
                List<Element> propertyElements = rootElement.selectNodes("//property");
                //  1.2循环遍历property节点
                for (Element propertyElement : propertyElements) {
                    //1.2.1获取每个property节点的name属性值和value属性值
                    String name = propertyElement.attributeValue("name");
                    String value = propertyElement.attributeValue("value");
                    //1.2.2对号入座,把数据库配置参数保存到Configuration对象中
                    if ("driver".equals(name)) {
                        cfg.setDriver(value);
                    } else if ("url".equals(name)) {
                        cfg.setUrl(value);
                    } else if ("username".equals(name)) {
                        cfg.setUsername(value);
                    } else if ("password".equals(name)) {
                        cfg.setPassword(value);
                    }
                }
                //2.解析<mapper resource=""/>或<mapper class=''/>节点,获取映射文件的路径或映射接口全名称
                //  2.1获取所有的mapper节点
                List<Element> mapperElements = rootElement.selectNodes("//mappers/mapper");
                //  2.2循环遍历mapper节点
                for (Element mapperElement : mapperElements) {
                    //2.2.1获取resource属性
                    String resource = mapperElement.attributeValue("resource");
                    //2.2.2判断resource是否存在,存在为xml方式,不存在为注解方式
                    if (resource != null) {
                        //为xml方式,则要解析xml映射文件
                        //调用loadMapperConfiguration函数,解析映射文件,返回结果
                        Map<String, Mapper> mappers = loadMapperConfiguration(resource);
                        //将结果添加进cfg的mappers中
                        //注意:每一次循环会解析一个配置文件,每次都会返回一个mappers
                        //注意:若在Configuration的setMappers中为this.mappers=mappers
                        //注意:前一个文件的mappers会被替换掉,所以应该使用this.mappers.putAll(mappers)
                        cfg.setMappers(mappers);
                    }
                }
                return cfg;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) throws Exception {
            try {
                //通过mapperPath获取inputStream
                InputStream inputStream = Resources.getResourceAsStream(mapperPath);
                //创建Map<String, Mapper>对象
                Map<String, Mapper> mappers = new HashMap<>();
                //创建SAXReader对象
                SAXReader reader = new SAXReader();
                //通过inputStream获取document对象
                Document document = reader.read(inputStream);
                //通过document获取root节点
                Element rootElement = document.getRootElement();
                //1.解析<mapper namespace=""/>节点(根节点),获取namespace属性值
                String namespace = rootElement.attributeValue("namespace");
                //2.解析<select id="" resultType="">sql语句</select>节点
                //  2.1获取所有select节点
                List<Element> selectElements = rootElement.selectNodes("//select");
                //  2.2循环遍历select节点
                for (Element selectElement : selectElements) {
                    //2.2.1获取id属性,即映射类对应的方法名
                    String id = selectElement.attributeValue("id");
                    //2.2.2获取resultType属性,即结果集类名全称
                    String resultType = selectElement.attributeValue("resultType");
                    //2.2.3获取节点内容,即sql语句
                    String sqlString = selectElement.getText();
                    //2.2.4
                    //  说明:mappers的key为namespace.id,即映射接口的某个方法
                    //  说明:mappers的value为Mapper类对象,用来存放sql语句和结果集类名全路径
                    //  构造key
                    String key = namespace + "." + id;
                    //  构造value
                    Mapper mapper = new Mapper();
                    mapper.setResultType(resultType);
                    mapper.setSqlString(sqlString);
                    //  将结果put到mappers对象中
                    mappers.put(key, mapper);
                }
                //返回结果
                return mappers;
            } catch (Exception e) {
                throw new Exception("解析映射文件错误");
            }
        }
    }
    

    Configuration类:xml解析后的配置信息。

    package com.seven.mybatis.cfg;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class Configuration {
        private String driver;
        private String url;
        private String username;
        private String password;
    
        private Map<String, Mapper> mappers = new HashMap<>();
    
        public String getDriver() {
            return driver;
        }
    
        public void setDriver(String driver) {
            this.driver = driver;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        public Map<String, Mapper> getMappers() {
            return mappers;
        }
    
        public void setMappers(Map<String, Mapper> mappers) {
            this.mappers.putAll(mappers);
        }
    }
    

    Mapper类:xml映射文件解析后的配置信息。

    package com.seven.mybatis.cfg;
    
    public class Mapper {
        private String resultType;
        private String sqlString;
    
        public String getResultType() {
            return resultType;
        }
    
        public void setResultType(String resultType) {
            this.resultType = resultType;
        }
    
        public String getSqlString() {
            return sqlString;
        }
    
        public void setSqlString(String sqlString) {
            this.sqlString = sqlString;
        }
    }
    

    SqlSessionFactoryBuilder类:解析xml配置文件和映射文件,创建SqlSessionFactory工厂(返回MySqlSessionFactory对象)。

    package com.seven.mybatis.sqsession;
    
    import com.seven.mybatis.cfg.Configuration;
    import com.seven.mybatis.sqsession.impl.MySqlSessionFactory;
    import com.seven.mybatis.util.XMLConfigParse;
    
    import java.io.InputStream;
    
    public class SqlSessionFactoryBuilder {
    
        /**
         * 解析xml配置文件和映射文件
         * 返回:MySqlSessionFactory对象,传入Configuration对象
         * @param resource
         * @return
         */
        public SqlSessionFactory build(InputStream resource) {
            //解析xml配置文件和映射文件
            Configuration cfg = XMLConfigParse.loadConfiguration(resource);
            //返回MySqlSessionFactory对象,传入Configuration对象
            return new MySqlSessionFactory(cfg);
        }
    }
    

    SqlSession接口:获取数据库连接对象,返回代理对象,关闭数据库连接。

    package com.seven.mybatis.sqsession;
    
    import com.seven.mybatis.cfg.Configuration;
    
    public interface SqlSession {
        <T> T getMapper(Class<T> mapperClass);
    
        void close();
    }
    

    MySqlSession类(SqlSession接口实现类):获取数据库连接对象,返回代理对象,关闭数据库连接。

    package com.seven.mybatis.sqsession.impl;
    
    import com.seven.mybatis.proxy.MapperProxy;
    import com.seven.mybatis.cfg.Configuration;
    import com.seven.mybatis.sqsession.SqlSession;
    import com.seven.mybatis.util.DataSourceUtil;
    
    import java.lang.reflect.Proxy;
    import java.sql.Connection;
    import java.sql.SQLException;
    
    /**
     * 获取数据库连接对象
     * 返回代理对象
     * 关闭数据库连接
     */
    public class MySqlSession implements SqlSession {
    
        private Configuration cfg;
        private Connection conn;
    
        /**
         * 构造函数
         * 输入:配置对象
         * 获取:数据库连接对象
         * @param cfg
         */
        public MySqlSession(Configuration cfg) {
            this.cfg = cfg;
            this.conn = DataSourceUtil.getConnection(cfg);
        }
    
        /**
         * 返回代理对象
         * @param mapperClass
         * @param <T>
         * @return
         */
        @Override
        public <T> T getMapper(Class<T> mapperClass) {
            return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(),
                    new Class[]{mapperClass}, new MapperProxy(cfg.getMappers(), conn));
        }
    
        /**
         * 关闭数据库连接
         */
        @Override
        public void close() {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    DataSourceUtil类:连接数据库。

    package com.seven.mybatis.util;
    
    import com.seven.mybatis.cfg.Configuration;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    
    public class DataSourceUtil {
    
        public static Connection getConnection(Configuration cfg) {
            try {
                Class.forName(cfg.getDriver());
                return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    MapperProxy类:映射类的代理类。

    package com.seven.mybatis.proxy;
    
    import com.seven.mybatis.cfg.Mapper;
    import com.seven.mybatis.util.Executor;
    import com.sun.deploy.net.proxy.ProxyHandler;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.util.Map;
    
    /**
     * Mapper接口的代理类
     */
    public class MapperProxy implements InvocationHandler {
    
        private Map<String, Mapper> mappers;
        private Connection conn;
    
        /**
         * 构造函数
         * 输入:Mapper对象(结果集类型全限定类名,sql语句)
         * 输入:数据库连接对象
         * @param mappers
         * @param conn
         */
        public MapperProxy(Map<String, Mapper> mappers, Connection conn) {
            this.mappers = mappers;
            this.conn = conn;
        }
    
        /**
         * 对被代理对象的方法增强
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //获取声明该方法的类的全限定类名
            String declaringClass = method.getDeclaringClass().getName();
            //获取该方法名
            String methodName = method.getName();
            //拼接key(declaringClass.methodName),对应mappers的key
            String key = declaringClass + "." + methodName;
            //从mappers中查找key,返回对应的mapper(结果集类型全限定类名,sql语句)
            Mapper mapper = mappers.get(key);
            //判断mapper是否存在
            //  若存在:调用Executor的selectList(mappers, conn)函数(执行sql语句,将结果集进行封装)
            //  否则:抛出异常
            if (mapper != null) {
                return new Executor().selectList(mapper, conn);
            } else {
                throw new IllegalArgumentException("执行的方法在映射文件中未定义");
            }
        }
    }
    

    Executor类:JDBC操作。

    package com.seven.mybatis.util;
    
    import com.seven.mybatis.cfg.Mapper;
    
    import java.beans.PropertyDescriptor;
    import java.lang.reflect.Method;
    import java.sql.*;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.PropertyPermission;
    
    /**
     * 进行数据库(JDBC)操作
     * 执行sql语句
     * 对结果集进行封装
     */
    public class Executor {
        public <T> List<T> selectList(Mapper mapper, Connection conn) {
            PreparedStatement pre = null;
            ResultSet res = null;
            try {
                //创建函数返回对象
                List<T> entityList = new ArrayList<>();
                //取出mapper中的sql数据
                String sqlString = mapper.getSqlString();
                //取出mapper中的结果集全限定类名,获取该类的class对象,用于后面的反射创建实体类
                String resultType = mapper.getResultType();
                Class<?> entityClass = Class.forName(resultType);
                //获取PreparedStatement对象
                pre = conn.prepareStatement(mapper.getSqlString());
                //执行sql语句,获取结果集
                res = pre.executeQuery();
                //封装
                while (res.next()) {
                    //通过反射实例化实体类对象
                    T entity = (T) entityClass.newInstance();
                    //获取结果集的元信息
                    ResultSetMetaData metaData = res.getMetaData();
                    //获取元信息中的列数
                    int columnCount = metaData.getColumnCount();
                    //循环遍历赋值给实体类对象
                    //注意:列序号从1开始
                    for (int i = 1; i <= columnCount; i++) {
                        //在元信息中获取第i列的列名
                        String columnName = metaData.getColumnName(i);
                        //在结果集中通过列名获取该列的值
                        Object columnValue = res.getObject(columnName);
                        //给entity赋值,使用java内省机制(借助PropertyDescriptor实现属性的封装)
                        PropertyDescriptor pd = new PropertyDescriptor(columnName, entityClass);
                        //获取pd的写入方法
                        Method writeMethod = pd.getWriteMethod();
                        //把获取的列的值给对象赋值
                        writeMethod.invoke(entity, columnValue);
                    }
                    //把实体类添加进函数返回对象中
                    entityList.add(entity);
                }
                return entityList;
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                release(res, pre);
            }
        }
    
        private void release(ResultSet res, PreparedStatement pre) {
            if (res != null) {
                try {
                    res.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (pre != null) {
                try {
                    pre.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    测试类:

    import com.seven.entity.Guest;
    import com.seven.mapper.GuestMapper;
    import com.seven.mybatis.io.Resources;
    import com.seven.mybatis.sqsession.SqlSession;
    import com.seven.mybatis.sqsession.SqlSessionFactory;
    import com.seven.mybatis.sqsession.SqlSessionFactoryBuilder;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.List;
    
    public class GuestTest {
    
        public static void main(String[] args) throws IOException {
            //1.获取Mybatis配置文件的inputStream-->resource
            InputStream resource = Resources.getResourceAsStream("mybatis.config.xml");
            //2.通过resource用SqlSessionFactoryBuilder创建SqlSessionFactory
            //构建者模式
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(resource);
            //3.通过SqlSessionFactory获取SqlSession对象
            SqlSession sqlSession = factory.openSession();
            //4.通过sqlSession获取代理对象
            GuestMapper mapper = sqlSession.getMapper(GuestMapper.class);
            //5.调用selectGuest方法
            List<Guest> guests = mapper.selectGuest();
            //6.遍历打印guests
            for (Guest guest : guests) {
                System.out.println(guest);
            }
            //7.关闭连接
            sqlSession.close();
        }
    }
    

    运行结果:
    在这里插入图片描述


    更新时间:2020-1-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值