《手写Spring渐进式源码实践》实践笔记 (第二十一章 将ORM框架整合到Spring容器中)

news/2024/12/3 8:36:36/

文章目录

  • 第二十一章 ORM框架整合Spring
    • 背景
    • 目标
    • 设计
    • 实现
      • 代码结构
      • 类图
      • 实现步骤
    • 测试
      • 事先准备
      • 属性配置文件
      • 测试用例
      • 测试结果:
    • 总结

第二十一章 ORM框架整合Spring

背景

MyBatis-Spring 能够实现 MyBatis 与 Spring 框架的无缝集成。它使得 MyBatis 能够参与 Spring 的事务管理,自动创建 Mapper 和 SqlSession 并将其注册为 Spring 容器中的 Bean,同时将 MyBatis 的异常转换为 Spring 的 DataAccessException。这样,应用程序的代码就可以不依赖于 MyBatis、Spring 或 MyBatis-Spring,实现解耦。

目标

基于当前实现的 Spring 框架,和简易的mybatis orm框架,实现两者的结合,完成mybatis-spring的功能封装。

设计

MyBatis-Spring 将 MyBatis 的功能与 Spring 框架的依赖注入和事务管理无缝集成,通过接口和抽象层进行交互,通过自动配置功能,可以自动创建 SqlSession 和 Mapper 接口的实现,并将其注册为 Spring Bean,从而简化了配置过程。整体设计结构如下图:

image-20241202112523377

实现

代码结构

image-20241120183420684

源码实现:https://github.com/swg209/spring-study/tree/main/step21-spring-mybatis

类图

image-20241120183448881

在整个类图中,通过在spring.xml配置自动注册、加载MapperScannerConfigure Bean,调用扫描方法,自动将指定文件路径下的mapper进行解析成MapperFactoryBean,注册对应的接口方法和sqlSessionFactory。

实现步骤

  1. step21的代码module的最外层pom 引入step-20 orm简易框架实现模块

     <dependency><groupId>cn.suwg.springframework</groupId><artifactId>step20-simple-orm</artifactId><version>1.0-SNAPSHOT</version></dependency>
    
  2. 定义SqlSessionFactoryBean

    定义SqlSessionFactoryBean, 实现了 FactoryBean, InitializingBean,处理mybatis核心流程类加载处理,通过SqlSessionFactoryBuilder使用ORM框架

    java">public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {// 资源路径private String resource;private SqlSessionFactory sqlSessionFactory;@Overridepublic SqlSessionFactory getObject() throws Exception {return sqlSessionFactory;}@Overridepublic Class<?> getObjectType() {return sqlSessionFactory.getClass();}@Overridepublic boolean isSingleton() {return true;}@Overridepublic void afterPropertiesSet() throws BeansException {DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();Resource resource = defaultResourceLoader.getResource(this.resource);try (InputStream inputStream = resource.getInputStream()) {//根据mapper文件,创建对应的sql会话工厂.this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);} catch (Exception e) {e.printStackTrace();}}public void setResource(String resource) {this.resource = resource;}
    }
    
  3. 定义MapperFactoryBean,完成代理类的实现, 完成对 SQL 的所有操作的分发处理。

    • T getObject(),中是一个 Java 代理类的实现,这个代理类对象会被挂到你的注入中。真正调用方法内容时会执行到代理类的实现部分,对应测试例子中,打印日志“你被代理了,执行SQL操作!

    • InvocationHandler,代理类的实现,主要开启SqlSession,并通过固定key执行结果,查询到结果信息会反射操作成对象类,这里就是我们实现的 ORM 中间件负责的事情了。

      java">public class MapperFactoryBean<T> implements FactoryBean<T> {private Class<T> mapperInterface;private SqlSessionFactory sqlSessionFactory;public MapperFactoryBean() {}public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {this.mapperInterface = mapperInterface;this.sqlSessionFactory = sqlSessionFactory;}@Overridepublic T getObject() throws Exception {InvocationHandler handler = ((proxy, method, args) -> {// 排除 Object 方法;toString、hashCodeif (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);}try {System.out.println("你被代理了,执行SQL操作!" + method.getName());return sqlSessionFactory.openSession().selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);} catch (Exception e) {e.printStackTrace();}return method.getReturnType().newInstance();});return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}public Class<T> getMapperInterface() {return mapperInterface;}public void setMapperInterface(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}public SqlSessionFactory getSqlSessionFactory() {return sqlSessionFactory;}public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;}
      }
  4. 定义MapperScannerConfigure,完成注册对象的扫描, 代理类的Bean注册.

    • 把 DAO 接口全部扫描出来,完成代理和注册到 Spring Bean 容器。
    • 表、类的扫描注册,classpath:org/suwg/demo/dao/**/.class,解析class文件获取资源信息; Set<Class<?>> classes = ClassScanner.scanPackage(basePackage);
    • 遍历 Resource,获取class 信息,用于注册 bean。
    • 最后执行注册 registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
java">public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {private String basePackage;private SqlSessionFactory sqlSessionFactory;@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {try {// 类的扫描Set<Class<?>> classes = ClassScanner.scanPackage(basePackage);for (Class<?> clazz : classes) {// Bean 对象定义BeanDefinition beanDefinition = new BeanDefinition(clazz);PropertyValues propertyValues = new PropertyValues();propertyValues.addPropertyValue(new PropertyValue("mapperInterface", clazz));propertyValues.addPropertyValue(new PropertyValue("sqlSessionFactory", sqlSessionFactory));beanDefinition.setPropertyValues(propertyValues);beanDefinition.setBeanClass(MapperFactoryBean.class);// Bean 对象注册registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);}} catch (Exception e) {e.printStackTrace();}}public void setBasePackage(String basePackage) {this.basePackage = basePackage;}public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;}
}

测试

事先准备

步骤1: 新建my_user表,往表里插入几条数据.

-- mybatis.my_user definitionCREATE TABLE `my_user` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增ID',`user_id` varchar(9) DEFAULT NULL COMMENT '用户ID',`user_head` varchar(16) DEFAULT NULL COMMENT '用户头像',`create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',`update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间',`user_name` varchar(64) DEFAULT NULL COMMENT '用户名',`user_password` varchar(64) DEFAULT NULL COMMENT '用户密码',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;INSERT INTO mybatis.my_user (user_id, user_head, create_time, update_time, user_name, user_password) VALUES
('1', '头像', '2024-03-25 18:00:12', '2024-03-25 18:00:12', '小苏', 's123asd'),
('2', '头像', '2024-03-25 18:00:12', '2024-03-25 18:00:12', '小苏', 's123asd');

步骤2: 创建用户类User ,Dao接口类 IUserDao

java">public class User {private Long id;private String userId;          // 用户IDprivate String userName;    // 昵称private String userHead;        // 头像private String userPassword;    // 密码private Date createTime;        // 创建时间private Date updateTime;        // 更新时间public User() {}public User(String userName) {this.userName = userName;}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUserName() {return userName;}public void setUserName(String userNickName) {this.userName = userNickName;}public String getUserHead() {return userHead;}public void setUserHead(String userHead) {this.userHead = userHead;}public String getUserPassword() {return userPassword;}public void setUserPassword(String userPassword) {this.userPassword = userPassword;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime = updateTime;}}

IUserDao

java">public interface IUserDao {User queryUserInfoById(Long id);List<User> queryUserList(User user);}

属性配置文件

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="sqlSessionFactory" class="cn.suwg.springframework.mybatis.SqlSessionFactoryBean"><property name="resource" value="classpath:mybatis-config-datasource.xml"/></bean><bean class="cn.suwg.springframework.mybatis.MapperScannerConfigurer"><!-- 注入sqlSessionFactory --><property name="sqlSessionFactory" ref="sqlSessionFactory"/><!-- 给出需要扫描Dao接口包 --><property name="basePackage" value="cn.suwg.springframework.mybatis.test.dao"/></bean></beans>

数据库配置 mybatis-config-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/></mappers></configuration>

User_Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.suwg.springframework.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long"resultType="cn.suwg.springframework.mybatis.test.po.User">SELECT id, user_id, user_name, user_head, user_password, create_timeFROM my_userwhere id = #{id}</select><select id="queryUserList" parameterType="cn.suwg.springframework.mybatis.test.po.User"resultType="cn.suwg.springframework.mybatis.test.po.User">SELECT id, user_id, user_name, user_head, user_password, create_time, update_timeFROM my_userwhere user_name = #{userName}</select></mapper>

测试用例

java">public class ApiTest {@Testpublic void testClassPathXmlApplicationContext() {BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring.xml");IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);User user = userDao.queryUserInfoById(1L);System.out.println("测试结果:" + JSON.toJSONString(user));}
}

测试结果:

image-20241202193159041

  • 从测试结果中可以看到,可以正常调用orm框架,与数据库交互,查询表数据功能。

总结

  • 本章节成功实现了MyBatis与Spring的整合,提供了一个简化配置、自动管理事务和异常处理的框架,使得开发者可以更专注于业务逻辑的实现。通过自动配置和代理机制,MyBatis-Spring框架简化了MyBatis与Spring的集成过程,提高了开发效率和代码的可维护性。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring


http://www.ppmy.cn/news/1551956.html

相关文章

C++草原三剑客之一:继承

为王的诞生献上礼炮吧&#xff01; 目录 1 继承的概念及其定义 1.1 继承的概念 1.2 继承的定义 1.2.1 定义格式 1.2.2 继承方式以及继承基类成员访问方式的变化 1.3 继承类模板 2 基类和派生类之间的转换 3 继承中的作用域 3.1 隐藏规则 3.2 两道考察继承作用的相关…

Linux 安装scala

文章目录 Linux 安装scala下载环境变量配置 Linux 安装scala 前提linux需要已经安装好JDK&#xff08;JDK安装&#xff09;&#xff0c;Scala对JDK版本有明确的要求。通常&#xff0c;Scala的稳定版本要求JDK版本不低于1.8。例如&#xff0c;Scala 2.11.8和2.12.7版本都要求JD…

leetcode102:二叉树的层序遍历

给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示例 2&#xff1a; 输入&a…

Linux - 防火墙

八、防火墙 1、简介 防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定义好的规则来控制数据包的进出。 防火墙又可以分为硬件防火墙与软件防火墙。硬件防火墙是由厂商设计好的主机硬件&#xff0c;这台硬件防火墙 的操作系统主要以提供数据包数据的…

软件测试丨Web自动化测试用例录制与编写全攻略

Web自动化测试的功能简介 Web自动化测试主要是使用特定的工具或框架自动执行对Web应用程序进行的测试。通过模拟用户的操作&#xff0c;自动化测试能够验证应用程序的功能及性能。这一过程的大致流程是&#xff1a; 用例设计&#xff1a;明确测试目标、场景及所需功能。录制测…

服务器配环境

<适用Ubuntu 系统> if 系统默认python版本与本项目所需python版本不一致&#xff1a; 安装 pyenv 1.安装依赖包 sudo apt update sudo apt install -y \make \build-essential \libssl-dev \zlib1g-dev \libbz2-dev \libreadline-dev \libsqlite3-dev \wget \curl \l…

SSH远程命令实践:如何打包、压缩并传输服务器文件

大家好&#xff0c;今天我要分享的是如何使用SSH命令来远程打包、压缩服务器上的文件&#xff0c;并将其传输到本地或其他服务器。这对于需要在远程服务器上进行文件备份或迁移的场景非常有用。 以下是本文的主要内容&#xff1a; 一、命令详解 我们要执行的命令是&#xff…

YOLOv11融合FCMNet中的WCMF模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《FCMNet: Frequency-aware cross-modality attention networks for RGB-D salient object detection》 一、 模块介绍 论文链接&#xff1a;https://…