天哪!手动编写mybatis雏形竟然长这样

org.dom4j

dom4j

2.1.3

思路:

1、获取文件流,转成document。

2、获取根节点,也就是mapper。获取根节点的namespace属性值

3、获取select 节点,获取其id,sql,resultType,paramType

4、将select 节点的属性封装到Mapper 实体类中。

5、同理获取update/insert/delete 节点的属性值封装到Mapper 中

6、通过namespace.id 生成key 值将mapper对象保存到Configuration实体中的HashMap 中。

7、返回 Configuration实体

代码如下:

public class XmlMapperBuilder {

private Configuration configuration;

public XmlMapperBuilder(Configuration configuration){

this.configuration=configuration;

}

public Configuration loadXmlMapper(InputStream in) throws DocumentException, ClassNotFoundException {

Document document=new SAXReader().read(in);

Element rootElement=document.getRootElement();

String namespace=rootElement.attributeValue(“namespace”);

List list=rootElement.selectNodes(“//select”);

for (int i = 0; i < list.size(); i++) {

Mapper mapper=new Mapper();

Element element= (Element) list.get(i);

String id=element.attributeValue(“id”);

mapper.setId(id);

String paramType = element.attributeValue(“paramType”);

if(paramType!=null && !paramType.isEmpty()){

mapper.setParmType(Class.forName(paramType));

}

String resultType = element.attributeValue(“resultType”);

if (resultType != null && !resultType.isEmpty()) {

mapper.setResultType(Class.forName(resultType));

}

mapper.setSql(element.getTextTrim());

String key=namespace+“.”+id;

configuration.getMapperMap().put(key,mapper);

}

return configuration;

}

}

上面我只解析了select 标签。大家可以解析对应insert/delete/uupdate 标签,操作都是一样的。

XmlConfigBuilder

我们再来解析一下SqlMapConfig.xml 配置信息思路是一样的,

1、获取文件流,转成document。

2、获取根节点,也就是configuration。

3、获取根节点中所有的property 节点,并获取值,也就是获取数据库连接信息

4、创建一个dataSource 连接池

5、将连接池信息保存到Configuration实体中

6、获取根节点的所有mapper 节点

7、调用XmlMapperBuilder 类解析对应mapper 并封装到Configuration实体中

8、完

代码如下:

public class XmlConfigBuilder {

private Configuration configuration;

public XmlConfigBuilder(Configuration configuration){

this.configuration=configuration;

}

public Configuration loadXmlConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {

Document document=new SAXReader().read(in);

Element rootElement=document.getRootElement();

//获取连接信息

List propertyList=rootElement.selectNodes(“//property”);

Properties properties=new Properties();

for (int i = 0; i < propertyList.size(); i++) {

Element element = (Element) propertyList.get(i);

properties.setProperty(element.attributeValue(“name”),element.attributeValue(“value”));

}

//是用连接池

ComboPooledDataSource dataSource = new ComboPooledDataSource();

dataSource.setDriverClass(properties.getProperty(“driverClass”));

dataSource.setJdbcUrl(properties.getProperty(“jdbcUrl”));

dataSource.setUser(properties.getProperty(“userName”));

dataSource.setPassword(properties.getProperty(“password”));

configuration.setDataSource(dataSource);

//获取mapper 信息

List mapperList=rootElement.selectNodes(“//mapper”);

for (int i = 0; i < mapperList.size(); i++) {

Element element= (Element) mapperList.get(i);

String mapperPath=element.attributeValue(“resource”);

XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);

configuration=xmlMapperBuilder.loadXmlMapper(Resources.getResources(mapperPath));

}

return configuration;

}

}

创建SqlSessionFactory


完成解析后我们创建SqlSessionFactory 用来创建Sqlseesion 的实体,这里为了尽量还原mybatis 设计思路,也也采用的工厂设计模式。

SqlSessionFactory 是一个接口,里面就一个用来创建SqlSessionf的方法。

如下:

public interface SqlSessionFactory {

public SqlSession openSqlSession();

}

单单这个接口是不够的,我们还得写一个接口的实现类,所以我们创建一个DefaultSqlSessionFactory。

如下:

public class DefaultSqlSessionFactory implements SqlSessionFactory {

private Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {

this.configuration = configuration;

}

public SqlSession openSqlSession() {

return new DefaultSqlSeeion(configuration);

}

}

可以看到就是创建一个DefaultSqlSeeion并将包含配置信息的configuration 传递下去。DefaultSqlSeeion 就是SqlSession 的一个实现类。

创建SqlSession


在SqlSession 中我们就要来处理各种操作了,比如selectList,selectOne,insert.update,delete 等等。

我们这里SqlSession 就先写一个selectList 方法。

如下:

public interface SqlSession {

/**

  • 条件查找

  • @param statementid 唯一标识,namespace.selectid

  • @param parm 传参,可以不传也可以一个,也可以多个

  • @param

  • @return

*/

public List selectList(String statementid,Object…parm) throws Exception;

然后我们创建DefaultSqlSeeion 来实现SqlSeesion 。

public class DefaultSqlSeeion implements SqlSession {

private Configuration configuration;

private Executer executer=new SimpleExecuter();

public DefaultSqlSeeion(Configuration configuration) {

this.configuration = configuration;

}

@Override

public List selectList(String statementid, Object… parm) throws Exception {

Mapper mapper=configuration.getMapperMap().get(statementid);

List query = executer.query(configuration, mapper, parm);

return query;

}

}

我们可以看到DefaultSqlSeeion 获取到了configuration,并通过statementid 从configuration 中获取mapper。 然后具体实现交给了Executer 类来实现。我们这里先不管Executer 是怎么实现的,就假装已经实现了。那么整个框架端就完成了。通过调用Sqlsession.selectList() 方法,来获取结果。

在这里插入图片描述

感觉我们都还没有处理,就框架搭建好了?骗鬼呢,确实前面我们从获取文件解析文件,然后创建工厂。都是做好准备工作。下面开始我们JDBC的实现。

SqlSession 具体实现

==============================================================================

我们前面说SqlSeesion 的具体实现有下面5步

1、获取数据库连接

2、获取sql,并对sql 进行解析

3、通过内省,将参数注入到preparedStatement 中

4、执行sql

5、通过反射将结果集封装成对象

但是我们在DefaultSqlSeeion 中将实现交给了Executer来执行。所以我们就要在Executer中来实现这些操作。

我们首先来创建一个Executer 接口,并写一个DefaultSqlSeeion中调用的query 方法。

public interface Executer {

List query(Configuration configuration,Mapper mapper,Object…parm) throws Exception;

}

接着我们写一个SimpleExecuter 类来实现Executer 。

然后SimpleExecuter.query()方法中,我们一步一步的实现。

获取数据库连接


因为数据库连接信息保存在configuration,所以直接获取就好了。

//获取连接

connection=configuration.getDataSource().getConnection();

获取sql,并对sql 进行解析


我们这里想一下,我们在Usermapper.xml写的sql 是什么样子?

select * from user where username=#{username}

#{username} 这样的sql 我们该怎么解析呢?

分两步

1、将sql 找到#{***},并将这部分替换成 ?号

2、对 #{***} 进行解析获取到里面的参数对应的paramType 中的值。

具体实现用到下面几个类。

GenericTokenParser类,可以看到有三个参数,开始标记,就是我们的“#{” ,结束标记就是 “}”, 标记处理器就是处理标记里面的内容也就是username。

public class GenericTokenParser {

private final String openToken; //开始标记

private final String closeToken; //结束标记

private final TokenHandler handler; //标记处理器

public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {

this.openToken = openToken;

this.closeToken = closeToken;

this.handler = handler;

}

/**

  • 解析${}和#{}

  • @param text

  • @return

  • 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。

  • 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现

*/

public String parse(String text) {

//具体实现

}

主要的就是parse() 方法,用来获取操作1 的sql。获取结果例如:

select * from user where username=?

那上面用到TokenHandler 来处理参数。

ParameterMappingTokenHandler实现TokenHandler的类

public class ParameterMappingTokenHandler implements TokenHandler {

private List parameterMappings = new ArrayList();

// context是参数名称 #{id} #{username}

@Override

public String handleToken(String content) {

parameterMappings.add(buildParameterMapping(content));

return “?”;

}

private ParameterMapping buildParameterMapping(String content) {

ParameterMapping parameterMapping = new ParameterMapping(content);

return parameterMapping;

}

public List getParameterMappings() {

return parameterMappings;

}

public void setParameterMappings(List parameterMappings) {

this.parameterMappings = parameterMappings;

}

}

可以看到将参数名称存放 ParameterMapping 的集合中了。

ParameterMapping 类就是一个实体,用来保存参数名称的。

public class ParameterMapping {

private String content;

public ParameterMapping(String content) {

this.content = content;

}

//getter()和setter() 方法。

}

所以我们在我们通过GenericTokenParser类,就可以获取到解析后的sql,以及参数名称。我们将这些信息封装到BoundSql实体类中。

public class BoundSql {

private String sqlText;

private List parameterMappingList=new ArrayList<>();

public BoundSql(String sqlText, List parameterMappingList) {

this.sqlText = sqlText;

this.parameterMappingList = parameterMappingList;

}

getter()和setter() 方法。

}

好了,那么分两步走,先获取,后解析

获取

获取原始sql 很简单,sql 信息就存在mapper 对象中,直接获取就好了。

String sql=mapper.getSql()

解析

1、创建一个ParameterMappingTokenHandler 处理器

2、创建一个GenericTokenParser 类,并初始化开始标记,结束标记,处理器

3、执行genericTokenParser.parse(sql);获取解析后的sql‘’,以及在parameterMappingTokenHandler 中存放了参数名称的集合。

4、将解析后的sql 和参数封装到BoundSql 实体类中。

/**

  • 解析自定义占位符

  • @param sql

  • @return

*/

private BoundSql getBoundSql(String sql){

ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();

GenericTokenParser genericTokenParser = new GenericTokenParser(“#{”,“}”,parameterMappingTokenHandler);

String parse = genericTokenParser.parse(sql);

return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());

}

将参数注入到preparedStatement 中


上面的就完成了sql,的解析,但是我们知道上面得到的sql 还是包含 JDBC的 占位符,所以我们需要将参数注入到preparedStatement 中。

1、通过boundSql.getSqlText()获取带有占位符的sql.

2、接收参数名称集合 parameterMappingList

3、通过mapper.getParmType() 获取到参数的类。

4、通过getDeclaredField(content)方法获取到参数类的Field。

5、通过Field.get() 从参数类中获取对应的值

6、注入到preparedStatement 中

BoundSql boundSql=getBoundSql(mapper.getSql());

String sql=boundSql.getSqlText();

List parameterMappingList = boundSql.getParameterMappingList();

//获取preparedStatement,并传递参数值

PreparedStatement preparedStatement=connection.prepareStatement(sql);

Class<?> parmType = mapper.getParmType();

for (int i = 0; i < parameterMappingList.size(); i++) {

ParameterMapping parameterMapping = parameterMappingList.get(i);

String content = parameterMapping.getContent();

Field declaredField = parmType.getDeclaredField(content);

declaredField.setAccessible(true);

Object o = declaredField.get(parm[0]);

preparedStatement.setObject(i+1,o);

}

System.out.println(sql);

return preparedStatement;

执行sql


其实还是调用JDBC 的executeQuery()方法或者execute()方法

//执行sql

ResultSet resultSet = preparedStatement.executeQuery();

通过反射将结果集封装成对象


在获取到resultSet 后,我们进行封装处理,和参数处理是类似的。

1、创建一个ArrayList

2、获取返回类型的类

3、循环从resultSet中取数据

4、获取属性名和属性值

5、创建属性生成器

6、为属性生成写方法,并将属性值写入到属性中

7、将这条记录添加到list 中

8、返回list

/**

  • 封装结果集

  • @param mapper

  • @param resultSet

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

image

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

@param resultSet

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-Pj107XEM-1710417347916)]
[外链图片转存中…(img-Pef5B9ha-1710417347917)]
[外链图片转存中…(img-3ggDKcY0-1710417347917)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-7dcbzLWp-1710417347918)]

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

[外链图片转存中…(img-ADt13U6L-1710417347918)]

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值