手撕代码带你领略Spring如何集成Mybatis

news2024/9/20 5:46:29

引言

有关于Spring整合Mybatis其实一直是一个很具有典型代表性的Spring实际应用,今天就带着大家由浅入深手撸一遍整合的代码

手撕代码

准备工作

首先准备两个Mapper作为今天演示的操作对象

import org.apache.ibatis.annotations.Select;

public interface UserMapper {

	@Select("select username from user where id = 1")
	String selectById();
}
import org.apache.ibatis.annotations.Select;

public interface OrderMapper {

	@Select("select 'user_login_platfrom")
	void selectById();
}

然后是UserService

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserService implements BeanNameAware,InitializingBean{

	@Autowired
	private UserMapper userMapper;

	public void test(){
		System.out.println(userMapper.selectById());
	}
}

紧接着定义Spring配置类

import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;

@ComponentScan("com.zzy")
@EnableScheduling
public class AppConfig {

}

最后,新建一个测试类,创建Spring容器并启动

import com.zzy.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(AppConfig.class);
		context.refresh();
		UserService userService = (UserService)context.getBean("userService");
		userService.test()
	}
}

至此,所有的业务类已经创建结束了,这也符合我们日常使用Mybatis的使用,在service层注入mapper的Bean,并执行mapper中的方法执行最终sql

现在摆在我们面前最大的问题就是如何将UserMapper这个接口(interface)注入到userSerivce中,众所周知,Spring在生成BeanDefinition会过滤掉接口类

思路1:FactoryBean

在Spring中,给我们提供了一个FactoryBean的接口类,通过实现FactoryBean并改写getObject()及getObjectType()方法可以返回任意类型的实例,代码如下:

import com.zzy.mapper.UserMapper;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Component
public class UserMapperFactoryBean implements FactoryBean {
	@Override
	public Object getObject() throws Exception {
		Object proxyInstance = Proxy.newProxyInstance(UserMapperFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println(method.getName());
				// todo完成相关数据库操作
				return null;
			}
		});
		return proxyInstance;
	}

	@Override
	public Class<?> getObjectType() {
		return UserMapper.class;
	}
}

在getObject中使用了JDK的动态代理,生成了代理对象进行返回,并且再代理类中加入了我们需要的逻辑(即执行数据库操作),这里因为不是最终方案,因此就不写完整了。

这种方式实现起来比较简单,比容易理解,不过不能作为mybatis这种组件实现的方式,因为丧失了扩展性,试想一下项目中会有很多的Mapper,那总不能每一个Mapper都去定义一个相应的BeanFactory吧,所以这就引出了下面一种方案

思路2:可扩展的FactoryBean

我们是否可以只是用一个FactoryBean,然后将类型作为入参实现呢,当然也是可以的,可以利用属性+构造方法的方式实现,代码如下:

import org.springframework.beans.factory.FactoryBean;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MapperFactoryBean implements FactoryBean {

	private Class mapperInterface;

	public MapperFactoryBean(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		Object proxy = Proxy.newProxyInstance(MapperFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				System.out.println("method = " + method.getName());
				// todo 执行数据库操作
				return null;
			}
		});
		return proxy;
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}
}

注意此处的MapperFactoryBean没有Component注解,因为我们需要多个Bean,因此需要去手动注册BeanDefinition,返回我们的Test类,修改main方法,代码如下:

public class Test {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(AppConfig.class);

		AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(MapperFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		context.registerBeanDefinition("userMapper",beanDefinition);


		context.refresh();
		UserService userService = (UserService)context.getBean("userService");
		userService.test();
		
	}
}

我们利用了AnnotationConfigApplicationContext注册了一个名为userMapper类型为MapperFactoryBean的BD

当然也可以使用BeanDefinitionRegistryPostProcessor来注册BD,代码如下:

@Component
public class BeanDefinitionRegister implements BeanDefinitionRegistryPostProcessor {
	

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

	}

	@Override
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
		AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
		beanDefinition.setBeanClass(MapperFactoryBean.class);
		beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
		registry.registerBeanDefinition("userMapper",beanDefinition);
	}
}

其实代码写到这里,大家肯定又有疑惑了,因为我们还是需要去手动注册,虽然不需要去创建很多个FactoryBean,但是仍然需要去手动创建很多个BD

那么有没有一种方法能够自动将所有的Mapper都解析出来呢?

了解Spring的第一时间肯定会想到 - 扫描

思路3:扫描

既然要扫描,那肯定需要确定扫描的路径,mybatis中提供了MapperScan接口,那我们自己写一个ZzyMapperScan接口吧

import org.springframework.context.annotation.Import;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZzyImportBeanDefinitionRegistrar.class)
public @interface ZzyMapperScan {

	String value();

}

在AppConfig中指定扫描路径:

@ComponentScan("com.zzy")
@ZzyMapperScan("com.zzy.mapper")
public class AppConfig {
}

在MapperScan中我们import了一个类ZzyImportBeanDefinitionRegistrar,具体代码如下:

public class ZzyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ZzyMapperScan.class.getName());
		String path = (String)annotationAttributes.get("value");

		System.out.println("path = " + path);

		ZzyClassPathBeanDefintionScan zzyClassPathBeanDefintionScan = new ZzyClassPathBeanDefintionScan(registry);
		zzyClassPathBeanDefintionScan.addIncludeFilter(new TypeFilter() {
			@Override
			public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
				return true;
			}
		});
 		zzyClassPathBeanDefintionScan.scan(path);
}

ZzyImportBeanDefinitionRegistrar实现了ImportBeanDefintionRegsitrar,并重写了registerBeanDefinitions方法,该方法在import类导入时会执行,而方法其实一共就做了两件事:

  1. 首先获取了导入类的ZzyMapperScan注解,并取得了扫描路径
  2. 利用自定义的扫描器对包进行扫描

自定义扫描器继承于ClassPathBeanDefinitionScanner,代码如下

public class ZzyClassPathBeanDefintionScan extends ClassPathBeanDefinitionScanner {
	public ZzyClassPathBeanDefintionScan(BeanDefinitionRegistry registry) {
		super(registry);
	}

	@Override
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
		for(BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders){
			BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
			beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
			beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
		}
		return beanDefinitionHolders;
	}

	@Override
	protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
		return beanDefinition.getMetadata().isInterface();
	}
}

其中:

  • 实现isCandidateComponent接口是为了让扫描器只去扫描接口类
  • 实现doScan接口可以完成自定义BD的注入

在doScan逻辑中,我们将当前类类型(接口)作为构造方法入参传入,而将BD的bean类型改为了上文所写的MapperFactoryBean

至此,我们完成了基于扫描的可扩展的Mapper注入,接下去就是执行接口注解定义的sql了,我们需要进行一些简单的改造,首先是MapperFactoryBean,我们现在直接使用Mybatis生成的代理对象:

public class MapperFactoryBean implements FactoryBean {

	private Class mapperInterface;

	private SqlSession sqlSession;

	@Autowired
	public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
		sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
		this.sqlSession = sqlSessionFactory.openSession();
	}

	public MapperFactoryBean(Class mapperInterface) {
		this.mapperInterface = mapperInterface;
	}

	@Override
	public Object getObject() throws Exception {
		return sqlSession.getMapper(mapperInterface);
	}

	@Override
	public Class<?> getObjectType() {
		return mapperInterface;
	}
}

这里我们利用到了mybatis的sqlSession,而想要注入sqlSession,我们可以利用构造方法自动注入sqlSessionFactory,并且使用openSession的方法返回sqlSession实例

而sqlSessionFactory就需要我们去手动创建该Bean了,我们可以直接写到AppConfig中:

@ComponentScan("com.zzy")
@ZzyMapperScan("com.zzy.mapper")
public class AppConfig {

	@Bean
	public SqlSessionFactory sqlSessionFactory() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		return sqlSessionFactory;
	}
}

mybaits.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">
      <!-- 使用jdbc事务管理 -->
      <transactionManager type="JDBC"/>
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url"
                  value="jdbc:mysql://{ip}:{port}/{datasource}?characterEncoding=utf-8&amp;useSSL=false"/>
        <property name="username" value="{username}"/>
        <property name="password" value="{password}"/>
      </dataSource>
    </environment>
  </environments>


</configuration>

最后运行,控制台输出:
在这里插入图片描述

总结

至此手写Spring-mybtias的过程就结束了,我们经过了一步步的推导过程,十分直观详细的向大家展示了其中的核心逻辑,当然mybatis内部实现的代码写的也是十分好的,例如生成代理对象执行sql等,以后有机会的话也可以带大家手撕一下代码~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/536720.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态规划——第N个泰波那契数

题目链接 leetcode在线oj题——第N个泰波那契数 题目描述 泰波那契序列 Tn 定义如下&#xff1a; T0 0, T1 1, T2 1, 且在 n > 0 的条件下 Tn3 Tn Tn1 Tn2 给你整数 n&#xff0c;请返回第 n 个泰波那契数 Tn 的值。 题目示例 示例1 输入&#xff1a;n 4 输出…

❤ cannot read properties of null(reading appendChild)解决办法

❤ 操作元素报&#xff1a;cannot read properties of null(reading appendChild)解决办法 1、场景&#xff1a; 写的一个js渲染&#xff0c;但是出了个小问题&#xff0c;cannot read properties of null(reading appendChild)报错。 <div id"divps" class&qu…

ASEMI代理Infineon英飞凌IPB64N25S3-20原厂MOS管

编辑-Z IPB64N25S3-20参数描述&#xff1a; 型号&#xff1a;IPB64N25S3-20 持续漏极电流&#xff1a;64A 脉冲漏极电流&#xff1a;256A 雪崩电流&#xff0c;单脉冲&#xff1a;27A 栅极-源极电压&#xff1a;20V 功率耗散&#xff1a;300W 操作和储存温度&#xff1…

【Linux】有名管道实现简单版聊天功能

目录 有名管道实现简单版聊天功能 橙色 有名管道实现简单版聊天功能 创建两个.c文件&#xff0c;一个是chata.c&#xff0c;一个是chatb.c chata.c文件内容如下&#xff1a; #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include &l…

Linux-基础IO

一、C语言文件IO 1、C库函数介绍 打关文件fopen/fclose&#xff1a; FILE * fopen(const char* filename, const char* mode); int fclose (FILE* stream );文件打开方式&#xff1a; 读写函数fread/fwrite&#xff1a; size_t fread( void *buffer, size_t size, size_t cou…

刷题day66:目标和

题意描述&#xff1a; 给你一个整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 &#xff0c;在 1 之前添…

Kibana 的安装

1. 简介 Kibana 是一个开源的分析与可视化平台&#xff0c;可以用 Kibana 搜索、查看存放在 Elasticsearch 中的数据&#xff0c;就跟谷歌的 elasticsearch head 插件类似&#xff0c;但 Kibana 与 Elasticsearch 的交互方式是各种不同的图表、表格、地图等&#xff0c;直观的…

物联网和边缘计算:如何将数据处理和决策推向设备边缘

第一章&#xff1a;引言 当我们谈论物联网&#xff08;IoT&#xff09;时&#xff0c;我们通常指的是将各种设备连接到互联网&#xff0c;并通过数据交换来实现智能化的网络。然而&#xff0c;传统的物联网模型通常涉及将数据发送到云端进行处理和分析。然而&#xff0c;随着技…

5月第2周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年5月8日-5月14日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站…

【js】原生input框及下拉框数据联动( 列如: 输入框输入 3,下拉框里的选项值则为:1,2,3)

需求 输入框输入 数字 下拉框要显示对应的数字 列如: 输入框输入 13 下拉框里的选项值则为&#xff1a;1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;7&#xff0c;8&#xff0c;9&#xff0c;10&#xff0c;11&#xff0c;12&#xff0c;…

【数据结构 -- C语言】 双向带头循环链表的实现

目录 1、双向带头循环链表的介绍 2、双向带头循环链表的接口 3、接口实现 3.1 开辟结点 3.2 创建返回链表的头结点 3.3 判断链表是否为空 3.4 打印 3.5 双向链表查找 3.6 双向链表在pos的前面进行插入 3.6.1 头插 3.6.2 尾插 3.6.3 更新头插、尾插写法 3.7 双向链…

springBoot中使用redis实现分布式锁实例demo

首先 RedisLockUtils工具类 package com.example.demo.utils;import org.junit.platform.commons.util.StringUtils; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.red…

【最短路径 本质模板】【最短路径问题 本质 Dijkstra 和 spfa】收藏本篇,遇到最短路问题,来看看模板和思路

也就是走过的节点&#xff0c;还可以再走 但是dij走过的不能再走了 这是本章的精髓&#xff0c;大家往下看 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章…

关于deeplabv3的输出维度与类别预测的对应关系

这里用到的代码是&#xff1a;DeepLabV3源码讲解(Pytorch)_哔哩哔哩_bilibili 背景说明&#xff1a;自己做了一个数据集&#xff0c;已经训练完毕&#xff0c;一共7类零食&#xff0c;加背景算8类。 前面的代码省略了model.eval() # 进入验证模式with torch.no_grad():# init …

WordPress 技巧:WordPress设置媒体文件的默认链接本身方法

当我们在 WordPress 后台上传文件&#xff0c;并添加到内容中得时候&#xff0c;默认媒体文件是链接到文件本身&#xff0c;这个是很烦人的&#xff0c;有时候我们只是想在文章内容中插入一张图片&#xff0c;而不想给这张图片加上任何链接&#xff0c;我们怎么做呢&#xff1f…

动态主机配置协议 DHCP

文章目录 1 概述2 DHCP2.1 工作原理2.2 报文类型 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-ZESmHWHRC6kYroqm {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ZESmHWHRC6kYroqm .error-icon{fill:#552222;}#merm…

Java EE 进阶--多线程(二)

目录 一、JUC(java.util.concurrent) 的常见类 1.1 信号量 Semaphore 1.2 CountDownLatch 1.3 CyclicBarrier -循环栅栏 二、线程安全的集合类 2.1 多线程环境使用 ArrayList 2.2 多线程环境使用队列 2.3 多线程环境使用哈希表 三、死锁 3.1 死锁是什么 3.2 如何避免死…

Linux文本三剑客之sed

Linux文本三剑客之sed 一、sed简介二、工作流程三、sed的常见用法1、常见的sed命令选项2、常见的操作3、基本用法实例3.1 sed查询3.2 sed删除3.3 sed替换sed ‘s/旧字符/新字符/’ &#xff1a;替换每行匹配到的第一个旧字符3.4 sed插入 一、sed简介 sed&#xff08;Stream ED…

Chrome浏览器竟然也可以用ChatGPT了!

最近这段时间想必 和我一样&#xff0c;都被chatGPT刷屏了。 在看到网上给出的一系列chatGPT回答问题的例子和自己亲自体验之后&#xff0c;的确发现它效果非常令人惊艳。 chatGPT的火热程度在开源社区也有很明显的体现&#xff0c;刚推出不久&#xff0c;围绕chatGPT的开源项…