跨境互联网 跨境互联网
首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)

Revin

首页
  • AI 工具

    • 绘图提示词工具 (opens new window)
    • ChatGPT 指令 (opens new window)
  • ChatGPT

    • ChatGP T介绍
    • ChatGPT API 中文开发手册
    • ChatGPT 中文调教指南
    • ChatGPT 开源项目
  • Midjourney

    • Midjourney 文档
  • Stable Diffusion

    • Stable Diffusion 文档
  • 其他

    • AIGC 热门文章
    • 账号合租 (opens new window)
    • 有趣的网站
  • Vue

    • Vue3前置
  • JAVA基础

    • Stream
    • Git
    • Maven
    • 常用第三方类库
    • 性能调优工具
    • UML系统建模
    • 领域驱动设计
    • 敏捷开发
    • Java 测试
    • 代码规范及工具
    • Groovy 编程
  • 并发编程&多线程

    • 并发编程
    • 高性能队列 Disruptor
    • 多线程并发在电商系统下的应用
  • 其他

    • 面试题
  • 消息中间中间件

    • Kafka
    • RabbitMQ
    • RocketMQ
  • 任务调度

    • Quartz
    • XXL-Job
    • Elastic-Job
  • 源码解析

    • Mybatis 高级使用
    • Mybatis 源码剖析
    • Mybatis-Plus
    • Spring Data JPA
    • Spring 高级使用
    • Spring 源码剖析
    • SpringBoot 高级使用
    • SpringBoot 源码剖析
    • Jdk 解析
    • Tomcat 架构设计&源码剖析
    • Tomcat Web应用服务器
    • Zookeeper 高级
    • Netty
  • 微服务框架

    • 分布式原理
    • 分布式集群架构场景化解决方案
    • Dubbo 高级使用
    • Dubbo 核心源码剖析
    • Spring Cloud Gateway
    • Nacos 实战应用
    • Sentinel 实战应用
    • Seata 分布式事务
  • 数据结构和算法的深入应用
  • 存储

    • 图和Neo4j
    • MongoDB
    • TiDB
    • MySQL 优化
    • MySQL 平滑扩容实战
    • MySQL 海量数据存储与优化
    • Elasticsearch
  • 缓存

    • Redis
    • Aerospike
    • Guava Cache
    • Tair
  • 文件存储

    • 阿里云 OSS 云存储
    • FastDF 文件存储
  • 基础

    • Linux 使用
    • Nginx 使用与配置
    • OpenResty 使用
    • LVS+Keepalived 高可用部署
    • Jekins
  • 容器技术

    • Docker
    • K8S
    • K8S
  • 01.全链路(APM)
  • 02.电商终极搜索解决方案
  • 03.电商亿级数据库设计
  • 04.大屏实时计算
  • 05.分库分表的深入实战
  • 06.多维系统下单点登录
  • 07.多服务之间分布式事务
  • 08.业务幂等性技术架构体系
  • 09.高并发下的12306优化
  • 10.每秒100W请求的秒杀架构体系
  • 11.集中化日志管理平台的应用
  • 12.数据中台配置中心
  • 13.每天千万级订单的生成背后痛点及技术突破
  • 14.红包雨的架构设计及源码实现
  • 人工智能

    • Python 笔记
    • Python 工具库
    • 人工智能(AI) 笔记
    • 人工智能(AI) 项目笔记
  • 大数据

    • Flink流处理框架
  • 加密区

    • 机器学习(ML) (opens new window)
    • 深度学习(DL) (opens new window)
    • 自然语言处理(NLP) (opens new window)
AI 导航 (opens new window)
  • Spring Data JPA
  • MyBatis

    • Mybatis高级使用

      • 自定义持久层框架
        • JDBC问题总结:
        • 使用端:
        • 框架端:
          • 1.读取配置文件
          • 2.解析配置文件
          • 3.创建SqlSessionFactory:
          • 4.创建sqlSession接口及实现类:主要封装crud方法
          • 涉及到的设计模式:
        • 1、创建SqlMapConfig.xml
        • 2、创建UserMapper.xml
        • 3、User对象
        • 4、在父工程中引入依赖
        • 5、在子工程中mybatis-persistence引入依赖
        • 6、完成框架端中的读取配置文件
          • (1)、编写Configuration类
          • (2)、编写MappedStatement类
        • 7、完成框架端中的解析配置文件
          • (1)、编写Resources类,读取客户端的xml文
          • (2)、编写SqlSessionFactoryBuilder类
          • (3)、编写XMLConfigBuilder类
          • (4)、编写XMLMapperBuilder类
          • (5)、SqlSessionFactory接口以及DefaultSqlSessionFactory实现类
          • (6)、SqlSession接口以及DefaultSqlSession实现类
          • (7)、Executor接口以及SimpleExecutor实现类
        • 解决思路:
      • Mybatis相关概念
      • Mybatis基本应用
      • Mybatis配置文件深入
      • Mybatis复杂映射开发
      • Mybatis注解开发
      • Mybatis缓存
      • Mybatis插件
      • Mybatis架构原理
      • Mybatis源码剖析
      • 设计模式
    • Mybatis源码剖析

    • Mybatis-Plus
    • 资料
  • Spring

  • SpringBoot

  • Jdk

  • Tomcat

  • Netty

  • 若依

  • Traefik

  • Openresty

  • 开源框架
  • MyBatis
  • Mybatis高级使用
Revin
2023-07-22
目录

自定义持久层框架

lg

# 1.1 分析JDBC操作问题

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        // 加载数据库驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 通过驱动管理类获取数据库连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
                "root", "root");
        // 定义sql语句 ?标识占位符
        String sql = "select * from user where id = ? aand username = ?";
        // 获取预处理statement
        ps = conn.prepareStatement(sql);
        // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为 设置的参数值
        ps.setString(1,  "riemann");
        // 向数据库发出sql执行查询,查询出结果集
        rs = ps.executeQuery();
        // 遍历结果集
        while (rs.next()) {
            int id = rs.getInt("id");
            String username = rs.getString("username");
            // 封装User
            user.setId(id);
            user.setUsername(username);
        }
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 释放资源
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# JDBC问题总结:

原始jdbc开发存在的问题如下:

1、 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。

2、 Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变

java代码。

3、 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便

# 1.2 问题解决思路

1使用数据库连接池初始化连接资源

2将sql语句抽取到xml配置文件中

3使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

# 1.3 自定义框架设计

# 使用端:

提供核心配置文件:

sqlMapConfig.xml : 存放数据源信息,引入mapper.xml

Mapper.xml : sql语句的配置文件信息

# 框架端:

# 1.读取配置文件

读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储

(1)Configuration : 存放数据库基本信息、Map<唯一标识,Mapper> 唯一标识:namespace + "."

+ id

(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型

# 2.解析配置文件

创建sqlSessionFactoryBuilder类:

方法:sqlSessionFactory build():

第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中

第二:创建SqlSessionFactory的实现类DefaultSqlSession

# 3.创建SqlSessionFactory:

方法:openSession() : 获取sqlSession接口的实现类实例对象

# 4.创建sqlSession接口及实现类:主要封装crud方法

方法:selectList(String statementId,Object param):查询所有

selectOne(String statementId,Object param):查询单个

具体实现:封装JDBC完成对数据库表的查询操作

# 涉及到的设计模式:

Builder构建者设计模式、工厂模式、代理模式

# 1.4 自定义框架实现

在使用端项目中创建配置配置文件

# 1、创建SqlMapConfig.xml

<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///mybatis"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
    <!--存放mapper.xml的全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>
1
2
3
4
5
6
7
8
9
10
11

# 2、创建UserMapper.xml

<mapper namespace="User">
  <select id="selectOne" paramterType="com.lagou.pojo.User" resultType="com.lagou.pojo.User">
    select * from user where id = #{id} and username =#{username}
  </select>
  <select id="selectList" resultType="com.lagou.pojo.User">
    select * from user
  </select>
</mapper>
1
2
3
4
5
6
7
8

# 3、User对象

@Data
public class User {
    private Integer id;
    private String username;
}
1
2
3
4
5

# 4、在父工程中引入依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>28.2-jre</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.9</version>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 5、在子工程中mybatis-persistence引入依赖

再创建一个Maven子工程并且导入需要用到的依赖坐标

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</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.17</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>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 6、完成框架端中的读取配置文件

# (1)、编写Configuration类

@Data
public class Configuration {
    private DataSource dataSource;
    /**
     * key:statementId
     * value:封装好的MappedStatement
     */
    Map<String, MappedStatement> mappedStatementMap = Maps.newHashMap();
}
1
2
3
4
5
6
7
8
9

# (2)、编写MappedStatement类

@Data
public class MappedStatement {
    // id标识
    private String id;
    // 返回值类型
    private String resultType;
    // 参数值类型
    private String parameterType;
    // sql语句
    private String sql;
}
1
2
3
4
5
6
7
8
9
10
11

# 7、完成框架端中的解析配置文件

# (1)、编写Resources类,读取客户端的xml文

public class Resources {
    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。
     * @param path 文件路径
     * @return     字节流
     */
    public static InputStream getResourceAsStream(String path) {
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}
1
2
3
4
5
6
7
8
9
10
11

# (2)、编写SqlSessionFactoryBuilder类

  • 使用dom4j解析配置文件,将解析出来的内容封装到Configuration
  • 创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        // 1.使用dom4j解析配置文件,将解析出来的内容封装到Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
        // 2.创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}
1
2
3
4
5
6
7
8
9
10

# (3)、编写XMLConfigBuilder类

public class XMLConfigBuilder {
    private Configuration configuration;
    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }
    /**
     * 该方法就是使用dom4j对配置文件进行解析,封装成Configuration对象
     * @param in 字节输入流
     * @return   Configuration
     */
    public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(in);
        // <configuration>
        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);
        }
        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解析:拿到路径--字节输入流--dom4j进行解析
        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);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# (4)、编写XMLMapperBuilder类

public class XMLMapperBuilder {
	private Configuration configuration;
	public XMLMapperBuilder( Configuration configuration )
	{
		this.configuration = configuration;
	}


	public void parse( InputStream inputStream ) throws DocumentException,
	ClassNotFoundException
	{
		Document	document	= new SAXReader().read( inputStream );
		Element		rootElement	= document.getRootElement();
		String		namespace	= rootElement.attributeValue( "namespace" );
		List<Element>	select		= rootElement.selectNodes( "select" );
		for ( Element element : select )                                                /* id的值 */
		{
			String	id		= element.attributeValue( "id" );
			String	paramterType	= element.attributeValue( "paramterType" );
			String	resultType	= element.attributeValue( "resultType" );       /* 输入参数 */
			class
			Class<?> paramterTypeClass = getClassType( paramterType );
			/* 返回结果class */
			Class<?> resultTypeClass = getClassType( resultType );
			/* statementId */
			String key = namespace + "." + id;
			/* sql语句 */
			String textTrim = element.getTextTrim();
			/* 封装 mappedStatement */
			MappedStatement mappedStatement = new MappedStatement();
			mappedStatement.setId( id );
			mappedStatement.setParamterType( paramterTypeClass );
			mappedStatement.setResultType( resultTypeClass );
			mappedStatement.setSql( textTrim );
			/* 填充 configuration */
			configuration.getMappedStatementMap().put( key, mappedStatement );
			private Class<?> getClassType( String paramterType ) throws
			ClassNotFoundException
			{
				Class<?> aClass = Class.forName( paramterType );
				return(aClass);
			}
		}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# (5)、SqlSessionFactory接口以及DefaultSqlSessionFactory实现类

public interface SqlSessionFactory {
    SqlSession openSession();
}
1
2
3
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration configuration;
    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}
1
2
3
4
5
6
7
8
9
10

# (6)、SqlSession接口以及DefaultSqlSession实现类

public interface SqlSession {
    /**
     * 查询所有
     * @param statementId sql唯一id
     * @param params      sql有可能十四模糊查询,传可变参数
     * @param <E>         泛型
     * @return            List集合
     */
    <E> List<E> selectList(String statementId, Object... params) throws Exception;
    /**
     * 根据条件查询单个
     * @param statementId sql唯一id
     * @param params      sql有可能十四模糊查询,传可变参数
     * @param <T>         泛型
     * @return            某一对象
     */
    <T> T selectOne(String statementId, Object... params) throws Exception;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }
    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        // 将要去完成对SimpleExecutor里的query方法的调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }
    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者结果过多!");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# (7)、Executor接口以及SimpleExecutor实现类

public interface Executor {
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, Exception;
}
1
2
3

SimpleExecutor

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1.注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.获取sql语句 : select * from user where id = #{id} and username = #{username}
        //   转换sql语句 : select * from user where id = ? and username = ?
        //   转换的过程中 : 还要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 4.设置参数
        //   获取到了参数的全路径
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();
            // 反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            // 暴力访问,防止访问的字段是private修饰
            declaredField.setAccessible(true);
            Object obj = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, obj);
        }
        // 5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        Object o = resultTypeClass.newInstance();
        List<Object> objects = Lists.newArrayList();
        // 6.封装返回结果集
        while (resultSet.next()) {
            // 元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);
                // 使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }
    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType != null) {
            Class<?> clazz = Class.forName(parameterType);
            return clazz;
        }
        return null;
    }
    /**
     * 完成对#{}的解析工作:
     * 1.将#{}使用?进行代替
     * 2.解析出#{}里面的值进行存储
     * @param sql 原生sql
     * @return    解析后的sql
     */
    private BoundSql getBoundSql(String sql) {
        // 1.标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 2.解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        // 3.#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 1.5 自定义框架优化

通过上述我们的自定义框架,我们解决了JDBC操作数据库带来的一些问题:例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的自定义框架代码,有没有什么问题?

问题如下:

  • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调用sqlsession方法,关闭 sqlsession)
  • dao的实现类中存在硬编码,调用sqlsession的方法时,参数statement的id硬编码

# 解决思路:

:使用代理模式来创建接口的代理对象

image

 @Test
   public void test2() throws Exception {
       InputStream resourceAsSteam = Resources.getResourceAsSteam(path:"sqlMapConfig.xml")
       SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam);
       SqlSession sqlSession = build.openSession();
       User user = new User();
       user.setld(l);
       user.setUsername("tom");
       //代理对象
       UserMapper userMapper = sqlSession.getMappper(UserMapper.class);
       User userl = userMapper.selectOne(user);
       System・out.println(userl);
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在sqlSession中添加方法

/**
 * 为Dao层接口生成代理实现类
 * @param mapperClass 字节码
 * @param <T>         泛型
 * @return            某一对象
 */
<T> T getMapper(Class<?> mapperClass) throws Exception;
1
2
3
4
5
6
7

DefaultSqlSession 实现类

@Override
public <T> T getMapper(Class<?> mapperClass) throws Exception {
    // 使用JDK动态代理来为Dao层接口生成代理对象,并返回。
    Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
             * 底层都还是去执行JDBC代码
             * 根据不同情况来调用findAll或者findByCondition方法
             * 准备参数:
             * 1.statementId: sql语句的唯一标识 nnamespace.id = 接口全限定名.方法名
             */
            // 方法名
            String methodNme = method.getName();
            String className = method.getDeclaringClass().getName();
            String   statementId  = className + "." + methodNme;
            // 准备参数 2.params:args
            // 获取被调用方法的返回值类型
            Type genericReturnType = method.getGenericReturnType();
            // 判断是否进行了泛型类型参数化
            if (genericReturnType instanceof ParameterizedType) {
                List<Object> objects = selectList(statementId, args);
                return objects;
            }
            return selectOne(statementId, args);
        }
    });
    return (T) proxyInstance;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

代理模式的过程如下图:

image

invoke()方法中的三个参数:

  • Object proxy 当前代理对象的引用
  • Method method 当前被调用方法的的引用
  • Object[] args 传递的参数

最后我们来测试一下优化后的结果:

public class PersistenceTest {
    @Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 调用
        User user = new User();
        user.setId(1);
        user.setUsername("riemann");
//      User user2 = sqlSession.selectOne("user.selectOne", user);
//      System.out.println(user2);
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        
        User user2 = userDao.findByCondition(user);
        System.out.println(user2);
        
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

日志打印如下:

User(id=1, username=riemann)
1

单个对象的OK,我们再来测一下多个的对象的。

List<User> all = userDao.findAll();
System.out.println(all);
1
2

日志打印如下:

[User(id=3, username=edgar), User(id=3, username=edgar), User(id=3, username=edgar)]
1

什么鬼?多个对象的结果输出显示都是数据库中最后的那条记录。

分析问题:

断点跟踪发现,原来是SimpleExecutor这个类的query方法中有点小问题:

image

解决完上面以后,测试结果如下:

结果正确!!!这下我们就优化好了使用代理模式来生成dao层接口的代理对象,解决了dao的实现类中存在重复代码以及dao的实现类存在硬编码。

# 1.6、代码仓库

https://github.com/riemannChow/perseverance/tree/master/handwriting-framework/mybatis

上次更新: 2025/04/03, 11:07:08
Spring Data JPA
Mybatis相关概念

← Spring Data JPA Mybatis相关概念→

最近更新
01
tailwindcss
03-26
02
PaddleSpeech
02-18
03
whisper
02-18
更多文章>
Theme by Vdoing | Copyright © 2019-2025 跨境互联网 | 豫ICP备14016603号-5 | 豫公网安备41090002410995号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式