}
这里我们为什么不添加namespace 的值呢?
聪明的你肯定发现了,因为mapper里面这些属性表明每个sql 都对应一个mapper,而namespace 是一个命名空间,算是sql 的上一层,所以在mapper中暂时使用不到,就没有添加了。
Configuration
Configuration 实体用来保存SqlMapConfig 中的信息。所以需要保存数据库连接,我们这里直接用JDK提供的 DataSource。还有一个就是mapper 的信息。每个mapper 有自己的标识,所以这里采用hashMap来存储。如下:
public class Configuration {
private DataSource dataSource;
HashMap <String,Mapper> mapperMap=new HashMap<>();
//getter()和setter方法
}
XmlMapperBuilder
做好了上面的准备工作,我们先来解析mapper 吧。我们创建一个XmlMapperBuilder 类来解析。通过dom4j 的工具类来解析XML 文件。我这里用的dom4j 依赖为:
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 用来创建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 中我们就要来处理各种操作了,比如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的实现。
==============================================================================
我们前面说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();
我们这里想一下,我们在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());
}
上面的就完成了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;
其实还是调用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
-
@param
-
@return
-
@throws Exception
*/
private List resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
ArrayList list=new ArrayList<>();
//封装结果集
Class<?> resultType = mapper.getResultType();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
Object o = resultType.newInstance();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
//属性名
String columnName = metaData.getColumnName(i);
//属性值
Object value = resultSet.getObject(columnName);
//创建属性描述器,为属性生成读写方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultType);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
list.add((E) o);
}
return list;
}
=========================================================================================
我们现在来创建一个SqlSessionFactoryBuilder 类,来为使用端提供一个人口。
public class SqlSessionFactoryBuilder {
private Configuration configuration;
public SqlSessionFactoryBuilder(){
configuration=new Configuration();
}
public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
configuration=xmlConfigBuilder.loadXmlConfig(in);
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
可以看到就一个build 方法,通过SqlMapConfig的文件流将信息解析到configuration,创建并返回一个sqlSessionFactory 。
到此,整个框架端已经搭建完成了,但是我们可以看到,只实现了select 的操作,update、inster、delete 的操作我们在我后面提供的源码中会有实现,这里只是将整体的设计思路和流程。
=================================================================
终于到了测试的环节啦。我们前面写了自定义的持久层,我们现在来测试一下能不能正常的使用吧。
见证奇迹的时刻到啦
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
lassNotFoundException {
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
configuration=xmlConfigBuilder.loadXmlConfig(in);
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
可以看到就一个build 方法,通过SqlMapConfig的文件流将信息解析到configuration,创建并返回一个sqlSessionFactory 。
到此,整个框架端已经搭建完成了,但是我们可以看到,只实现了select 的操作,update、inster、delete 的操作我们在我后面提供的源码中会有实现,这里只是将整体的设计思路和流程。
=================================================================
终于到了测试的环节啦。我们前面写了自定义的持久层,我们现在来测试一下能不能正常的使用吧。
见证奇迹的时刻到啦
最后
作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料
[外链图片转存中…(img-SkEoAak4-1720100084528)]
[外链图片转存中…(img-klZpIjIf-1720100084529)]