注解@Autowired和@Resource的区别总结
ztj100 2024-12-26 17:44 17 浏览 0 评论
零、前言
@Autowired和@Resource注解都可以在Spring应用中进行声明式的依赖注入。以前都是看的网上关于两者的区别,但是实际和网上说的有出入,故从源码角度进行分析、验证。
以下源码基于spring 5.3.20(通过springboot 2.7.0引入)
一、结论
1、@Autowired注解总结
- 可用于构造函数,成员变量以及set方法
- 从Spring 4.3开始,如果目标Bean只有一个构造函数,则在该构造函数上可以省略@Autowired注解;如果目标Bean有多个构造函数则不可省略
@Autowired注入方式:
- 按照type查找bean,如果使用@Qualifier注解声明了name,则从结果集中取出与该name相匹配的bean返回(此时可以视为通过name和type获取bean,但实质是先通过type获取所有bean,然后通过name筛选,详情见后文findAutowireCandidates()方法源码分析)
- 如果没有使用@Qualifier注解,且找到多个bean,则判断这些bean中是否有使用@Primary注解和@Priority注解,有就返回优先级最高的哪一个bean,没有就按照字段名称去匹配bean,匹配成功返回,不成功抛出异常。(详情见后文determineAutowireCandidate()方法源码解析)
整体注入流程如下所示:
2、@Resource注解总结
- 可用于成员变量以及set方法
- 若不指定name属性,则会把name属性值处理为字段名或set方法标识的字段名称
- 若指定type属性,则type属性值必须与字段类型或set方法返回值类型为父子关系(type属性值可以是子类,也可以是超类),否则会抛出异常
- @Resource先按照name属性值注入,若未找到,则按type属性值注入。即默认的name或指定的name找不到 bean ,就会按 type 注入
整体注入流程如下所示:
二、@Autowired注入过程源码分析
1、AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata():切入点
首先定位到org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata()方法,代码如下。可以看到,如果是在属性上声明@Autowired,则构造AutowiredFieldElement对象,如果是在方法上声明@Autowired,则构造AutowiredMethodElement对象。
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
?
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
?
// 字段调用
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
// 构造AutowiredFieldElement对象
currElements.add(new AutowiredFieldElement(field, required));
}
});
?
// 方法调用
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
// 构造AutowiredMethodElement对象
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
?
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
?
return InjectionMetadata.forElements(elements, clazz);
}
复制代码
AutowiredFieldElement和AutowiredMethodElement对象很类似,都是构造器初始化数据,inject()获取bean,现以AutowiredFieldElement为例进行说明。
2、AutowiredFieldElement对象
private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
?
private final boolean required;
?
private volatile boolean cached;
?
@Nullable
private volatile Object cachedFieldValue;
?
// 1、构造器初始化数据,由于@Autowired就一个required属性,故非常见到那
public AutowiredFieldElement(Field field, boolean required) {
super(field, null);
this.required = required;
}
?
// 2、inject()方法获取bean
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
try {
// 2.1 开启缓存,调用resolvedCachedArgument获取bean
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// Unexpected removal of target bean for cached argument -> re-resolve
// 调用resolvedCachedArgument获取bean,则调用resolveFieldValue获取bean
value = resolveFieldValue(field, bean, beanName);
}
}
else {
// 2.2 调用resolveFieldValue获取bean
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
?
// 3、调用resolveFieldValue()获取bean
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
Assert.state(beanFactory != null, "No BeanFactory available");
TypeConverter typeConverter = beanFactory.getTypeConverter();
Object value;
try {
// 3.1 实际上就是调用beanFactory.resolveDependency方法
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
this.cached = true;
}
}
return value;
}
}
复制代码
如果开启缓存,则inject()方法直接调用resolvedCachedArgument()方法获取bean,否则调用resolveFieldValue()方法,而resolveFieldValue()方法又会去调用beanFactory.resolveDependency()方法,即调用org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency()方法,进而调用doResolveDependency()方法
3、DefaultListableBeanFactory对象
3.1 resolveDependency():@Lazy懒加载处理
总结:如果使用了@Lazy,则生成cglib代理对象返回;否则调用doResolveDependency()方法获取bean
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
?
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
if (Optional.class == descriptor.getDependencyType()) {
return createOptionalDependency(descriptor, requestingBeanName);
}
else if (ObjectFactory.class == descriptor.getDependencyType() ||
ObjectProvider.class == descriptor.getDependencyType()) {
return new DependencyObjectProvider(descriptor, requestingBeanName);
}
else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
}
// 上面是一些特殊逻辑处理,下面才是主流程
else {
// 如果使用@Autowired的同时使用了@Lazy,则这里返回一个cglib代理对象,否则返回null
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
if (result == null) {
// 也就是说只有单独使用@Autowired才会进这里
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;
}
}
复制代码
3.2 doResolveDependency():获取bean的处理流程
// 注入依赖时未使用@Lazy注解,调用此方法
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
?
......
// 1、按类型查找所有的bean,未通过@Qualifier指定name时,查到几个就是几个;
// 若通过@Qualifier指定name,则将查到的bean与name进行匹配,没有匹配的就返回空集合
// findAutowireCandidates()方法详解见下文
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) { // 没有找到
if (isRequired(descriptor)) { // required值为true,必须有值,抛出异常
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null; //required值为false,返回null
}
?
String autowiredBeanName;
Object instanceCandidate;
?
// 2、按类型找到多个bean
if (matchingBeans.size() > 1) {
// 从多个bean中找到那唯一一个,没有找到返回null
// 寻找规则:@Primary > @Priority (若有多个,返回优先级最高的那个,值越小优先级越高) > 按照字段名匹配
// determineAutowireCandidate()方法详解见下文
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
// 没有从多个bean中选出唯一一个,而且还是必须的,则抛出异常
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
}
else {
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// We have exactly one match.
// 3、按类型找到一个bean,直接返回
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}
?
......
}
复制代码
3.3 findAutowireCandidates():通过类型查找bean的处理流程
方法路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#findAutowireCandidates()
总结:
- 获取所有类型的bean
- 如果使用@Qualifier声明name,则将指定name的bean放入结果集中,若没有则返回空
- 如果未使用@Qualifier声明name,则将所有bean放入结果集中,返回
protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
?
// 1、获取该类型的所有bean
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());
// 2、将resolvableDependencies集合中类型相同的bean放到result中
Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
Class<?> autowiringType = classObjectEntry.getKey();
if (autowiringType.isAssignableFrom(requiredType)) {
Object autowiringValue = classObjectEntry.getValue();
autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
if (requiredType.isInstance(autowiringValue)) {
result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
break;
}
}
}
// 3、循环所有bean数组,将满足条件的bean放入result中
for (String candidate : candidateNames) {
// isSelfReference:判断是否为自引用
// isAutowireCandidate:判断指定的 bean 是否有资格作为自动装配候选者。
// 如果没有使用@Qualifier声明name,则都有资格;若声明了name,则只有beanName等于name时才有资格
// 具体调用路径见后文
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
// 后备匹配选项,暂时忽略
......
return result;
}
复制代码
isAutowireCandidate()方法调用流程简单说明(通过debug可以快速走出来)
// 第一步:重载方法
@Override
public boolean isAutowireCandidate(String beanName, DependencyDescriptor descriptor)
throws NoSuchBeanDefinitionException {
?
return isAutowireCandidate(beanName, descriptor, getAutowireCandidateResolver());
}
?
// 第二步:重载方法
protected boolean isAutowireCandidate(
String beanName, DependencyDescriptor descriptor, AutowireCandidateResolver resolver)
throws NoSuchBeanDefinitionException {
?
String bdName = BeanFactoryUtils.transformedBeanName(beanName);
if (containsBeanDefinition(bdName)) {
// 2、走这里
return isAutowireCandidate(beanName, getMergedLocalBeanDefinition(bdName), descriptor, resolver);
}
else if (containsSingleton(beanName)) {
return isAutowireCandidate(beanName, new RootBeanDefinition(getType(beanName)), descriptor, resolver);
}
// 代码省略
......
}
?
// 第三步:
protected boolean isAutowireCandidate(String beanName, RootBeanDefinition mbd,
DependencyDescriptor descriptor, AutowireCandidateResolver resolver) {
// 代码省略
......
// 3、走这里
return resolver.isAutowireCandidate(holder, descriptor);
}
?
// 第四步:org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#isAutowireCandidate()
@Override
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
boolean match = super.isAutowireCandidate(bdHolder, descriptor);
if (match) {
// 4、判断Qualifier注解声明的name是否与当前bean匹配
match = checkQualifiers(bdHolder, descriptor.getAnnotations());
if (match) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
Method method = methodParam.getMethod();
if (method == null || void.class == method.getReturnType()) {
match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
}
}
}
}
return match;
}
?
// 第五步:将给定的限定符(即Qualifier声明的name)与候选 bean 的beanName匹配
protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
......
}
复制代码
3.4 determineAutowireCandidate():找到多个bean的处理流程
方法路径:org.springframework.beans.factory.support.DefaultListableBeanFactory#determineAutowireCandidate()
总结:
- 判断是否有使用@Primary注解的bean,有直接返回,没有返回null
- 判断是否有使用@Priority注解的bean,没有返回null;有一个直接返回;有多个,返回优先级最高的那个(@Priority值越小优先级越高)
- 既没有使用@Primary,也没有使用@Priority,用被@Autowired注解字段名称当做beanName去匹配,匹配成功直接返回,否则返回null
@Nullable
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
Class<?> requiredType = descriptor.getDependencyType();
// 1、判断是否有使用@Primary注解的bean,有直接返回,没有返回null
String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
if (primaryCandidate != null) {
return primaryCandidate;
}
// 2、判断是否有使用@Priority注解的bean,没有返回null;有一个直接返回;有多个,返回优先级最高的那个(@Priority值越小优先级越高)
String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
if (priorityCandidate != null) {
return priorityCandidate;
}
// Fallback
// 3、既没有使用@Primary,也没有使用@Priority,用被@Autowired注解字段名称当做beanName去匹配,匹配成功直接返回,否则返回null
for (Map.Entry<String, Object> entry : candidates.entrySet()) {
String candidateName = entry.getKey();
Object beanInstance = entry.getValue();
// matchesBeanName():判断候选bean集合中是否存在与字段名称相同的bean
if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
matchesBeanName(candidateName, descriptor.getDependencyName())) {
return candidateName;
}
}
return null;
}
复制代码
三、@Resource注入过程源码分析
1、CommonAnnotationBeanPostProcessor#buildResourceMetadata():切入点
首先定位到org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#buildResourceMetadata()方法,里面有这么一段代码:判断字段是否使用@Resource注解:先判断是否为静态字段,然后判断字段类型是否为忽略的类型,若不是则构建一个ResourceElement对象。
......
else if (field.isAnnotationPresent(Resource.class)) {
// 判断是否为静态字段
if (Modifier.isStatic(field.getModifiers())) {
throw new IllegalStateException("@Resource annotation is not supported on static fields");
}
// 判断字段类型是否为忽略的类型,若不是则构建一个ResourceElement对象
if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
currElements.add(new ResourceElement(field, field, null));
}
}
......
复制代码
2、ResourceElement对象
对象路径:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement()
ResourceElement类包含2个方法:一个构造器,一个getResourceToInject()方法。构造器只要功能就是初始化数据,代码如下:
public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
super(member, pd);
Resource resource = ae.getAnnotation(Resource.class);
// 1、获取注解的name属性值
String resourceName = resource.name();
// 2、获取注解的type属性值
Class<?> resourceType = resource.type();
// 3、判断name属性值是否为默认值""
this.isDefaultName = !StringUtils.hasLength(resourceName);
if (this.isDefaultName) {
// 3.1 获取被注解原始的名称:用在字段上,则拿到的就是字段名称,否则是set方法
resourceName = this.member.getName();
// 3.2 如果是方法,则截取set后的元素,并将首字母改为小写
if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
resourceName = Introspector.decapitalize(resourceName.substring(3));
}
}
else if (embeddedValueResolver != null) {
// 4、指定了name属性,则进行解析(占位符的处理等)
resourceName = embeddedValueResolver.resolveStringValue(resourceName);
}
// 5、若显示指定了type属性值,则判断指定的值与字段的类型或set方法返回值类型是否为父子关系,若不是则抛出异常
if (Object.class != resourceType) {
checkResourceType(resourceType);
}
else {
// 未指定type属性值,使用默认值Object.class,则获取字段类型或set方法的返回值类型
resourceType = getResourceType();
}
// 6、将name属性值和type属性值赋值给成员变量
this.name = (resourceName != null ? resourceName : "");
this.lookupType = resourceType;
String lookupValue = resource.lookup();
this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());
// 7、@Lazy懒加载处理
Lazy lazy = ae.getAnnotation(Lazy.class);
this.lazyLookup = (lazy != null && lazy.value());
}
?
@Override
protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
// this.lazyLookup = true,表示使用了@Lazy注解,此时调用buildLazyResourceProxy返回cglib代理对象,否则调用getResource方法
return (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :
getResource(this, requestingBeanName));
}
复制代码
总结:
- 从这里可以看出,只要你不指定name属性,则都会把name属性值处理未字段名或set方法声明的字段名称
- 除非你指定的type属性值与字段类型或set方法返回值类型为父子关系(指定值可以是子类,也可以是超类) ,否则都会抛出异常
- @Resource 在不指定 name 的情况下,默认的name是字段名或set方法标识的字段名称;如果默认的name 或指定的name找不到 bean ,就会按 type 注入。
3、autowireResource():获取bean流程
调用链:getResourceToInject() -> getResource() -> autowireResource
方法路径:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource()
protected Object autowireResource(BeanFactory factory, LookupElement element, @Nullable String requestingBeanName)
throws NoSuchBeanDefinitionException {
?
Object resource;
Set<String> autowiredBeanNames;
String name = element.name;
?
// 1、判断beanFactory类型
if (factory instanceof AutowireCapableBeanFactory) {
AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory) factory;
DependencyDescriptor descriptor = element.getDependencyDescriptor();
// 1.1 根据 name 属性值获取不到bean
if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
autowiredBeanNames = new LinkedHashSet<>();
// 1.1.1 根据 type 类型获取bean
resource = beanFactory.resolveDependency(descriptor, requestingBeanName, autowiredBeanNames, null);
// 1.1.2 获取不到抛出异常
if (resource == null) {
throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
}
}
// 1.2 根据name属性获取到bean,判断类型是否相同,不同抛出异常
else {
resource = beanFactory.resolveBeanByName(name, descriptor);
autowiredBeanNames = Collections.singleton(name);
}
}
else { // 2、根据name属性获取到bean,判断类型是否相同,不同抛出异常
resource = factory.getBean(name, element.lookupType);
autowiredBeanNames = Collections.singleton(name);
}
?
if (factory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory) factory;
for (String autowiredBeanName : autowiredBeanNames) {
if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
}
}
}
?
return resource;
}
学习更多JAVA知识与技巧,内容涵盖:Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、SpringBoot、SpringCloud、RabbitMQ、Kafka、Linux等技术栈,关注博主!
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)