自己动手实现一个ORM框架

Java技术迷

共 10252字,需浏览 21分钟

 · 2022-05-27

点击关注公众号,Java干货及时送达

作者 | 汪伟俊 

出品 | Java技术迷(ID:JavaFans1024)

引言

本篇文章我们来自己动手实现一个ORM框架,我们先来看一下传统的JDBC代码:

static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";  
static final String JDBC_URL = "jdbc:mysql:///user";
static final String USER_NAME = "root";
static final String PASS_WORD = "123456";

public static void main(String[] args) {
    Class.forName(JDBC_DRIVER);
    Connection conn = DriverManager.getConnection(JDBC_URL, USER_NAME, PASS_WORD);
    Statement stmt = conn.createStatement();
    String sql = "SELECT * FROM user";
    ResultSet rs = stmt.executeQuery(sql);
    while(rs.next()){
        int id  = rs.getInt("id");
        int age = rs.getInt("age");
        System.out.println("ID: " + id);
        System.out.println("Age: " + age);
    }
    rs.close();
}

以上代码通过JDBC实现了对数据表的查询操作,不过这里有一些明显的问题,对于数据库的配置信息是硬编码在代码中的,想要修改配置信息还得来修改代码,我们可以将其抽取成一个配置文件;对于sql的编写也是硬编码在代码中,也可以考虑将其抽取出去;然后是对结果集的封装,每次都需要通过循环解析结果集也非常麻烦。综上所述,我们借鉴MyBatis来实现一个自己的ORM框架。

ORM框架整体架构

我们先来梳理一下框架的整体架构,首先我们需要解析一下配置文件,正如MyBatis框架那样,我们需要使用到两种配置文件,一个是框架的全局配置文件,一个是Mapper配置文件,定义格式如下:

<configuration>
configuration>
<mapper>
mapper>

那么首先框架的第一步就是读取配置文件,全局配置文件中应该包含数据源配置信息和Mapper配置文件所在位置,如下所示:

<configuration>
    
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///user"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    dataSource>

    
    <mapper resource="UserMapper.xml"/>
configuration>

对该配置文件进行解析后,我们可以将这些数据封装成一个Java实体,该实体包含了所有的配置信息,由于全局配置文件中可能含有多个Mapper文件的配置,所以将其封装成一个Map集合:

Map

集合的key为String类型,value为MapperStatement类型,MapperStatement是对Mapper配置文件的一个封装:

<mapper namespace="user">
    <select id="selectList" resultType="com.wwj.pojo.User">
        select * from e_user
    select>
mapper>

这里需要注意一点,框架会将整个项目中的Mapper配置文件都封装成一个MapperStatement并保存到Map中,这就需要对每个MapperStatement进行区分,区分的关键就是Mapper配置文件中的namespaceid,我们将其拼接起来作为statementId。到这里,配置文件的解析就完成了,然后我们提供对应的查询方法,该查询方法的作用是对sql语句进行解析并调用JDBC查询数据库,通过内省封装结果集。以上是框架的一个整体思路,大家可能现在还没有理解到,没关系,接下来是对实现过程的一个详细概述。

解析配置文件

新建一个类Resources,该类负责将一个文件转换成输入流:

public class Resources {

    /**
     * 根据配置文件的路径将配置文件加载成字节输入流
     *
     * @param path
     * @return
     */

    public static InputStream getResourceAsStream(String path) {
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

接下来我们需要一个SqlSessionFactoryBuilder对象,该对象会提供一个build方法来生成SqlSessionFactory:

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
        // 使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XmlConfigBuilder builder = new XmlConfigBuilder();
        Configuration configuration = builder.parseConfig(inputStream);
        // 创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return sqlSessionFactory;
    }
}

SqlSessionFactory是一个接口,我们创建它的默认实现类DefaultSqlSessionFactory,该类需要传入一个Configuration类型对象,这个Configuration就是对全局配置文件的一个封装:

public class Configuration {

    private DataSource dataSource;
    /**
     *  key:statementId
     *  value:封装好的MapperStatement对象
     */

    private Map mappedStatementMap = new HashMap<>();
}

那么现在的关键就是对全局配置文件的解析了,我们提供一个类XmlConfigBuilder,该类的parseConfig方法可以将输入流转换为Configuration对象,实现如下:

public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        // 
        Element rootElement = document.getRootElement();
        // 全局查找标签
        List propertyList = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        propertyList.forEach(element -> {
            // 获取到标签中的name和value属性
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        });
        // 创建数据源
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        configuration.setDataSource(comboPooledDataSource);

        // 解析mapper.xml文件
        List mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream mapperAsStream = Resources.getResourceAsStream(mapperPath);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(mapperAsStream);
        }
        return configuration;
    }

借助dom4j可以很容易地实现解析,将每个标签中的属性和属性值读取出来,进行对应的封装即可,对于Mapper配置文件的解析也是如此,通过resource属性可以得到Mapper文件位置,然后将其转为输入流并解析:

public class XmlMapperBuilder {

    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        // 得到根标签
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        // 得到所有
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报