Transactional事务失效场景汇总

news2024/12/24 10:15:49

文章目录

  • 1、前言
  • 2、失效场景
    • 2.1、Service没有被Spring管理
    • 2.2、事务方法被final、static关键字修饰
    • 2.3、同一个类中,方法内部调用
    • 2.4、方法的访问权限不是public
    • 2.5、数据库的存储引擎不支持事务
    • 2.6、@Transactional 注解配置错误
    • 2.7、使用了错误的事务传播机制
    • 2.8、rollbackFor属性配置错误
    • 2.9、异常被捕获并处理了,没有抛出
    • 2.10、手动抛了别的异常
    • 2.11、多线程调用场景
  • 3、总结

1、前言

作为后端程序员,在日常开发中,经常会遇到事务处理的场景,在Spring中,为了更好的支撑我们进行数据库操作,它提供了两种事务管理的方式:

  • 编程式事务
  • 声明式事务

那众所周知,我们平时用的最多的就是声明式事务,也就是使用**@Transactional**注解的方式了

但是在日常开发中,如果对注解@Transactional使用不当的话,可能会导致事务失效,所以今天我们一起来总结梳理一下常见的一些失效场景,我这里梳理了下面这些场景:

image-20230419182746992

2、失效场景

2.1、Service没有被Spring管理

看如下代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
//@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    public JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;//制造异常:发生算数异常
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理来支持事务。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢,所以此种场景事务注解会失效,大家在开发过程中要仔细了,不要忘记,将@Transactional所在的类,交给Spring管理

2.2、事务方法被final、static关键字修饰

看如下代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    public final JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

如果一个方法被声明为final或者static,则该方法不能被子类重写,也就是说无法在该方法上进行动态代理,这会导致Spring无法生成事务代理对象来管理事务

2.3、同一个类中,方法内部调用

看下面代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    public JSONResult addUser(User user, SysOperLog log) {
        doSomething(user, log);
        return JSONResult.success("操作成功");
    }

    @Transactional
    public void doSomething(User user, SysOperLog log){
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。即以上代码,调用目标doSomething方法不是通过代理类进行的,因此事务不生效

2.4、方法的访问权限不是public

看下面代码:

package org.wujiangbo.service.impl.user;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.wujiangbo.domain.log.SysOperLog;
import org.wujiangbo.domain.user.User;
import org.wujiangbo.mapper.log.SysOperLogMapper;
import org.wujiangbo.mapper.user.UserMapper;
import org.wujiangbo.query.user.UserQuery;
import org.wujiangbo.result.JSONResult;
import org.wujiangbo.service.user.UserService;
import org.wujiangbo.utils.StringUtils;

import javax.annotation.Resource;
import java.util.List;

/**
 * <p>
 * 用户表 服务实现类
 * </p>
 *
 * @author bobo(weixin:javabobo0513)
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{

    @Resource
    private UserMapper userMapper;
    @Resource
    private SysOperLogMapper logMapper;

    @Override
    @Transactional
    private JSONResult addUser(User user, SysOperLog log) {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
        return JSONResult.success("操作成功");
    }

}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

spring事务方法addUser的访问权限不是public,所以事务就不生效了,因为Spring事务是由AOP机制实现的,AOP机制的本质就是动态代理,而代理的事务方法不是public的话,computeTransactionAttribute()就会返回null,也就是这时事务属性不存在了

大家可以看下AbstractFallbackTransactionAttributeSource的源码:

image-20230419173640624

2.5、数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,必须要确认你的选择的存储引擎是支持事务的

比如下面的SQL创建用户表时,就采用的是InnoDB存储引擎:

DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `age` int(11) NULL DEFAULT NULL COMMENT '年龄',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

2.6、@Transactional 注解配置错误

看如下代码:

@Transactional(readOnly = true)
public JSONResult addUser(User user, SysOperLog log) {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    int i = 1/0;
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在保存数据时会抛出如下异常:

image-20230419174314860

我们使用@Transactional注解时,一般不需要跟后面的readOnly属性

2.7、使用了错误的事务传播机制

看如下代码:

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public JSONResult addUser(User user, SysOperLog log) {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    int i = 1/0;
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

这里事务失效的原因是:Propagation.NOT_SUPPORTED表示传播特性不支持事务

我们一起来回顾下Spring提供了七种事务传播机制。它们分别是:

  • REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
  • SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
  • MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
  • REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
  • NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
  • NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

2.8、rollbackFor属性配置错误

看如下代码:

@Transactional(rollbackFor = Error.class)
public JSONResult addUser(User user, SysOperLog log) throws Exception {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    if(1 == 1){
        //模拟抛出异常
        throw new Exception();
    }
    return JSONResult.success("操作成功");
}

分析:

rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeExceptionError两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效

大家可以看一下Transactional注解源码:

image-20230419175336972

2.9、异常被捕获并处理了,没有抛出

看如下代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
    }
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是用户数据和日志数据还是会存到数据库之中

分析:

事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚

我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

//省略其他代码,只留了下面核心代码
    
@Nullable
 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
  
   TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
   Object retVal;
   try {
        //Spring AOP中MethodInterceptor接口的一个方法,它允许拦截器在执行被代理方法之前和之后执行额外的逻辑。
        retVal = invocation.proceedWithInvocation();
   }
   catch (Throwable ex) {
    //用于在发生异常时完成事务(如果Spring catch不到对应的异常的话,就不会进入回滚事务的逻辑)
    completeTransactionAfterThrowing(txInfo, ex);
    throw ex;
   }
   finally {
    cleanupTransactionInfo(txInfo);
   }

   //用于在方法正常返回后提交事务。
   commitTransactionAfterReturning(txInfo);
   return retVal;
  }
}

invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是在我们测试代码中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了

解决方案

spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志,一定要重新把异常抛出来,正例如下:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //新增日志记录
        logMapper.insert(log);
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    return JSONResult.success("操作成功");
}

在catch中添加:throw e;

2.10、手动抛了别的异常

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log) throws Exception {
    //新增用户信息
    userMapper.insert(user);
    //新增日志记录
    logMapper.insert(log);
    if(1 == 1){
        //模拟抛出异常
        throw new Exception();
    }
    return JSONResult.success("操作成功");
}

分析:

Spring默认只处理RuntimeException和Error或其子类,对于普通的Exception是不会回滚的,但是上面的代码例子中,手动抛了Exception异常,所以是不会回滚,除非用rollbackFor属性指定,如下:

@Transactional(rollbackFor = Exception.class)

2.11、多线程调用场景

看下面代码:

@Transactional
public JSONResult addUser(User user, SysOperLog log){
    try {
        //新增用户信息
        userMapper.insert(user);
        //多线程调用
        new Thread(() -> {
            //新增日志记录
            logMapper.insert(log);
        }).start();

        //模拟异常
        int i = 1/0;
    }
    catch (Exception e){
        e.printStackTrace();
        throw e;
    }
    return JSONResult.success("操作成功");
}

代码执行结果:

虽然发生了算数异常,但是日志数据还是会存到数据库之中,只有用户数据会回滚

分析:

这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效

我们可以进入到TransactionAspectSupport类的prepareTransactionInfo方法中看一下,有一个解释如下:

image-20230419181636899

简单翻译:

image-20230419181711752

从这里我们得知,事务信息是跟线程绑定的。

因此在多线程环境下,事务的信息都是独立的,将会导致Spring在接管事务上出现差异

3、总结

经过这样的总结梳理,相信你应该已经对@Transactional 注解使用的一些坑有所了解了,以后在开发过程中就要格外注意了

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

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

相关文章

elasticsearch与mysql数据同步

目录 数据同步一.思路分析1.同步调用2.异步通知3.监听binlog4.选择 二.实现数据同步1.思路2.导入demo3.声明交换机、队列3.1引入依赖3.2 配置文件3.3 声明队列交换机名称3.4 声明队列交换机 4.发送MQ消息4.1 事务配置类4.2 service 代码 5.接收MQ消息 数据同步 elasticsearch中…

数据分析----IQR(Interquartile Range)四分位距的理解与应用及Python实现

【原文链接】数据分析----IQR&#xff08;Interquartile Range&#xff09;四分位距的理解与应用及Python实现 一、IQR&#xff08;Interquartile Range&#xff09;四分位距的含义 1.1 IQR的官方定义 IQR 是用于标记离群值的另一种稳健方法。用于检测离群值的 IQR&#xff…

解放你的双手:自动化文档整理

目录 引子: 应用场景&#xff1a; 源代码&#xff1a; 源代码说明&#xff1a; 效果如下所示&#xff1a; movefiletofolderbytype.py 引子: 例如&#xff0c;一个人可能会在计算机上存储大量的照片、视频和文档文件&#xff0c;这些文件可能散落在不同的文件夹中&#xf…

1678_计算机架构黄金时代_文章阅读

全部学习汇总&#xff1a; GreyZhang/g_risc_v: Learning notes about RISC V. (github.com) 看了一份几年前的文章&#xff0c;觉得还是挺有收获的&#xff0c;因此做一个简单的整理。 对于架构有很大影响的主要考虑四点&#xff1a;专用硬件的实现、高安全性的要求、开放指令…

原来这些功能才是blender大受欢迎的原因

作为全球最热门的免费开源3D创作套件&#xff0c;好用、易上手是Blender的公认优点。相比许多付费的三维软件来说&#xff0c;Blender在功能上是有过之无不及。除了在建模方面表现出色外&#xff0c;还提供了从模型雕刻、动画、材质、渲染、到音频处理、视频剪辑等一系列制作解…

Nature子刊:HPV改变阴道菌群的机制

​ 研究背景 人类乳头瘤病毒(HPV)是最常见的性传播感染&#xff0c;影响全球3亿人以上。 虽然大多数感染像得了场“感冒”一样被免疫系统清除&#xff0c;或保持在无症状或潜伏状态&#xff0c;但致癌(高危)HPV株(如最明显的HPV16和18型)会导致宫颈鳞状上皮内病变[低级别(LSIL…

MATLAB在逐渐被Python淘汰吗?

Python和MATLAB都是常用的科学计算工具&#xff0c;但是它们有很多不同之处。 Python是一种通用编程语言&#xff0c;而MATLAB主要是用来做数值计算的。Python的基本数据类型和一般的编程语言一样普遍&#xff0c;但是离开了Numpy这个包&#xff0c;就不再有数组或者矩阵的数据…

网络安全为什么缺人? 缺什么样的人

1.网络安全为什么缺人? 缺人的原因是有了新的需求。 以前的时候&#xff0c;所有企业是以产品为核心的&#xff0c;管你有啥漏洞&#xff0c;管你用户信息泄露不泄露&#xff0c;我只要做出来的产品火爆就行。 这一切随着《网络安全法》、《数据安全法》、《网络安全审查办法…

Blender插件Lazy Viewport

目录 1.Lazy Viewport插件1.1 解压Lazy Viewport插件1.2 blender偏好设置1.3 打开插件1.4 安装插件1.5 勾选插件Lazy Viewport1.6 安装插件前1.7 安装插件后 1.Lazy Viewport插件 Blender 的一个简单插件&#xff0c;用于将标准 G、R、S 热键映射到视图工具&#xff0c;因此您…

哪款无线洗地机最好用?好用的无线洗地机分享

洗地机是近几年来比较火的家庭清洁工具&#xff0c;我从推出以来就一直在使用&#xff0c;这些年下来也使用过不少品牌的洗地机&#xff0c;其中有好用的&#xff0c;也有体验一般的。今天为大家分享几款个人使用下来感觉还不错的洗地机。希望对于同样在选购洗地机的你有所帮助…

线程七大状态

线程生命周期&#xff08;七大状态&#xff09; 新建状态&#xff08;New&#xff09;&#xff1a;当Java线程被创建时&#xff0c;它处于新建状态。此时&#xff0c;线程对象已被创建&#xff0c;但尚未启动。在这个状态下&#xff0c;线程并没有开始执行任何代码&#xff0c;…

[Java]监听器(Listener)

过滤器&#xff08;Filter&#xff09;https://blog.csdn.net/m0_71229255/article/details/130246404?spm1001.2014.3001.5501 一 : Listener监听器简述 监听器就是监听某个对象的的状态变化的组件 监听器的相关概念&#xff1a; 事件源&#xff1a; 被监听的对象 ----- 三…

Jenkins安装maven integration plugin以及jenkins安装allure插件失败的解决方法

这里写目录标题 一、Jenkins安装maven integration plugin失败解决方法&#xff08;1&#xff09;修改系统时间&#xff08;2&#xff09;查看当前操作系统时间&#xff08;3&#xff09;防止出错先执行命令&#xff08;4&#xff09;修改系统时间&#xff08;5&#xff09;写入…

Cocos Creator 源码解读:引擎启动与主循环

前言 本文基于 Cocos Creator 2.4.3 撰写。 Ready? 不知道你有没有想过&#xff0c;假如把游戏世界比作一辆汽车&#xff0c;那么这辆“汽车”是如何启动&#xff0c;又是如何持续运转的呢&#xff1f; 如题&#xff0c;本文的内容主要为 Cocos Creator 引擎的启动流程和主…

C# | 上位机开发新手指南(十一)压缩算法

上位机开发新手指南&#xff08;十一&#xff09;压缩算法 文章目录 上位机开发新手指南&#xff08;十一&#xff09;压缩算法前言压缩算法的分类从数据来源角度分类流式压缩块压缩 从是否需要建立字典角度分类字典压缩无字典压缩 流式压缩与块压缩流式压缩的优势与劣势优势劣…

各种开源协议介绍

世界上的开源许可证&#xff08;Open Source License&#xff09;大概有上百种&#xff0c;今天我们来介绍下几种我们常见的开源协议。大致有GPL、BSD、MIT、Mozilla、Apache和LGPL等。 Apache License Apache License&#xff08;Apache许可证&#xff09;&#xff0c;是Apac…

O2OA (翱途) 平台 V8.0 即将亮相

亲爱的小伙伴们&#xff0c;O2OA (翱途) 平台开发团队经过几个月的持续努力&#xff0c;实现功能的新增、优化以及问题的修复。2023 年度 V8.0 版本将于近期正式发布。届时我们将会用文档或者视频的方式详细来介绍新增的功能和优化的亮点&#xff0c;欢迎大家一起来体验&#x…

在Vue中将单独一张图片设为背景图并充满整个屏幕

将单独一张图片设为背景图并充满整个屏幕 代码如下(在主div中添加样式) background: url("../xx/images/图片名字.jpg");//这里的地址是用你项目中图片所在的路径为准background-repeat: no-repeat;//将图片样式不重复background-size: 100% 100%; //设置图片大小po…

YOLOv8 更换主干网络之 PP-LCNet

《PP-LCNet: A Lightweight CPU Convlutional Neural Network》 论文地址:https://arxiv.org/abs/2109.15099 代码地址:https://github.com/ngnquan/PP-LCNet 我们提出了一种基于MKLDNN加速策略的轻量级CPU网络,名为PP LCNet,它提高了轻量级模型在多个任务上的性能。本文列…

13、DRF实战总结:重写DRF的to_representation和to_internal_value方法的作用详解(附源码)

DRF的to_representation和to_internal_value是序列化和反序列化过程中最核心的方法&#xff0c;它们分别用于将数据对象转换成字典&#xff0c;和将字典转换成数据对象。 DRF所有序列化器类都继承了BaseSerializer类&#xff0c;通过重写该类的to_representation()和to_intern…