文章目录
- 第二十一章 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,从而简化了配置过程。整体设计结构如下图:
实现
代码结构
源码实现:https://github.com/swg209/spring-study/tree/main/step21-spring-mybatis
类图
在整个类图中,通过在spring.xml配置自动注册、加载MapperScannerConfigure Bean,调用扫描方法,自动将指定文件路径下的mapper进行解析成MapperFactoryBean,注册对应的接口方法和sqlSessionFactory。
实现步骤
-
step21的代码module的最外层pom 引入step-20 orm简易框架实现模块
<dependency><groupId>cn.suwg.springframework</groupId><artifactId>step20-simple-orm</artifactId><version>1.0-SNAPSHOT</version></dependency>
-
定义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;} }
-
定义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;} }
-
-
定义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));}
}
测试结果:
- 从测试结果中可以看到,可以正常调用orm框架,与数据库交互,查询表数据功能。
总结
- 本章节成功实现了MyBatis与Spring的整合,提供了一个简化配置、自动管理事务和异常处理的框架,使得开发者可以更专注于业务逻辑的实现。通过自动配置和代理机制,MyBatis-Spring框架简化了MyBatis与Spring的集成过程,提高了开发效率和代码的可维护性。
参考书籍:《手写Spring渐进式源码实践》
书籍源代码:https://github.com/fuzhengwei/small-spring