git地址:https://github.com/dp33221/my_orm
上次分析完了Mybatis启动以及执行流程后,本次根据上次的思路实现一个简单的mybatis框架。
首先需要分析一下什么场景下出现了ORM框架,最早的时候我们使用数据库链接步骤如下
可以看到每次都需要注册驱动,建立连接,执行查询,获取结果,关闭连接。显然这存在高耦合以及硬编码问题。所以我们一步一步的解决问题。
第一四个问题:注册驱动以及建立数据库连接
这边我们可以封装一个连接池,这样就可以解决耦合问题,然后数据库连接配置的话我们需要使用方提供,这边借鉴Mybatis的方式,使用xml进行配置。所以需要一个configuration对象。
第二三个问题:执行查询
为了能够解耦以及可配置性,这边也是想到的使用配置文件的方式进行配置,参考Mybatis的Mapper.xml文件,这边需要一个mapperStatement对象存储对应的xml信息,分析一下这个对象需要哪些属性,首先肯定需要记录sql,然后要记录入参parameter,然后就是返回结果result,还有执行的类型也就是executorType,以及对应的id这个id默认就是标签中的id。
xml中每个标签都会被解析成一个mappedstatement对象,所以我们需要有一个映射关系来保存,最容易想到的就是Map也就是键值对,键就取xml中的namespace+id,值就是对应的MappedStatement对象。存储在configuration对象中。所以configuration对象要存一个连接池,以及解析的xml的数据。
首先解决前两个问题,第一步需要配置配置文件sqlMapConfig.xml,其中我设置一个根标签<configuration />,以及子标签property来配置数据库连接属性解决硬编码问题。在解析configuration文件时也可以同时解析mapper.xml所以在设置一个mapper标签来设置要解析的mapper路径。
这就是基础的configuration配置,其中property就是数据库连接的基本配置,mapper就是要解析的mapper.xml
mapper.xml文件配置了一些sql信息包括namespace、id、paramType、resultType、sql这些信息。
解析配置文件
在解析配置文件之前,要理清楚一个大体的流程。首先需要创建一个SqlSession对象的工厂用于生成SqlSession,同时解析配置文件,然后生成sqlsession对象,之后在进行excutor操作。
第一步
1.开始解析xml配置文件,我借助dom4j以及jaxen来实现。首先第一步要将xml配置文件读成输入流的形式,借助classloader的getResourcesAsStream方法实现。
2.将字节输入流传递进来开始进行xml文件解析,执行XmlConfigBuild的buildConfiguration方法
3.首先获取字节流,然后获取xml跟标签, 通过xpath表达式得到节点信息,然后存储到properties中,创建连接池,然后设置数据源,存储到configuration对象中。然后获取所有的mapper标签,并遍历解析。调用的xmlMapperBuild的build方法。
在新建xmlmapperbuild对象时,要将已经设置数据源的configuration传入。解析Mapper的步骤与sqlMapConfig差不多,这边主要讲一下解析各个标签的步骤,首先就是获取到对应的id然后返回类型以及参数类型,再获取到sql语句,生成statementid(namespace+id)以及MappedStatement,存入configuration对象中的Map集合中。到这一步就执行完文件解析的步骤了。
第二步
接着就是生成sqlsession对象,sqlsession主要就是sql对话对象,执行sql操作
首先定义一个接口 需要实现哪些方法,然后生成一个默认的实现类来实现这些方法,主要就是一个查询select方法以及一个更新update方法。其中提供了getMapper方法来实现Mapper接口的代理对象创建(jdk动态代理),具体步骤就是通过全限定类名+方法名去获取到MappedStatement对象(注意:因此namespace需要是类的全限定路径,id是类的方法,同时这也导致了方法无法重载)。然后通过返回值类型以及操作类型判断是走查询还是走更新操作,如果走更新操作则会记录方法的参数数组。sqlsession是一个对外的会话对象,其中执行sql语句还是通过Executor对象来执行的。
第三步
执行sql操作
和sqlsession一样先创建一个接口定义规则,然后创建一个defaultExecutor进行默认实现。其中有一个查询功能一个更新功能,我这边注释写的比较全面,就不赘述了。其中有在处理请求参数时我借用了mybatis中的处理类。并进行了简化
参数处理:下面是代码
GenericTokenParser
```
public class GenericTokenParser {
private final StringopenToken;
private final StringcloseToken;
private final TokenHandlerhandler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text ==null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset =0;
final StringBuilder builder =new StringBuilder();
StringBuilder expression =null;
while (start > -1) {
if (start >0 && src[start -1] =='\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset -1).append(openToken);
offset = start +openToken.length();
}else {
// found open token. let's search close token.
if (expression ==null) {
expression =new StringBuilder();
}else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start +openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end -1] =='\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset -1).append(closeToken);
offset = end +closeToken.length();
end = text.indexOf(closeToken, offset);
}else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
}else {
builder.append(handler.handleToken(expression.toString()));
offset = end +closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
```
ParameterMapping
```
public class ParameterMapping {
private Stringname;
}
```
ParameterMappingTokenHandler
```
public class ParameterMappingTokenHandlerimplements TokenHandler {
private ListparameterMappings =new ArrayList<>();
public List getParameterMappings() {
return parameterMappings;
}
@Override
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
return new ParameterMapping(content);
}
}
```
TokenHandler
public interface TokenHandler {
String handleToken(String content);
}
如图就是自定义ORM框架的查询结果,如果要查看增删改方法则调用update/delete/insert方法