ToB企服应用市场:ToB评测及商务社交产业平台
标题:
超详细的手把手撸代码---教你你⾃定义持久层框架设计--Mybatis(强烈建议阅
[打印本页]
作者:
民工心事
时间:
2022-6-23 10:55
标题:
超详细的手把手撸代码---教你你⾃定义持久层框架设计--Mybatis(强烈建议阅
目录
目录
概述
为什么要Mybatis,之前的jdbc不香吗??
JDBC的问题
问题解决思路
⾃定义框架设计
使用方:
提供核⼼配置⽂件:
下面是具体的实现:
框架方:
1.读取配置⽂件
实现方法:
首先编写一个Resources类,用于加载配置文件
2.解析配置文件:dom4j(解析xml)
第二步:封装两个javaBean
3.创建sqlSessionFactory
4.创建sqlSession接口及实现类:DefaultSqlSession
5.创建Executor接口及实现类 SimpleExecutor实现类
存在问题:
概述
为什么要Mybatis,之前的jdbc不香吗??
带着这个问题,我们看一下原生的jdbc是如何进行数据库交互的,下面是一个简单的查询语句。
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding = utf - 8 ", " root ", " root ");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = 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");
// 封装User
user.setId(id);
user.setUsername(username);
}
System.out.println(user);
}
} catch(
Exception e)
{
e.printStackTrace();
} finally
{
// 释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
复制代码
what???一个简单的查询就做了这么多的操作,真实让人头疼。
JDBC的问题
下面我们再聊一聊其他问题JDBC问题:
原始jdbc开发存在的问题如下:
1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。(需要创建链接,三次握手等操作耗时)
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。
3、 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名,),手动设计结果集,如果结果很多,实现起来很麻烦,sql变化导致解析代码变化,系统不易维护,如果能将数据 库 记录封装成pojo对象解析⽐较⽅便
5、数据库配置信息 驱动等硬编码
带着上面的问题我们能不能优化一下,改吧jdbc的不足呢?
问题解决思路
提到硬编码--->配置文件解决
创建连接------>连接池
①使⽤数据库连接池初始化连接资源
②将sql语句抽取到xml配置⽂件中
③使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射
根据上面的思路,我们来实现一个自己自定义的jdbc框架,实现对数据库的操作。
⾃定义框架设计
不可少的部分:
数据库配置信息、sql的配置信息(sql语句+参数类型+返回值类型) ------->由使用方进行提供
使用配置文件提供这些,具体见下面
使用方:
提供核⼼配置⽂件:
1)sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml
2)Mapper.xml : sql语句的配置⽂件信息
下面是具体的实现:
首先创建一个工程
我这里叫my_persistence_demo
主要进行了配置文件的编写
sqlM aMapConfig.xml内容如下:
主要是数据库的一些配置信息,个标签都是自定义的(可以根据喜好命名,下面的也是如此)
未来能够减少读取xml文件的次数,将userMapper.xml文件路径也引入了
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///custom_mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</dataSource>
<mapper resource="userMapper.xml"></mapper>
</configuration>
复制代码
我这里使用的是本地数据库 库名是custom_mybatis
建表语句如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) DEFAULT NULL,
`name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
SET FOREIGN_KEY_CHECKS = 1;
复制代码
userMapper.xml内容如下
只要是一些sql语句的配置
<mapper namespace="user">
<select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
select * from user
</select>
<select id="selectOne" resultType="cn.mystylefree.my_persistence_demo.domain.User" paramterType="cn.mystylefree.my_persistence_demo.domain.User">
select * from user where id= #{id} and name= #{name}
</select>
复制代码
User实体类如下
@Data
public class User {
private String id;
private String name;
}
复制代码
这是使用端的代码就基本写完了,下面是对服务端的编写
框架方:
其实就是对jdbc代码进行封装优化
项使用必须有上面的配置信息,将信息解析出来就能连接数据库了
读取配置⽂件
解析配置文件
创建sqlSessionFactory
创建sqlSession接口及实现类:主要封装crud操作
涉及到的设计模式:
Builder构建者设计模式、⼯⼚模式、代理模式
1.读取配置⽂件
创建Resources类。 方法。InputStream getResourceAsStream(String path)
读取完成以后以流的形式存在,
我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
封装成两个实体 javaBean,存放的就是上面两个配置文件的信息
(1)Configuration 核心配置类:
存放数据库基本信息存放的就是sqlMapConfig.xml配置信息的内容:
Map 唯⼀标识:namespace + "." + id
(2)MappedStatement映射配置类:
存Mapper.xml的配置
sql语句、statement类型、输⼊参数java类型、输出参数java类型
实现方法:
首先编写一个Resources类,用于加载配置文件
public class Resources {
/**
* 根据指定的配置文件路径,将配置文件加载成字节输入流,存储到内参中
* @param path
* @return
*/
public static InputStream getResourceAsStream(String path){
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}
复制代码
下面对这歌类进行测试
首先要将框架端端项目进行打包,这样使用方才能够引入该依赖
<dependency>
<groupId>cn.mystylefree</groupId>
<artifactId>my_persistence</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
复制代码
说明一如成功了
创建测试类
2.解析配置文件:dom4j(解析xml)
创建sqlSessionFactoryBuilder类:
⽅法:sqlSessionFactory build(inputStream):
第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
为啥不直接new 用工厂模式
第二步:封装两个javaBean
这里的MappedStatement的属性是根据 userMapper.xml中的配置标签来的
@Data
public class MappedStatement {
//id标识
private String id;
//返回值类型
private String resultType;
//参数类型
private String parameterType;
//sql语句
private String sql;
}
复制代码
未了方便进行对象的传递,这里将这两个配置Bean封装成一个对象
DataSource就是数据库的配置信息
@Data
public class Configuration {
private DataSource dataSource;
/**
* 将解析的Configuration 和MappedStatement同时封装到一起
* 每一个sql语句就是一个MappedStatement
* k 唯一标示 statementId namespace.id组成:这个唯一的id现在叫 statementId
* v 分装好的MappedStatement对象
*/
Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
}
复制代码
使用dom4j解析配置,要引入相关的依赖
下面是框架端的全部依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
复制代码
public class SqlSessionFactoryBuilder {
/**
* 创建sqlSessionFactoryBuilder类:
* <p>
* ⽅法:sqlSessionFactory build(inputStream):
* <p>
* 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
* 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
*
* @param inputStream
* @return
*/
public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
//第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
//第⼆:创建SqlSessionFactory
return null;
}
}
复制代码
通过parseConfig方法实现解析
对数据源信息解析
public class XMLConfigBuilder {
private Configuration configuration;
/**
* 在调用到时候就会执行无参构造
*/
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
/**
* 对将配置文件使用dom4j进行解析,封装Configuration的方法
*
* @param inputStream
* @return
*/
public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
Document read = new SAXReader().read(inputStream);
/*<configuration>根对象*/
//数据源的解析
Element rootElement = read.getRootElement();
//表示 property 在任意位置都能获取到 一个property就是一个元素
List<Element> list = rootElement.selectNodes("//property");
//<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
Properties properties = new Properties();
for (Element element : list) {
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解析
/*@step1 获取路径 拿到路径 字节流 dom4j*/
//<mapper resource="userMapper.xml"></mapper>
List<Element> elements = rootElement.selectNodes("//mapper");
for (Element element : elements) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsStream);
}
return configuration;
}
}
复制代码
对sql配置xml的解析
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
/**
* <select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
* select * from user
* </select>
*
* @param inputStream
* @throws DocumentException
*/
public void parse(InputStream inputStream) throws DocumentException {
Document read = new SAXReader().read(inputStream);
//获取到了mapper节点 接下来获取select 标签
Element rootElement = read.getRootElement();
List<Element> selectNodes = rootElement.selectNodes("//select");
/*没过element就是
<select id="selectList" resultType="cn.mystylefree.my_persistence_demo.domain.User">
select * from user
</select>*/
String namespace = rootElement.attributeValue("namespace");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String resultType = node.attributeValue("resultType");
String parameterType = node.attributeValue("parameterType");
//sql语句 文本信息
String sqlText = node.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setParameterType(parameterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sqlText);
//唯一标识
String key = namespace + "." + id;
configuration.getMappedStatementMap().put(key, mappedStatement);
}
}
}
复制代码
到此 Configuration配置就解析完成了
3.创建sqlSessionFactory
DefaultSqlSessionFactory 生产sqlSession
⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
public class SqlSessionFactoryBuilder {
/**
* 创建sqlSessionFactoryBuilder类:
* <p>
* ⽅法:sqlSessionFactory build(inputStream):
* <p>
* 第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
* 第⼆:创建SqlSessionFactory的实现类DefaultSqlSession 产生sqlSession(会话对象)工厂模式
*
* @param inputStream
* @return
*/
public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException {
//第⼀:使⽤dom4j解析配置⽂件,将解析出来内容封装到Configuration和MappedStatement中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
/*
* 第⼆:创建SqlSessionFactory
* 是一个工厂类:生产sqlSession 会话对象
* 数据库交互的增、删、改、查方法都封装在sqlSession对象
* 以有参构造的方式将 配置传递下去
* */
SqlSessionFactory sqlSessionFactory=new DefaultSqlSessionFactory(configuration);
return sqlSessionFactory;
}
}
复制代码
public interface SqlSessionFactory {
public SqlSession openSession();
}
复制代码
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession();
}
}
复制代码
public interface SqlSession {
}
public class DefaultSqlSession implements SqlSession {
}
复制代码
此时创建sqlSessionFactory就完成了
4.创建sqlSession接口及实现类:DefaultSqlSession
主要封装crud操作
⽅法:
selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
update
delete
具体实现:封装JDBC完成对数据库表的查询操作
实现SqlSession接口
public interface SqlSession {
/**
* 查询所有
*
* @param <E> 这里会有范型
* user模块就是user Product就是product 所以不确定类型
* @param statementId 这里是需要将查询的唯一标识传进来,
* 只有传递这个id了才知道执行的sql语句(Configuration中查出来)
* @param params 查询条件
* @return
*/
public <E> List<E> selectList(String statementId, Object... params);
/**
* 根据条件查询单个
* @param statementId
* @param params
* @param <T>
* @return
*/
public <T> T selectOne(String statementId, Object... params);
}
复制代码
实现
这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
复制代码
public class DefaultSqlSession implements SqlSession {
/**
* 这里其实就能够直接写jdbc了,但是为了更优雅继续进行封装
* @param statementId 这里是需要将查询的唯一标识传进来,
* 只有传递这个id了才知道执行的sql语句(Configuration中查出来)
* @param params 查询条件
* @param <E>
* @return
*/
@Override
public <E> List<E> selectList(String statementId, Object... params) {
return null;
}
@Override
public <T> T selectOne(String statementId, Object... params) {
List<Object> objects = this.selectList(statementId, params);
if (objects.size() == 1) {
return (T) objects.get(0);
} else {
throw new RuntimeException("查询结果为空或者查询结果过多");
}
}
}
复制代码
5.创建Executor接口及实现类 SimpleExecutor实现类
query(Configuration, MappedStatement, Object ...params):执行的就是JDBC代码
参数信息就是步骤1中的对象信息
Object 操作数据库查询条件的具体参数值,不确定查询对象参数个数
如id=1 code=xsd
将jdbc的代码添加到这里
复制代码
下面是用于解析的工具类
这是标记的解析器
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) {
// 验证参数问题,如果是null,就返回空字符串。
if (text == null || text.isEmpty()) {
return "";
}
// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
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] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression变量,避免空指针或者老数据干扰。
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();
}
}
复制代码
这里存的是context是参数名称 #{id} #{username} 中的id和username
就是#{}中的内容
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
复制代码
public interface TokenHandler {
String handleToken(String content);
}
复制代码
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是参数名称 #{id} #{username} 中的id和username
public 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;
}
}
复制代码
下面的是编写jdbc代码
//step1 注册驱动,获取连接
复制代码
在配置中有
//step2 获取sql语句 select * from user where id= #{id} and name= #{name}
复制代码
mappedStatement中存储
//id标识
private String id;
//返回值类型
private String resultType;
//参数类型
private String parameterType;
//sql语句
private String sql;
复制代码
//step3 转换sql语句
复制代码
对sql进行解析 #{}的解析工作
将sql和参数名封装到BoundSql中
复制代码
//step4 获取预处理对象 preparedStatement
复制代码
ParameterMapping中存的是 #{}中的参数
复制代码
//step5 设置参数,进行替换 占位符
复制代码
//step6 执行sql
复制代码
//step7 封装返回结果集
复制代码
public class SimpleExecutor implements Executor {
/**
* 编写jdbc代码
*
* @param configuration
* @param mappedStatement
* @param params
* @param <E>
* @return
*/
@SneakyThrows
@Override
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) {
//step1 注册驱动,获取连接
Connection connection = configuration.getDataSource().getConnection();
//step2 获取sql语句 select * from user where id= #{id} and name= #{name}
//jdbc不能识别
// select * from user where id= ? and name= ? 转换过程中对 #{} 中对值进行解析存储
//为啥不直接在写sql时就用? 因为是一种需求 传入对是对象,根据占位符中对值,到对象属性中查找
String sql = mappedStatement.getSql();
//step3 转换sql语句
BoundSql boundSql = getBoundSql(sql);
//step4 获取预处理对象 preparedStatement
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
//step5 设置参数,如何进行替换呢??
//替换中的数据是什么变量怎么进行设计的呢???
//这里的数据是保存在 BoundSql中的
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
//需要这个下标
//获取到参数的全地址
String parameterType = mappedStatement.getParameterType();
Class<?> parameterTypeClass = getClassType(parameterType);
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
//id 和 username
String content = parameterMapping.getContent();
//通过反射 根据 content 获取到对象中的属性值--在获取到user参数中传递的具体值
//(1)获取实体的全路径 String resultType = node.attributeValue("resultType");
//parameterType="cn.mystylefree.my_persistence_demo.domain.User
Field declaredField = parameterTypeClass.getDeclaredField(content);
//获取到属性对象之后,下面是获取属性对象的值的方法
declaredField.setAccessible(true);//暴力访问
//这里获取的就是属性的值 从入参获取
Object o = declaredField.get(params[0]);
preparedStatement.setObject(i + 1, o);//sql设置参数的下标是从1开始
}
//step6 执行sql
ResultSet resultSet = preparedStatement.executeQuery();
//step7 封装返回结果集
//获取返回对象class
Class<?> resultTypeClass = Class.forName(mappedStatement.getResultType());
/*这就是封装好的对象了*/
Object instance = resultTypeClass.getConstructor().newInstance();
List<Object> objects = new ArrayList<>();
while (resultSet.next()) {
//元数据 这里包含了查询字段的名称
ResultSetMetaData metaData = resultSet.getMetaData();
//为啥从1开始?
for (int i = 1; i < metaData.getColumnCount(); i++) {//查询结果的列数(表中的字段个数)
//字段名
String columnName = metaData.getColumnName(i);//这个ColumnName的下标从1开始
//字段的值
Object value = resultSet.getObject(columnName);
//使用反射或者内省,根据数据库表和实体的对应关系,完成封装
//利用有参数构造,在创建后 就会从 resultType中获取 columnName (这个属性)生成读写方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
//写方法
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(instance, value);//这里就把value值封装到了instance对象中了
}
objects.add(instance);
}
return (List<E>) objects;
}
private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
if (parameterType != null) {
Class<?> aClass = Class.forName(parameterType);
return aClass;
}
return null;
}
/**
* 完成对 #{}的解析工作:
* <p>
* 1.将#{}使用 ?进行代替
* 2.解析成#{} 中的值进行存储
*
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
//标记处理类:配置标记解析器 来完成对占位符对解析处理工作
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//2.解析成#{} 中的值进行存储
//#{}中解析出来的参数名称
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
//面向对象思想,将sql和参数名封装到BoundSql中
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
}
复制代码
这是进行解析时调试的信息,将sql进行了替换,具体的替换方法在代码中有详细的标记,这里就不做过多的冗余介绍了。
数据库 数据如下:
这时就将数据库中的数据查到了,是不是感到很有成就感,给自己点赞
来源:
https://blog.csdn.net/qq_29235677/article/details/125271197
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4