自己动手实现一个ORM框架
点击关注公众号,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配置文件中的namespace
和id
,我们将其拼接起来作为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");
// 得到所有
图片
表情
视频