【源码】Spring Data JPA原理解析之事务注册原理

news2024/11/23 7:50:07

 Spring Data JPA系列

1、SpringBoot集成JPA及基本使用

2、Spring Data JPA Criteria查询、部分字段查询

3、Spring Data JPA数据批量插入、批量更新真的用对了吗

4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作

5、Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

6、【源码】Spring Data JPA原理解析之Repository的自动注入(一)

7、【源码】Spring Data JPA原理解析之Repository的自动注入(二)

8、【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码

9、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)

10、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)

11、【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理

12、【源码】SpringBoot事务注册原理

13、【源码】Spring Data JPA原理解析之事务注册原理

14、【源码】Spring Data JPA原理解析之事务执行原理

前言

JPA是Java Persistence API的简称,中文名Java持久层API。JPA采用ORM对象关系映射,以Java面向对象的编程思想,在javax.persistence包下提供对实体对象的CRUD操作,将开发者从繁琐的JDBC和SQL代码中解脱出来。

在数据库的操作中,为了处理脏读、不可重复读、幻读等问题,需要通过事务来处理。本篇从源码的角度和大家一下分享一下Spring Data JPA中的Repository的事务注册原理。

Repository的事务注册

在Spring框架中,数据操作的事务是通过添加@Transaction注解来实现的。详见:

【源码】SpringBoot事务注册原理-CSDN博客

在Spring Data JPA中,实现事务也是只需要在对应的方法中添加@Transaction注解即可。下面从源码的角度来分析一下事务实现的原理。

【源码】Spring Data JPA原理解析之Repository的自动注入(二)-CSDN博客

在上面的博文中分享了Repository bean的创建。Respository的bean是一个通过ProxyFactory创建的动态代理对象。源码如下:

public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
    /**
	 * 返回给定接口的存储库实例,该实例由为自定义逻辑提供实现逻辑的实例支持。
	 */
	@SuppressWarnings({ "unchecked" })
	public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
 
		if (logger.isDebugEnabled()) {
			logger.debug(LogMessage.format("Initializing repository instance for %s…", repositoryInterface.getName()));
		}
 
		Assert.notNull(repositoryInterface, "Repository interface must not be null");
		Assert.notNull(fragments, "RepositoryFragments must not be null");
 
		ApplicationStartup applicationStartup = getStartup();
 
		StartupStep repositoryInit = onEvent(applicationStartup, "spring.data.repository.init", repositoryInterface);
 
		repositoryBaseClass.ifPresent(it -> repositoryInit.tag("baseClass", it.getName()));
 
		StartupStep repositoryMetadataStep = onEvent(applicationStartup, "spring.data.repository.metadata",
				repositoryInterface);
		// 获取repository元数据,包括Repository<T, ID>中的T类型、ID类型、接口类型(如GoodsRepository)等
		RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
		repositoryMetadataStep.end();
 
		StartupStep repositoryCompositionStep = onEvent(applicationStartup, "spring.data.repository.composition",
				repositoryInterface);
		repositoryCompositionStep.tag("fragment.count", String.valueOf(fragments.size()));
		// 获取
		RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
		// 获取Repository信息,getRepositoryInformation()返回一个RepositoryInformation对象。
		// 如子类JpaRepositoryFactory,指定baseClass为SimpleJpaRepository.class
		RepositoryInformation information = getRepositoryInformation(metadata, composition);
 
		repositoryCompositionStep.tag("fragments", () -> {
 
			StringBuilder fragmentsTag = new StringBuilder();
 
			for (RepositoryFragment<?> fragment : composition.getFragments()) {
 
				if (fragmentsTag.length() > 0) {
					fragmentsTag.append(";");
				}
 
				fragmentsTag.append(fragment.getSignatureContributor().getName());
				fragmentsTag.append(fragment.getImplementation().map(it -> ":" + it.getClass().getName()).orElse(""));
			}
 
			return fragmentsTag.toString();
		});
 
		repositoryCompositionStep.end();
 
		StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target",
				repositoryInterface);
		// 获取目标Repository对象,SimpleJpaRepository对象
		Object target = getTargetRepository(information);
 
		repositoryTargetStep.tag("target", target.getClass().getName());
		repositoryTargetStep.end();
 
		RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target));
		validate(information, compositionToUse);
 
		// Create proxy
		// 创建代理对象
		StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface);
		ProxyFactory result = new ProxyFactory();
		result.setTarget(target);
		// 代理对象实现的接口
		result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
 
		if (MethodInvocationValidator.supports(repositoryInterface)) {
			result.addAdvice(new MethodInvocationValidator());
		}
		// 添加界面
		result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
 
		if (!postProcessors.isEmpty()) {
			StartupStep repositoryPostprocessorsStep = onEvent(applicationStartup, "spring.data.repository.postprocessors",
					repositoryInterface);
			// 执行后置处理
			// CrudMethodMetadataPostProcessor
			// TransactionalRepositoryProxyPostProcessor
			// JpaRepositoryFactory构造方法中加入的内部处理器,
			// 添加SurroundingTransactionDetectorMethodInterceptor,记录是否处理状态
			// PersistenceExceptionTranslationRepositoryProxyPostProcessor
			// EventPublishingRepositoryProxyPostProcessor
			postProcessors.forEach(processor -> {
 
				StartupStep singlePostProcessor = onEvent(applicationStartup, "spring.data.repository.postprocessor",
						repositoryInterface);
				singlePostProcessor.tag("type", processor.getClass().getName());
				processor.postProcess(result, information);
				singlePostProcessor.end();
			});
			repositoryPostprocessorsStep.end();
		}
 
		if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
			// 添加DefaultMethodInvokingMethodInterceptor拦截器
			result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
		}
 
		Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
				evaluationContextProvider);
		// 添加QueryExecutorMethodInterceptor拦截器
		result.addAdvice(new QueryExecutorMethodInterceptor(information, getProjectionFactory(), queryLookupStrategy,
				namedQueries, queryPostProcessors, methodInvocationListeners));
		// 添加ImplementationMethodExecutionInterceptor拦截器
		result.addAdvice(
				new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));
 
		T repository = (T) result.getProxy(classLoader);
		repositoryProxyStep.end();
		repositoryInit.end();
 
		if (logger.isDebugEnabled()) {
			logger
					.debug(LogMessage.format("Finished creation of repository instance for %s.",
				repositoryInterface.getName()));
		}
 
		return repository;
	}
}

在生成代理对象之前,如果有后置处理器postProcessors,则执行后置处理器的postProcess()方法。在后置处理器中,有一个TransactionalRepositoryProxyPostProcessor。

JpaRepositoryFactoryBean

【源码】Spring Data JPA原理解析之Repository的自动注入(一)-CSDN博客

在上面的博文中分享了JpaRepositoriesAutoConfiguration的源码,在Spring Data JPA自动注入时,会自动注入JpaRepositoryConfigExtension,在JpaRepositoryConfigExtension中getRepositoryFactoryBeanClassName()方法,返回JpaRepositoryFactoryBean.class.getName(),该类最终会被装载到Spring IOC容器中。

JpaRepositoryFactoryBean初始化时,执行父父类RepositoryFactoryBeanSupport的afterPropertiesSet()方法,在该方法中,主要执行如下:

3.1)调用抽象方法createRepositoryFactory()创建Repository工厂对象,在TransactionalRepositoryFactoryBeanSupport中实现了该方法;

在createRepositoryFactory()方法中,创建了Repository工厂之后,添加了两个后置处理器,其中一个为TransactionalRepositoryProxyPostProcessor。

3.2)为3.1)中的Repository工厂对象设置查询查找策略、NamedQueries、后置处理器等;

3.3)调用3.1)中的Repository工厂对象,调用getRepository()创建代理的Repository对象;

详细可以看【源码】Spring Data JPA原理解析之Repository的自动注入(二)

TransactionalRepositoryFactoryBeanSupport

TransactionalRepositoryFactoryBeanSupport的源码如下:

package org.springframework.data.repository.core.support;

public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID>
		extends RepositoryFactoryBeanSupport<T, S, ID> implements BeanFactoryAware {

	private String transactionManagerName = TxUtils.DEFAULT_TRANSACTION_MANAGER;
	private @Nullable RepositoryProxyPostProcessor txPostProcessor;
	private @Nullable RepositoryProxyPostProcessor exceptionPostProcessor;
	private boolean enableDefaultTransactions = true;
	
	// 忽略其他

	@Override
	protected final RepositoryFactorySupport createRepositoryFactory() {
		// 调用抽象方法,创建一个RepositoryFactorySupport对象,JpaRepositoryFactory对象
		RepositoryFactorySupport factory = doCreateRepositoryFactory();
		// 添加两个后置处理器,分别为PersistenceExceptionTranslationRepositoryProxyPostProcessor
		// 和TransactionalRepositoryProxyPostProcessor。在setBeanFactory()方法中初始化
		RepositoryProxyPostProcessor exceptionPostProcessor = this.exceptionPostProcessor;

		if (exceptionPostProcessor != null) {
			factory.addRepositoryProxyPostProcessor(exceptionPostProcessor);
		}

		RepositoryProxyPostProcessor txPostProcessor = this.txPostProcessor;

		if (txPostProcessor != null) {
			factory.addRepositoryProxyPostProcessor(txPostProcessor);
		}

		return factory;
	}

	protected abstract RepositoryFactorySupport doCreateRepositoryFactory();

	public void setBeanFactory(BeanFactory beanFactory) {

		Assert.isInstanceOf(ListableBeanFactory.class, beanFactory);

		super.setBeanFactory(beanFactory);
		// 创建后置处理器
		ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
		this.txPostProcessor = new TransactionalRepositoryProxyPostProcessor(listableBeanFactory, transactionManagerName,
				enableDefaultTransactions);
		this.exceptionPostProcessor = new PersistenceExceptionTranslationRepositoryProxyPostProcessor(listableBeanFactory);
	}
}

1)TransactionalRepositoryFactoryBeanSupport实现了BeanFactoryAware,在初始化前,会执行setBeanFactory()方法,在该方法中,创建了两个后置处理器,分别为PersistenceExceptionTranslationRepositoryProxyPostProcessor和TransactionalRepositoryProxyPostProcessor;

2)在createRepositoryFactory()方法中,执行子类JpaRepositoryFactoryBean的doCreateRepositoryFactory()方法,创建JpaRepositoryFactory对象。并添加了1)中创建的两个后置处理器,其中一个为TransactionalRepositoryProxyPostProcessor;

TransactionalRepositoryProxyPostProcessor

在TransactionalRepositoryProxyPostProcessor处理器中,会向代理工厂添加TransactionInterceptor拦截器。

TransactionalRepositoryProxyPostProcessor的代码如下:

package org.springframework.data.repository.core.support;

class TransactionalRepositoryProxyPostProcessor implements RepositoryProxyPostProcessor {

	private final BeanFactory beanFactory;
	private final String transactionManagerName;
	private final boolean enableDefaultTransactions;

	public TransactionalRepositoryProxyPostProcessor(ListableBeanFactory beanFactory, String transactionManagerName,
			boolean enableDefaultTransaction) {

		Assert.notNull(beanFactory, "BeanFactory must not be null");
		Assert.notNull(transactionManagerName, "TransactionManagerName must not be null");

		this.beanFactory = beanFactory;
		this.transactionManagerName = transactionManagerName;
		this.enableDefaultTransactions = enableDefaultTransaction;
	}

	public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) {
		// 定义一个TransactionInterceptor对象
		TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
		// 设置RepositoryAnnotationTransactionAttributeSource
		transactionInterceptor.setTransactionAttributeSource(
				new RepositoryAnnotationTransactionAttributeSource(repositoryInformation, enableDefaultTransactions));
		transactionInterceptor.setTransactionManagerBeanName(transactionManagerName);
		transactionInterceptor.setBeanFactory(beanFactory);
		transactionInterceptor.afterPropertiesSet();
		// 添加TransactionInterceptor拦截器
		factory.addAdvice(transactionInterceptor);
	}

	static class RepositoryAnnotationTransactionAttributeSource extends AnnotationTransactionAttributeSource {

		private static final long serialVersionUID = 7229616838812819438L;

		private final RepositoryInformation repositoryInformation;
		private final boolean enableDefaultTransactions;

		public RepositoryAnnotationTransactionAttributeSource(RepositoryInformation repositoryInformation,
				boolean enableDefaultTransactions) {
			// 执行父类AnnotationTransactionAttributeSource,事务的分析器
			// 为JtaTransactionAnnotationParser和SpringTransactionAnnotationParser。
			// 即支持javax.transaction.Transactional的@Transactional注解和spring的@Transactional
			super(true);

			Assert.notNull(repositoryInformation, "RepositoryInformation must not be null");
			// enableDefaultTransactions默认为true
			this.enableDefaultTransactions = enableDefaultTransactions;
			// DefaultRepositoryInformation对象
			this.repositoryInformation = repositoryInformation;
		}

		@Override
		@Nullable
		protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {

			// Don't allow no-public methods as required.
			if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
				return null;
			}

			// Ignore CGLIB subclasses - introspect the actual user class.
			Class<?> userClass = targetClass == null ? targetClass : ProxyUtils.getUserClass(targetClass);

			// The method may be on an interface, but we need attributes from the target class.
			// If the target class is null, the method will be unchanged.
			Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);

			// If we are dealing with method with generic parameters, find the original method.
			// 如果正在处理具有泛型参数的方法,请找到原始方法。
			specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

			TransactionAttribute txAtt = null;

			if (specificMethod != method) {

				// Fallback is to look at the original method.
				txAtt = findTransactionAttribute(method);

				if (txAtt != null) {
					return txAtt;
				}

				// Last fallback is the class of the original method.
				txAtt = findTransactionAttribute(method.getDeclaringClass());

				if (txAtt != null || !enableDefaultTransactions) {
					return txAtt;
				}
			}

			// First try is the method in the target class.
			txAtt = findTransactionAttribute(specificMethod);

			if (txAtt != null) {
				return txAtt;
			}

			// Second try is the transaction attribute on the target class.
			txAtt = findTransactionAttribute(specificMethod.getDeclaringClass());

			if (txAtt != null) {
				return txAtt;
			}

			if (!enableDefaultTransactions) {
				return null;
			}

			// Fallback to implementation class transaction settings of nothing found
			// return findTransactionAttribute(method);
			Method targetClassMethod = repositoryInformation.getTargetClassMethod(method);

			if (targetClassMethod.equals(method)) {
				return null;
			}

			txAtt = findTransactionAttribute(targetClassMethod);

			if (txAtt != null) {
				return txAtt;
			}

			txAtt = findTransactionAttribute(targetClassMethod.getDeclaringClass());

			if (txAtt != null) {
				return txAtt;
			}

			return null;
		}
	}
}

5.1 在postProcess()方法中,执行如下:

1)声明一个TransactionInterceptor对象;

2)声明一个RepositoryAnnotationTransactionAttributeSource对象;

3)添加transactionManagerName和beanFactory;

4)在ProxyFactory添加TransactionInterceptor拦截器;

5.2 RepositoryAnnotationTransactionAttributeSource注解事务属性资源类为内部类,该类继承AnnotationTransactionAttributeSource。RepositoryAnnotationTransactionAttributeSource的构造方法中,执行父类的构造方法。

在AnnotationTransactionAttributeSource的构造方法中,会添加注解事务的解析器。添加了SpringTransactionAnnotationParser和JtaTransactionAnnotationParser。分别用于解析org.springframework.transaction.annotation包和javax.transaction包下的@Transactional注解。即在代码中使用两个包的@Transactional注解都可以实现事务。

在RepositoryAnnotationTransactionAttributeSource的computeTransactionAttribute()方法中,会调用父类AnnotationTransactionAttributeSource的findTransactionAttribute(),进而执行determineTransactionAttribute()方法,遍历解析器,解析对应的@Transactional注解。

结尾

限于篇幅,本篇先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

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

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

相关文章

git一次提交多个项目之windows

方案1:【快速】单个/多个项目提交到一个已有地址 步骤: 1,在git仓库,创建新的地址 2,在代码所在文件夹,编辑脚本 2.1,获得所有文件名:编写bat脚本,获得所有文件名称【非必须】; dir *.* /b/s>test.txt 获取所有文件之后,复制对应的文件名; 2.2,编写bat脚…

软件游戏提示msvcp120.dll丢失的解决方法,总结多种靠谱的解决方法

在电脑使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp120.dll”。那么&#xff0c;msvcp120.dll是什么&#xff1f;它对电脑有什么影响&#xff1f;有哪些解决方法&#xff1f;本文将从以下几个方面进行探讨。 一&#xff0c;了解msv…

微服务之基本介绍

一、微服务架构介绍 1、微服务架构演变过程 单体应用架构->垂直应用架构一>分布式架构一>SOA架构-->微服务架构 ① 单体应用架构 互联网早期&#xff0c; 一般的网站应用流量较小&#xff0c;只需一个应用&#xff0c;将所有功能代码都部署在一起就可以&#x…

Java面向对象-[封装、继承、多态、权限修饰符]

Java面向对象-封装、继承、权限修饰符 一、封装1、案例12、案例2 二、继承1、案例12、总结 三、多态1、案例 四、权限修饰符1、private2、default3、protected4、public 一、封装 1、案例1 package com.msp_oop;public class Girl {private int age;public int getAge() {ret…

ReactRouter——路由配置、路由跳转、带参跳转、新route配置项

目录 写在前面 (一)初步使用router 1.安装react-router-dom 2.创建router结构 3.嵌套路由 4.配置not found页面 (1)确切路由报错页面 (2)未配置路由报错页面 5.重定向 (二)路由跳转 1.组件跳转 2.NavLink 3.js跳转 (三)传递参数 1.searchParams(query)参数 2…

OS复习笔记ch8-3

驻留集 驻留集&#xff1a;指请求分页存储管理中给进程分配的内存块的集合。 在采用了虚拟存储技术的系统中&#xff0c;驻留集大小一般小于进程的总大小。 驻留集&#xff0c;从某种角度可以看成是进程可以常驻内存的内存块的集合。 若驻留集太小&#xff0c;会导致缺页频繁…

Golang | Leetcode Golang题解之第132题分割回文串II

题目&#xff1a; 题解&#xff1a; func minCut(s string) int {n : len(s)g : make([][]bool, n)for i : range g {g[i] make([]bool, n)for j : range g[i] {g[i][j] true}}for i : n - 1; i > 0; i-- {for j : i 1; j < n; j {g[i][j] s[i] s[j] && g[…

CentOS7 配置Nginx域名HTTPS

Configuring Nginx with HTTPS on CentOS 7 involves similar steps to the ones for Ubuntu, but with some variations in package management and service control. Here’s a step-by-step guide for CentOS 7: Prerequisites Domain Name: “www.xxx.com”Nginx Install…

阿里云平台产品创建过程 网页端界面 手机APP

云平台产品创建 登录后选择 产品-物联网-物联网平台&#xff1a; 进入后选择 公共示例-立即试用&#xff1a; 选择 公共示例&#xff1a; 选择 设备管理-产品-创建产品&#xff1a; 产品名称: 传感器 所属品类&#xff1a;自定义品类 节点类型&#xff1a;直连设备 联网方式…

mysql-community-libs-5.7.44-1.el7.x86_64.rpm 的公钥尚未安装

在 CentOS 或 RHEL 系统上安装 RPM 包时&#xff0c;如果遇到“公钥尚未安装”的问题&#xff0c;通常是因为系统没有导入相应的 GPG 公钥。MySQL 官方提供了一个 GPG 公钥&#xff0c;用于验证 RPM 包的签名。 以下是解决该问题的步骤&#xff1a; 下载并导入 MySQL 官方的 GP…

Linux本地搭建DataEase并发布公网远程访问进行数据分析

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

目标检测数据集 - 垃圾桶满溢检测数据集下载「包含VOC、COCO、YOLO三种格式」

数据集介绍&#xff1a;垃圾桶满溢检测数据集&#xff0c;真实场景高质量图片数据&#xff0c;涉及场景丰富&#xff0c;比如城市道边垃圾桶满溢、小区垃圾桶满溢、社区垃圾桶满溢、农村道边垃圾桶满溢、垃圾集中处理点垃圾桶满溢、公园垃圾桶满溢数据等。数据集标注标签划分为…

Luminar Neo - AI智能修图软件超越PS和LR,简单易用又高效!

很多人都想美化自己的风景和人物的图片&#xff0c;得到更加美丽耀眼的效果。然而&#xff0c;专业摄影师和设计师在电脑上使用的后期工具如 Photoshop 和 LightRoom 过于复杂。 通常为了一些简单的效果&#xff0c;你必须学习许多教程。而一些针对小白用户的“一键式美颜/美化…

度小满金融大模型的应用创新

XuanYuan/README.md at main Duxiaoman-DI/XuanYuan GitHub

晶振十大品牌

晶振是电路的心脏&#xff0c;特别对抖动、稳定度有要求&#xff0c;当然除了稳定度&#xff0c;抖动&#xff0c;还对环境温度有要求&#xff0c;优秀的厂商如下&#xff1a; 链接&#xff1a; 晶振十大品牌-晶振品牌-振荡器品牌-Maigoo品牌榜

大话设计模式解读01-简单工厂模式

本系列的文章&#xff0c;来介绍编程中的设计模式&#xff0c;介绍的内容主要为《大话设计模式》的读书笔记&#xff0c;并改用C语言来实现&#xff08;书中使用的是.NET中的C#&#xff09;,本篇来学习第一章&#xff0c;介绍的设计模式是——简单工厂模式。 1 面向对象编程 …

华为防火墙配置 SSL VPN

前言 哈喽&#xff0c;我是ICT大龙。本期给大家更新一次使用华为防火墙实现SSL VPN的技术文章。 本次实验只需要用到两个软件&#xff0c;分别是ENSP和VMware&#xff0c;本次实验中的所有文件都可以在文章的末尾获取。话不多说&#xff0c;教程开始。 什么是VPN 百度百科解…

Pycharm中import torch报错解决方案(Python+Pycharm+Pytorch cpu版)

pycharm环境搭建完毕后&#xff0c;编写一个py文件demo&#xff0c;import torch报错&#xff0c;提示没有。设置python解释器&#xff1a; 选择conda环境&#xff0c;使用现有环境&#xff0c;conda执行文件找到Anaconda安装路径下Scripts文件夹内的conda.exe&#xff0c;最后…

十大人工智能企业

​​​​​​链接&#xff1a;​​​​​​人工智能品牌排行-人工智能十大公司-人工智能十大品牌-Maigoo品牌榜

发光二极管十大品牌

日常电路设计中&#xff0c;LED是必用的元器件之一&#xff0c;辅助判定电路异常。 十大发光二极管品牌-LED灯珠生产厂家哪家好-LED发光二极管厂家前十-Maigoo品牌榜