当前位置: 首页 > news >正文

仿照MyBatis手写一个持久层框架学习

首先数据准备,创建MySQL数据库mybatis,创建表并插入数据。

DROP TABLE IF EXISTS user_t;
CREATE TABLE user_t ( id INT PRIMARY KEY, username VARCHAR ( 128 ) );
INSERT INTO user_t VALUES(1,'Tom');
INSERT INTO user_t VALUES(2,'Jerry');

JDBC API允许应用程序访问任何形式的表格数据,特别是存储在关系数据库中的数据。

在这里插入图片描述

JDBC代码示例:

    @Test@DisplayName("JDBC模式访问数据库示例")public void jdbc_test() {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;try {// 加载数据库驱动Class.forName("com.mysql.cj.jdbc.Driver");// 通过驱动管理类来获取数据库连接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "123456");// 定义SQL语句 ?表示占位符String sql = "select * from user_t where username = ?";// 获取预处理StatementpreparedStatement = connection.prepareStatement(sql);// 设置参数,第一个参数为SQL语句中参数的序号(从1开始),第二个参数为设置的参数值preparedStatement.setString(1, "Tom");// 向数据库发出SQL执行查询,查询出结果集resultSet = preparedStatement.executeQuery();// 遍历查询结果集while (resultSet.next()) {int id = resultSet.getInt("id");String username = resultSet.getString("username");// 封装UserUser user = new User();user.setId(id);user.setUsername(username);System.out.println(user);}} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (SQLException e) {throw new RuntimeException(e);} finally {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (connection != null) {try {connection.close();} catch (SQLException e) {throw new RuntimeException(e);}}}}

我们发现使用过程中存在以下问题:

  • 数据库配置信息硬编码
  • 频繁创建释放数据库连接,而数据库连接是宝贵的资源
  • SQL语句、参数、返回结果集获取 均存在硬编码问题
  • 需要手动封装返回结果集,较为繁琐

手写持久层框架思路分析

在这里插入图片描述

创建一个maven项目mybatis-demo,作为使用端,引入自定义持久层框架jar包

在这里插入图片描述

其中文件内容如下:

package com.mybatis.it.dao;import com.mybatis.it.pojo.User;import java.util.List;public class UserDaoImpl implements IUserDao {@Overridepublic List<User> findAll() {return null;}@Overridepublic User findByCondition(User user) {return null;}
}
package com.mybatis.it.pojo;public class User {private int id;private String username;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +'}';}
}

创建SqlMapConfig.xml配置文件:存放数据库配置信息、存放mapper.xml路径

<?xml version="1.0" encoding="utf-8" ?>
<configuration><!-- 1. 配置数据库信息--><dataSource><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property><property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&amp;serverTimezone=UTC"></property><property name="username" value="root"></property><property name="password" value="123456"></property></dataSource><!-- 2. 引入映射配置文件--><mappers><mapper resource="mapper/UserMapper.xml"></mapper></mappers></configuration>

创建mapper.xml配置文件:存放SQL信息、参数类型、返回值类型等

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 唯一标识:namespace.id 取名字叫statementId -->
<mapper namespace="com.mybatis.it.dao.IUserDao"><!--规范:接口的全路径要和namespace的值保持一致接口中的方法名要和id的值保持一致--><!-- 查询所有--><select id="findAll" resultType="com.mybatis.it.pojo.User">select * from user_t</select><!-- 按照条件进行查询--><select id="findByCondition" resultType="com.mybatis.it.pojo.User" parameterType="com.mybatis.it.pojo.User">select * from user_t where id = #{id} and username = #{username}</select>
</mapper>

单元测试用例:

package com.mybatis.it;import com.mybatis.it.dao.IUserDao;
import com.mybatis.it.pojo.User;
import com.mybatis.it.sdk.io.Resources;
import com.mybatis.it.sdk.session.SqlSession;
import com.mybatis.it.sdk.session.SqlSessionFactory;
import com.mybatis.it.sdk.session.SqlSessionFactoryBuilder;
import jdk.nashorn.internal.ir.annotations.Ignore;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;import java.io.InputStream;
import java.util.List;public class MyBatisSDKTest {@Ignore@DisplayName("测试手写版本1MyBatis使用")public void test() {// 1. 根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. 解析了配置文件,封装了Configuration对象 ; 创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3. 生产sqlSession 创建了执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. 调用sqlSession方法User user = new User();user.setId(1);user.setUsername("Tom");User user2 = sqlSession.selectOne("user.selectOne", user);System.out.println(user2);List<Object> list = sqlSession.selectList("user.selectList", null);System.out.println(list);// 释放资源sqlSession.close();}@Test@DisplayName("测试手写版本2MyBatis使用")public void test2() {// 1. 根据配置文件的路径,加载成字节输入流,存到内存中 注意:配置文件还未解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. 解析了配置文件,封装了Configuration对象 ; 创建sqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3. 生产sqlSession 创建了执行器对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. 调用sqlSession方法User user = new User();user.setId(1);user.setUsername("Tom");IUserDao userDao = sqlSession.getMapper(IUserDao.class);User user2 = userDao.findByCondition(user);System.out.println(user2);List<User> list = userDao.findAll();System.out.println(list);// 释放资源sqlSession.close();}
}

项目pom.xml内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mybatis.it</groupId><artifactId>mybatis-demo</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 引入自定义持久层框架的jar包 --><dependency><groupId>com.mybatis.it.sdk</groupId><artifactId>mybatis-sdk</artifactId><version>1.0-SNAPSHOT</version></dependency><!-- 引入单元测试依赖 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.10.1</version><scope>test</scope></dependency></dependencies></project>

同时创建一个maven模块:mybatis-sdk

在这里插入图片描述

引入依赖如下(pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.mybatis.it.sdk</groupId><artifactId>mybatis-sdk</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!-- 解析xml --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!-- xpath --><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency><!-- mysql数据库驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><version>8.2.0</version></dependency><!-- 数据库连接池 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.20</version></dependency><!-- 日志 --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j-impl</artifactId><version>2.22.0</version><scope>test</scope></dependency><!-- 单元测试 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId><version>5.10.1</version><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency></dependencies></project>

创建Resources类:负责加载配置文件,加载成字节流,存到内存中

package com.mybatis.it.sdk.io;import java.io.InputStream;public class Resources {/*** 根据配置文件的路径,加载配置文件成字节输入流,存到内存中,注意配置文件还未解析** @param path* @return*/public static InputStream getResourceAsStream(String path) {InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path);return inputStream;}
}

Configuration:全局配置类:存储SqlMapConfig.xml配置文件解析出来的内容

package com.mybatis.it.sdk.pojo;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 全局配置类:存放核心配置文件解析出来的内容*/
public class Configuration {// 数据源对象private DataSource dataSource;/*** 声明一个Map集合* key:statementId:namespace.id* MappedStatement: 封装好的MappedStatement对象*/private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();public DataSource getDataSource() {return dataSource;}public void setDataSource(DataSource dataSource) {this.dataSource = dataSource;}public Map<String, MappedStatement> getMappedStatementMap() {return mappedStatementMap;}public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {this.mappedStatementMap = mappedStatementMap;}
}

MappedStatement:映射配置类:存储mapper.xml配置文件解析出来的内容

package com.mybatis.it.sdk.pojo;/*** 映射配置类:存放mapper.xml解析内容*/
public class MappedStatement {// 唯一标识:statementId:namespace.idprivate String statementId;// 返回值类型private String resultType;// 参数值类型private String parameterType;// SQL语句private String sql;// 判断当前是什么操作的一个属性private String sqlCommandType;public String getStatementId() {return statementId;}public void setStatementId(String statementId) {this.statementId = statementId;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public String getParameterType() {return parameterType;}public void setParameterType(String parameterType) {this.parameterType = parameterType;}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public String getSqlCommandType() {return sqlCommandType;}public void setSqlCommandType(String sqlCommandType) {this.sqlCommandType = sqlCommandType;}
}

解析配置文件,填充容器对象。创建SqlSessionFactoryBuilder类

提供方法:build(InputStream stream) 方法:

(1) 解析配置文件(dom4j + xpath),封装Configuration

(2) 创建SqlSessionFactory

package com.mybatis.it.sdk.session;import com.mybatis.it.sdk.config.XMLConfigBuilder;
import com.mybatis.it.sdk.pojo.Configuration;import java.io.InputStream;public class SqlSessionFactoryBuilder {/*** 1.解析配置文件,封装容器对象* 2.创建SqlSessionFactory工厂对象** @param inputStream* @return*/public SqlSessionFactory build(InputStream inputStream) {// 1. 解析配置文件,封装容器对象 XMLConfigBuilder:专门解析核心配置文件的解析类XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = xmlConfigBuilder.parse(inputStream);// 2. 创建SqlSessionFactory工厂对象DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);return defaultSqlSessionFactory;}}

创建SqlSessionFactory接口及DefaultSqlSessionFactory实现类

提供方法:SqlSession openSession(); 工厂模式

提供接口类:

package com.mybatis.it.sdk.session;public interface SqlSessionFactory {/*** 1.生产sqlSession对象* 2. 创建执行器对象** @return*/SqlSession openSession();
}

提供实现类:

package com.mybatis.it.sdk.session;import com.mybatis.it.sdk.executor.Executor;
import com.mybatis.it.sdk.executor.SimpleExecutor;
import com.mybatis.it.sdk.pojo.Configuration;public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic SqlSession openSession() {// 1. 创建执行器对象Executor executor = new SimpleExecutor();// 2. 生产sqlSession对象DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration, executor);return defaultSqlSession;}
}

创建SqlSession接口和DefaultSqlSession实现类:

提供方法:

  • selectList(); 查询所有
  • selectOne(); 查询单个
  • update(); 更新
  • delete(); 删除
  • insert(); 添加
  • close();
  • getMapper();
package com.mybatis.it.sdk.session;import java.util.List;public interface SqlSession {/*** 查询多个结果** @param statementId* @param param       SQL参数* @param <E>         元素* @return*/<E> List<E> selectList(String statementId, Object param);/*** 查询单个结果** @param statementId* @param param       SQL参数* @param <T>         类型* @return*/<T> T selectOne(String statementId, Object param);/*** 清除资源*/void close();/*** 生成代理对象*/<T> T getMapper(Class<?> mapperClass);
}

DefaultSqlSession实现类:

package com.mybatis.it.sdk.session;import com.mybatis.it.sdk.executor.Executor;
import com.mybatis.it.sdk.pojo.Configuration;
import com.mybatis.it.sdk.pojo.MappedStatement;import java.lang.reflect.*;
import java.util.List;public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic <E> List<E> selectList(String statementId, Object param) {// 将查询操作委托给底层的执行器// query():执行底层的JDBC操作:1.数据库配置信息 2.SQL配置信息MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);List<E> list = executor.query(configuration, mappedStatement, param);return list;}@Overridepublic <T> T selectOne(String statementId, Object param) {// 去调用selectList方法List<Object> list = this.selectList(statementId, param);if (list.size() == 1) {return (T) list.get(0);} else if (list.size() > 1) {throw new RuntimeException("返回结果大于预期");} else {return null;}}@Overridepublic void close() {executor.close();}@Overridepublic <T> T getMapper(Class<?> mapperClass) {// 使用JDK动态代理生成基于接口的代理对象Object proxy = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {/**** @param proxy 代理对象的引用,很少用* @param method 被调用的方法的字节码对象* @param args 调用的方法参数** @return* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 具体的逻辑:执行底层的JDBC// 通过调用sqlSession里面的方法来完成方法调用// 参数准备:1.statementId 2.param// 问题1:无法获取现有的statementId// 规范:接口的全路径要和namespace的值保持一致;接口中的方法名要和id的值保持一致String methodName = method.getName();String className = method.getDeclaringClass().getName();String statementId = className + "." + methodName;// 方法调用:问题2:要调用sqlSession中增删改查的什么方法?// 改造当前工程:sqlCommandType:判断当前是什么操作的一个属性MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);// sqlCommandType取值范围(insert delete update select)String sqlCommandType = mappedStatement.getSqlCommandType();switch (sqlCommandType) {case "select":// 执行查询方法调用// 问题3:该调用selectList还是selectOne?Type genericReturnType = method.getGenericReturnType();// 判断是否实现了 泛型类型参数化if (genericReturnType instanceof ParameterizedType) {// 表示返回结果是带泛型的if (args != null) {return selectList(statementId, args[0]);}return selectList(statementId, null);}if (args != null) {return selectOne(statementId, args[0]);}return selectOne(statementId, null);case "update":// 执行更新方法调用case "delete":// 执行删除方法调用case "insert":// 执行插入方法调用}return null;}});return (T) proxy;}
}

创建Executor接口和实现类SimpleExecutor

提供方法:query(Configuration,MappedStatement,Object parameter); 执行的就是底层JDBC代码(数据库配置信息、SQL配置信息)

package com.mybatis.it.sdk.executor;import com.mybatis.it.sdk.pojo.Configuration;
import com.mybatis.it.sdk.pojo.MappedStatement;import java.sql.SQLException;
import java.util.List;public interface Executor {<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param);void close();
}

SimpleExecutor实现类:

package com.mybatis.it.sdk.executor;import com.mybatis.it.sdk.config.BoundSql;
import com.mybatis.it.sdk.pojo.Configuration;
import com.mybatis.it.sdk.pojo.MappedStatement;
import com.mybatis.it.sdk.utils.GenericTokenParser;
import com.mybatis.it.sdk.utils.ParameterMapping;
import com.mybatis.it.sdk.utils.ParameterMappingTokenHandler;import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;public class SimpleExecutor implements Executor {private Connection connection = null;private PreparedStatement preparedStatement = null;private ResultSet resultSet = null;@Overridepublic <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object param) {try {// 1. 加载驱动,获取数据源连接connection = configuration.getDataSource().getConnection();// 2. 获取preparedStatement预编译对象// 获取要执行的SQL语句/*** select * from user_t where id = #{id} and username = #{username}* 替换成* select * from user_t where id = ? and username = ?* 解析替换过程中:自定义占位符#{id}里面的值保存下来*/String sql = mappedStatement.getSql();BoundSql boundSql = getBoundSql(sql);String finalSql = boundSql.getFinalSql();preparedStatement = connection.prepareStatement(finalSql);// 3.设置参数String parameterType = mappedStatement.getParameterType();if (parameterType != null) {Class<?> parameterTypeClass = Class.forName(parameterType);List<ParameterMapping> parameterMappingList = boundSql.getList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);// 值为#{}里面的内容String paramName = parameterMapping.getContent();// 反射Field declaredField = parameterTypeClass.getDeclaredField(paramName);// 暴力访问declaredField.setAccessible(true);Object value = declaredField.get(param);// 赋值占位符preparedStatement.setObject(i + 1, value);}}// 4. 执行SQL,发起查询resultSet = preparedStatement.executeQuery();// 5. 处理返回结果集List<E> list = new ArrayList<>();while (resultSet.next()) {// 元数据信息 包含了:字段名以及字段的值ResultSetMetaData metaData = resultSet.getMetaData();String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = Class.forName(resultType);Object object = resultTypeClass.newInstance();for (int i = 1; i <= metaData.getColumnCount(); i++) {// 字段名String columnName = metaData.getColumnName(i);// 字段值Object value = resultSet.getObject(columnName);// 封装// 属性描述器:通过API方法获取某个属性的读写方法PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();// 参数1:实例对象 参数2:要设置的值writeMethod.invoke(object, value);}list.add((E) object);}return list;} catch (Exception exception) {throw new RuntimeException(exception);}}/*** 1. 将#{}占位符替换成?* 2. 解析替换的过程中 将#{}里面保存的值保存下来** @param sql* @return*/private BoundSql getBoundSql(String sql) {// 1. 创建标记处理器:配合标记解析器完成标记的处理解析工作ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 2. 创建标记解析器GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);// #{}占位符替换成? 解析替换过程中 将#{}里面保存的值保存下来ParameterMapping集合中String finalSql = genericTokenParser.parse(sql);// #{}里面的值的一个集合List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();BoundSql boundSql = new BoundSql(finalSql, parameterMappings);return boundSql;}/*** 释放资源*/@Overridepublic void close() {if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {throw new RuntimeException(e);}}if (connection != null) {try {connection.close();} catch (SQLException e) {throw new RuntimeException(e);}}}
}

解析配置文件逻辑:

XMLConfigBuilder

package com.mybatis.it.sdk.config;import com.alibaba.druid.pool.DruidDataSource;
import com.mybatis.it.sdk.io.Resources;
import com.mybatis.it.sdk.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;
import java.util.Properties;public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}/*** 使用dom4j+xpath解析配置文件,封装Configuration对象** @param inputStream* @return*/public Configuration parse(InputStream inputStream) {try {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();List<Element> list = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : list) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name, value);}// 创建数据源对象DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setDriverClassName(properties.getProperty("driverClassName"));druidDataSource.setUrl(properties.getProperty("url"));druidDataSource.setUsername(properties.getProperty("username"));druidDataSource.setPassword(properties.getProperty("password"));// 创建好的数据源对象封装到Configuration对象中configuration.setDataSource(druidDataSource);/*** 解析映射配置文件* 1.获取映射配置文件的路径* 2.根据路径进行映射配置文件的加载解析* 3.封装到MappedStatement对象中 --> Configuration里面Map<String, MappedStatement>中*/// 1.获取映射配置文件的路径List<Element> mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);// 专门解析映射配置文件的对象XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);// 2.根据路径进行映射配置文件的加载解析// 3.封装到MappedStatement对象中 --> Configuration里面Map<String, MappedStatement>中xmlMapperBuilder.parse(resourceAsStream);}return configuration;} catch (DocumentException e) {throw new RuntimeException(e);}}
}

XMLMapperBuilder

package com.mybatis.it.sdk.config;import com.mybatis.it.sdk.pojo.Configuration;
import com.mybatis.it.sdk.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;/*** parse:解析配置文件 --> mappedStatement --> Configuration里面Map<String, MappedStatement>中*/
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream resourceAsStream) {try {Document document = new SAXReader().read(resourceAsStream);Element rootElement = document.getRootElement();String namespace = rootElement.attributeValue("namespace");List<Element> selectList = rootElement.selectNodes("//select");for (Element element : selectList) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sql = element.getTextTrim();String statementId = namespace + "." + id;// 封装MappedStatement对象MappedStatement mappedStatement = new MappedStatement();mappedStatement.setStatementId(statementId);mappedStatement.setParameterType(parameterType);mappedStatement.setResultType(resultType);mappedStatement.setSql(sql);mappedStatement.setSqlCommandType("select");// 将封装好的MappedStatement封装到Configuration里面Map<String, MappedStatement>集合中this.configuration.getMappedStatementMap().put(statementId, mappedStatement);}} catch (DocumentException e) {throw new RuntimeException(e);}}
}

BoundSql

package com.mybatis.it.sdk.config;import com.mybatis.it.sdk.utils.ParameterMapping;import java.util.List;public class BoundSql {private String finalSql;private List<ParameterMapping> list;public BoundSql(String finalSql, List<ParameterMapping> list) {this.finalSql = finalSql;this.list = list;}public String getFinalSql() {return finalSql;}public void setFinalSql(String finalSql) {this.finalSql = finalSql;}public List<ParameterMapping> getList() {return list;}public void setList(List<ParameterMapping> list) {this.list = list;}
}

解析参数工具类:

GenericTokenParser:来自mybatis源码

package com.mybatis.it.sdk.utils;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;}/*** 解析${}和#{}* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。* 其中,解析工作由该方法完成,处理工作由处理器handler的handleToken()方法来实现** @param text* @return*/public String parse(String text) {// 验证参数问题,如果是null,就返回空字符串if (text == null || text.isEmpty()) {return "";}// search open token// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行int start = text.indexOf(openToken, 0);if (start == -1) {return text;}// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder// text变量中占位符对应的变量名是expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行char[] src = text.toCharArray();int offset = 0;final StringBuilder builder = new StringBuilder();StringBuilder expression = null;while (start > -1) {// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理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 {// 重置expression变量,避免空指针或者老数据干扰。// 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);offset = end + closeToken.length();break;}}if (end == -1) {// close token was not found.builder.append(src, start, src.length - start);offset = src.length;} else {// 首先根据参数的key(即expression)进行参数处理,返回?作为占位符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();}
}

TokenHandler:来自mybatis源码

package com.mybatis.it.sdk.utils;public interface TokenHandler {String handleToken(String content);
}

ParameterMapping:

package com.mybatis.it.sdk.utils;public class ParameterMapping {// 值为#{}里面的内容:如:id、usernameprivate String content;public ParameterMapping(String content) {this.content = content;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}
}

ParameterMappingTokenHandler:参考mybatis源码

package com.mybatis.it.sdk.utils;import java.util.ArrayList;
import java.util.List;public class ParameterMappingTokenHandler implements TokenHandler {private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();// content是参数名称 #{id} #{username}@Overridepublic String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}private ParameterMapping buildParameterMapping(String content) {ParameterMapping parameterMapping = new ParameterMapping(content);return parameterMapping;}public List<ParameterMapping> getParameterMappings() {return parameterMappings;}public void setParameterMappings(List<ParameterMapping> parameterMappings) {this.parameterMappings = parameterMappings;}
}

对应项目源码资源:https://download.csdn.net/download/liwenyang1992/88615616

相关文章:

仿照MyBatis手写一个持久层框架学习

首先数据准备&#xff0c;创建MySQL数据库mybatis&#xff0c;创建表并插入数据。 DROP TABLE IF EXISTS user_t; CREATE TABLE user_t ( id INT PRIMARY KEY, username VARCHAR ( 128 ) ); INSERT INTO user_t VALUES(1,Tom); INSERT INTO user_t VALUES(2,Jerry);JDBC API允…...

关东升老师极简系列丛书(由清华大学出版社出版)

极简系列丛书&#xff0c;编程学习新体验 在这个科技日新月异的时代&#xff0c;编程已经成为了一种必备技能。但是面对各种复杂的编程语言&#xff0c;你是否也曾感到过迷茫和困惑&#xff1f;由清华大学出版社出版的“极简系列丛书”就是为了帮助你解决这个问题。 这套丛书…...

要求CHATGPT高质量回答的艺术:提示工程技术的完整指南—第 27 章:如何避开和绕过所有人工智能内容检测器

要求CHATGPT高质量回答的艺术&#xff1a;提示工程技术的完整指南—第 27 章&#xff1a;如何避开和绕过所有人工智能内容检测器 使用高易错性和突发性方法 与人工智能生成的文本相比&#xff0c;人类写作往往具有更多的突发性&#xff0c;这是由于人类往往比人工智能生成的文…...

JavaWeb笔记之MySQL数据库

#Author 流云 #Version 1.0 一、引言 1.1 现有的数据存储方式有哪些&#xff1f; Java程序存储数据&#xff08;变量、对象、数组、集合&#xff09;&#xff0c;数据保存在内存中&#xff0c;属于瞬时状态存储。 文件&#xff08;File&#xff09;存储数据&#xff0c;保存…...

Amazon CodeWhisperer 开箱初体验

文章作者&#xff1a;Coder9527 科技的进步日新月异&#xff0c;正当人工智能发展如火如荼的时候&#xff0c;各大厂商在“解放”码农的道路上不断创造出各种 Coding 利器&#xff0c;今天在下就带大家开箱体验一个 Coding 利器&#xff1a; Amazon CodeWhisperer。 亚马逊云科…...

Java的引用类型有几种?区别是什么?

Java中的引用类型主要分为四种&#xff1a;强引用&#xff08;Strong Reference&#xff09;、软引用&#xff08;Soft Reference&#xff09;、弱引用&#xff08;Weak Reference&#xff09;和虚引用&#xff08;Phantom Reference&#xff09;。这些引用类型在Java中主要用于…...

掌握iText:轻松处理PDF文档-基础篇

关于iText iText是一个强大的PDF处理库&#xff0c;可以用于创建、读取和操作PDF文件。它支持PDF表单、加密和签署等操作&#xff0c;同时支持多种字体和编码。maven的中央仓库中的最新版本是5.X&#xff0c;且iText5不是完全免费的&#xff0c;但是基础能力是免费使用的&…...

小红书民宿文案怎么写?建议收藏

随着民宿市场的日益火爆&#xff0c;如何在众多民宿中脱颖而出&#xff0c;吸引更多租客入住&#xff0c;成为摆在每一位民宿业主面前的难题。一篇优质的小红书民宿文案&#xff0c;不仅能吸引潜在租客的关注&#xff0c;还能提高民宿的知名度。本文伯乐网络传媒将从八个方面教…...

C#教程(一):面向对象

1、介绍 C#是一种多范式编程语言&#xff0c;但其中一个主要的编程范式是面向对象编程&#xff08;OOP&#xff09;。面向对象编程有一些特点&#xff0c;而C#提供了丰富的功能来支持这些特点。 2、面向对象特点 封装&#xff08;Encapsulation&#xff09;&#xff1a; 封装…...

Linux系统中部署minio服务、开启反向代理、二级域名SSL加固

链接: B站1小时-配置指导视频: 一、创建minio 文件目录(/project/minio) 二、下载Minio wget https://dl.min.io/server/minio/release/linux-amd64/minio 三、在minio目录中-创建日志文件 四、对minio(可以理解为windows系统中的.exe可执行文件) 进行授权 chmod 777 min…...

PMP备考总结:项目管理PMP考试提高通过率,轻松上岸~

分享一篇左羊学霸的备考总结&#xff0c;希望能帮到正在备考的友友们~ 前言 作为⼀名通过PMP项⽬管理认证并且拿到3A成绩 ( PMP认证最好成绩) 的 学习者&#xff0c; 来跟⼤家分享下我考取PMP证书的动机与过程 。考证不是主要⽬ 的&#xff0c; 在考证的过程深化⾃⼰的项⽬管理…...

shell脚本中获取当前脚本的绝对路径

说明: PWD 是获取当前脚本的执行路径的&#xff0c;下面的方式是获取文件绝对路径的。 话不多说&#xff0c;直接上硬货!!! #!/bin/bashecho "执行路径 $PWD"absolute_path$(readlink -f "$0") # 获取目录路径 directory$(dirname "$absolute_path&q…...

SSD基础架构与NAND IO并发问题探讨

在我们的日常生活中&#xff0c;我们经常会遇到一些“快如闪电”的事物&#xff1a;比如那场突如其来的雨、那个突然出现在你眼前的前任、还有就是今天我们要聊的——固态硬盘&#xff08;SSD&#xff09;。 如果你是一个技术宅&#xff0c;或者对速度有着近乎偏执的追求&…...

激光雷达反射率定标板如何提取障碍信息

随着信息科技技术的发展&#xff0c;自动驾驶技术在移动机器人等智能移动设备领域得到广泛应用。智能移动设备不仅减少了人力劳动&#xff0c;方便生活&#xff0c;而且提高了工作效率。激光雷达作为自动驾驶技术的核心避障传感器&#xff0c;得到迅速发展。 激光雷达通过对发射…...

【开源】基于JAVA的桃花峪滑雪场租赁系统

项目编号&#xff1a; S 036 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S036&#xff0c;文末获取源码。} 项目编号&#xff1a;S036&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 游客服务2.2 雪场管理 三、数据库设…...

将VOC2012格式的数据集转为YOLOV8格式

文章目录 简介1.数据集格式1.1数据集目录格式对比1.2标签格式对比 2.格式转换脚本3.文件处理脚本 简介 将voc2012中xml格式的标签转为yolov8中txt格式将转换后的图像和标签按照yolov8训练的要求整理为对应的目录结构 1.数据集格式 1.1数据集目录格式对比 &#xff08;1&…...

DevExpress WinForms Pivot Grid组件,一个类似Excel的数据透视表控件(二)

界面控件DevExpress WinForms的Pivot Grid组件是一个类似Excel的数据透视表控件&#xff0c;用于多维(OLAP)数据分析和跨选项卡报表。在上文中&#xff08;点击这里回顾>>&#xff09;我们介绍了DevExpress WinForms Pivot Grid组件的性能、分析服务、数据塑造能力等&…...

为什么越来越多的人从事软件测试行业?

1.市场需求增加&#xff1a;随着数字化转型和互联网的普及&#xff0c;各行各业都需要高质量、稳定可靠的软件来支持其业务运作。因此&#xff0c;对软件测试人员的需求也随之增加。同时&#xff0c;新兴技术的发展&#xff0c;如物联网、大数据、区块链、人工智能等&#xff0…...

ERP数据仓库模型

ERP数据仓库模型建设是一个复杂的过程&#xff0c;涉及到多个主题域。以下是一个详细的设计方案&#xff1a; 确定业务需求和目标 在开始设计数据仓库模型之前&#xff0c;需要了解企业的业务需求和目标。这包括了解企业的运营模式、业务流程、关键绩效指标等。通过与业务部门…...

基于单片机的智能小车 (论文+源码)

1. 系统设计 此次可编程智能小车系统的设计系统&#xff0c;结合STM32单片机&#xff0c;蓝牙模块&#xff0c;循迹模块&#xff0c;电机驱动模块来共同完成本次设计&#xff0c;实现小车的循迹避障功能和手机遥控功能&#xff0c;其整体框架如图2.1所示。其中&#xff0c;采用…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)

2025年能源电力系统与流体力学国际会议&#xff08;EPSFD 2025&#xff09;将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会&#xff0c;EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)

1.获取 authorizationCode&#xff1a; 2.利用 authorizationCode 获取 accessToken&#xff1a;文档中心 3.获取手机&#xff1a;文档中心 4.获取昵称头像&#xff1a;文档中心 首先创建 request 若要获取手机号&#xff0c;scope必填 phone&#xff0c;permissions 必填 …...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

招商蛇口 | 执笔CID,启幕低密生活新境

作为中国城市生长的力量&#xff0c;招商蛇口以“美好生活承载者”为使命&#xff0c;深耕全球111座城市&#xff0c;以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子&#xff0c;招商蛇口始终与城市发展同频共振&#xff0c;以建筑诠释对土地与生活的…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...