你的业务代码中Spring声明式事务处理正确了吗?

news2024/9/27 21:28:41

 

Spring 针对 Java Transaction API (JTA)、JDBC、Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 Spring 的声明式事务功能更是提供了极其方便的事务配置方式,配合 Spring Boot 的自动配置,大多数 Spring Boot 项目只需要在方法上标记 @Transactional 注解,即可一键开启方法的事务性配置。

据我观察,大多数业务开发同学都有事务的概念,也知道如果整体考虑多个数据库操作要么成功要么失败时,需要通过数据库事务来实现多个操作的一致性和原子性。但,在使用上大

多仅限于为方法标记 @Transactional,不会去关注事务是否有效、出错后事务是否正确回滚,也不会考虑复杂的业务代码中涉及多个子业务逻辑时,怎么正确处理事务。

事务没有被正确处理,一般来说不会过于影响正常流程,也不容易在测试阶段被发现。但当系统越来越复杂、压力越来越大之后,就会带来大量的数据不一致问题,随后就是大量的人工介入查看和修复数据。

所以说,一个成熟的业务系统和一个基本可用能完成功能的业务系统,在事务处理细节上的差异非常大。要确保事务的配置符合业务功能的需求,往往不仅仅是技术问题,还涉及产品流程和架构设计的问题。今天这一讲的标题“20% 的业务代码的 Spring 声明式事务,可能都没处理正确”中,20% 这个数字在我看来还是比较保守的。

本文要分享的内容,就是帮助你在技术问题上理清思路,避免因为事务处理不当让业务逻辑的实现产生大量偶发 Bug。

一、小心Spring的事务可能没有生效

在使用 @Transactional 注解开启声明式事务时, 第一个最容易忽略的问题是,很可能事务并没有生效。

实现下面的 Demo 需要一些基础类,首先定义一个具有 ID 和姓名属性的 UserEntity,也就是一个包含两个字段的用户表:

@Entity
@Data
publicclassUserEntity{
    @Id
    @GeneratedValue(strategy=AUTO)
    privateLongid;
    privateStringname;
    publicUserEntity(){
    }
    publicUserEntity(Stringname){
        this.name=name;
    }
}

为了方便理解,我使用 Spring JPA 做数据库访问,实现这样一个 Repository,新增一个根据用户名查询所有数据的方法:

@Repository
publicinterfaceUserRepositoryextendsJpaRepository<UserEntity,long> {
    List<UserEntity> findByName(String name);
}

定义一个 UserService 类,负责业务逻辑处理。如果不清楚 @Transactional 的实现方式,只考虑代码逻辑的话,这段代码看起来没有问题。

定义一个入口方法 createUserWrong1 来调用另一个私有方法 createUserPrivate,私有方法上标记了 @Transactional 注解。当传入的用户名包含 test 关键字时判断为用户名不合法,抛出异常,让用户创建操作失败,期望事务可以回滚:

@Service
@Slf4j
publicclassUserService{
    @Autowired
    privateUserRepository userRepository;
    //一个公共方法供Controller调用,内部调用事务性的私有方法
    publicintcreateUserWrong1(String name) {
        try{
            this.createUserPrivate(newUserEntity(name));
        }
        catch(Exception ex) {
            log.error("create user failed because {}", ex.getMessage());
        }
        returnuserRepository.findByName(name).size();
    }
    //标记了@Transactional的private方法
    @Transactional
    privatevoidcreateUserPrivate(UserEntity entity) {
        userRepository.save(entity);
        if(entity.getName().contains("test"))
        thrownewRuntimeException("invalid username!");
    }
    //根据用户名查询用户数
    publicintgetUserCount(String name) {
        returnuserRepository.findByName(name).size();
    }
}

下面是 Controller 的实现,只是调用一下刚才定义的 UserService 中的入口方法createUserWrong1。

@Autowired
privateUserServiceuserService;

@GetMapping("wrong1")
publicintwrong1(@RequestParam("name")Stringname){
    returnuserService.createUserWrong1(name);
}

调用接口后发现,即便用户名不合法,用户也能创建成功。刷新浏览器,多次发现有十几个的非法用户注册。

这里给出 @Transactional 生效原则 1,除非特殊配置(比如使用 AspectJ 静态织入实现AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。

你可能会说,修复方式很简单,把标记了事务注解的 createUserPrivate 方法改为 public即可。在 UserService 中再建一个入口方法 createUserWrong2,来调用这个 public 方法再次尝试:

publicintcreateUserWrong2(Stringname){
    try{
        this.createUserPublic(newUserEntity(name));
    }
    catch(Exceptionex){
        log.error("createuserfailedbecause{}",ex.getMessage());
    }
    returnuserRepository.findByName(name).size();
}

//标记了@Transactional的public方法
@Transactional
public void createUserPublic(UserEntity entity) {
    userRepository.save(entity);
    if(entity.getName().contains("test"))
    thrownewRuntimeException("invalid username!");
}

测试发现,调用新的 createUserWrong2 方法事务同样不生效。这里,我给出@Transactional 生效原则 2,必须通过代理过的类从外部调用目标方法才能生效

Spring 通过 AOP 技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象。我们尝试修改下 UserService 的代码,注入一个 self,然后再通过 self 实例调用标记有@Transactional 注解的 createUserPublic 方法。设置断点可以看到,self 是由 Spring 通过 CGLIB 方式增强过的类:

  • CGLIB 通过继承方式实现代理类,private 方法在子类不可见,自然也就无法进行事务增强;
  • this 指针代表对象自己,Spring 不可能注入 this,所以通过 this 访问方法必然不是代理。

 

把 this 改为 self 后测试发现,在 Controller 中调用 createUserRight 方法可以验证事务是生效的,非法的用户注册操作可以回滚。

虽然在 UserService 内部注入自己调用自己的 createUserPublic 可以正确实现事务,但更合理的实现方式是,让 Controller 直接调用之前定义的 UserService 的 createUserPublic方法,因为注入自己调用自己很奇怪,也不符合分层实现的规范:

@GetMapping("right2")
publicintright2(@RequestParam("name")Stringname){
    try{
        userService.createUserPublic(newUserEntity(name));
    }
    catch(Exceptionex){
        log.error("createuserfailedbecause{}",ex.getMessage());
    }
    returnuserService.getUserCount(name);
}

我们再通过一张图来回顾下 this 自调用、通过 self 调用,以及在 Controller 中调用UserService 三种实现的区别:

 

通过 this 自调用,没有机会走到 Spring 的代理类;后两种改进方案调用的是 Spring 注入的 UserService,通过代理调用才有机会对 createUserPublic 方法进行动态增强。

这里,我还有一个小技巧,强烈建议你在开发时打开相关的 Debug 日志,以方便了解Spring 事务实现的细节,并及时判断事务的执行情况

我们的 Demo 代码使用 JPA 进行数据库访问,可以这么开启 Debug 日志:

logging.level.org.springframework.orm.jpa=DEBUG

开启日志后,我们再比较下在 UserService 中通过 this 调用和在 Controller 中通过注入的UserService Bean 调用 createUserPublic 区别。很明显,this 调用因为没有走代理,事务没有在 createUserPublic 方法上生效,只在 Repository 的 save 方法层面生效:

//在UserService中通过this调用public的createUserPublic
[10:10:19.913][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
//在Controller中通过注入的UserServiceBean调用createUserPublic
[10:10:47.750][http-nio-45678-exec-6][DEBUG][o.s.orm.jpa.JpaTransactionManag

你可能还会考虑一个问题,这种实现在 Controller 里处理了异常显得有点繁琐,还不如直接把 createUserWrong2 方法加上 @Transactional 注解,然后在 Controller 中直接调用这个方法。这样一来,既能从外部(Controller 中)调用 UserService 中的方法,方法又是 public 的能够被动态代理 AOP 增强。

你可以试一下这种方法,但很容易就会踩第二个坑,即因为没有正确处理异常,导致事务即便生效也不一定能回滚。

二、事务即便生效也不一定能回滚

通过 AOP 实现事务处理可以理解为,使用 try…catch…来包裹标记了 @Transactional 注解的方法,当方法出现了异常并且满足一定条件的时候,在 catch 里面我们可以设置事务回滚,没有异常则直接提交事务。

这里的“一定条件”,主要包括两点。

第一,只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。在 Spring的 TransactionAspectSupport 里有个 invokeWithinTransaction 方法,里面就是处理事务的逻辑。可以看到,只有捕获到异常才能进行后续事务处理:

try{
    //Thisisanaroundadvice:Invokethenextinterceptorinthechain.//Thiswillnormallyresultinatargetobjectbeinginvoked.
    retVal=invocation.proceedWithInvocation();
}
catch(Throwableex){
    //targetinvocationexception
    completeTransactionAfterThrowing(txInfo,ex);
    throwex;
}
finally{
    cleanupTransactionInfo(txInfo);
}

第二,**默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring才会回滚事务

打开 Spring 的 DefaultTransactionAttribute 类能看到如下代码块,可以发现相关证据,通过注释也能看到 Spring 这么做的原因,大概的意思是受检异常一般是业务异常,或者说是类似另一种方法的返回值,出现这样的异常可能业务还能完成,所以不会主动回滚;而Error 或 RuntimeException 代表了非预期的结果,应该回滚:

/**
    *ThedefaultbehaviorisaswithEJB:rollbackonuncheckedexception
    *({@linkRuntimeException}),assuminganunexpectedoutcomeoutsideofany
    *businessrules.Additionally,wealsoattempttorollbackon{@linkError}w
    *isclearlyanunexpectedoutcomeaswell.Bycontrast,acheckedexceptioni
    *consideredabusinessexceptionandthereforearegularexpectedoutcomeof
    *transactionalbusinessmethodi.e.akindofalternativereturnvaluewhich,
    *stillallowsforregularcompletionofresourceoperations.
    *p>ThisislargelyconsistentwithTransactionTemplate'sdefaultbehavior,
    *exceptthatTransactionTemplatealsorollsbackonundeclaredcheckedexcept
    *(acornercase).Fordeclarativetransactions,weexpectcheckedexceptions
    *intentionallydeclaredasbusinessexceptions,leadingtoacommitbydefaul
    *@seeorg.springframework.transaction.support.TransactionTemplate#execute
*/
@Override
publicbooleanrollbackOn(Throwableex){
    return(exinstanceofRuntimeException||exinstanceofError);
}

接下来,我和你分享 2 个反例。

重新实现一下 UserService 中的注册用户操作:

  • 在 createUserWrong1 方法中会抛出一个 RuntimeException,但由于方法内 catch 了所有异常,异常无法从方法传播出去,事务自然无法回滚。
  • 在 createUserWrong2 方法中,注册用户的同时会有一次 otherTask 文件读取操作,如果文件读取失败,我们希望用户注册的数据库操作回滚。虽然这里没有捕获异常,但因为 otherTask 方法抛出的是受检异常,createUserWrong2 传播出去的也是受检异常,事务同样不会回滚。
@Service
@Slf4j
publicclassUserService{
    @Autowired
    privateUserRepositoryuserRepository;
    //异常无法传播出方法,导致事务无法回滚
    @Transactional
    publicvoidcreateUserWrong1(Stringname){
        try{
            userRepository.save(newUserEntity(name));
            thrownewRuntimeException("error");
        }
        catch(Exceptionex){
            log.error("createuserfailed",ex);
        }
    }
    //即使出了受检异常也无法让事务回滚
    @Transactional
    publicvoidcreateUserWrong2(Stringname)throwsIOException{
        userRepository.save(newUserEntity(name));
        otherTask();
    }
    //因为文件不存在,一定会抛出一个IOException
    privatevoidotherTask()throwsIOException{
        Files.readAllLines(Paths.get("file-that-not-exist"));
    }
}

Controller 中的实现,仅仅是调用 UserService 的 createUserWrong1 和createUserWrong2 方法,这里就贴出实现了。这 2 个方法的实现和调用,虽然完全避开了事务不生效的坑,但因为异常处理不当,导致程序没有如我们期望的文件操作出现异常时回滚事务。

现在,我们来看下修复方式,以及如何通过日志来验证是否修复成功。针对这 2 种情况,对应的修复方法如下。

第一,如果你希望自己捕获异常进行处理的话,也没关系,可以手动设置让当前事务处于回滚状态:

@Transactional
publicvoidcreateUserRight1(Stringname){
    try{
        userRepository.save(newUserEntity(name));
        thrownewRuntimeException("error");
    }
    catch(Exceptionex){
        log.error("createuserfailed",ex);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

运行后可以在日志中看到 Rolling back 字样,确认事务回滚了。同时,我们还注意到“Transactional code has requested rollback”的提示,表明手动请求回滚:

[22:14:49.352][http-nio-45678-exec-4][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:14:49.353][http-nio-45678-exec-4][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:14:49.353][http-nio-45678-exec-4][DEBUG][o.s.orm.jpa.JpaTransactionManag

第二,在注解中声明,期望遇到所有的 Exception 都回滚事务(来突破默认不回滚受检异常的限制):

@Transactional(rollbackFor=Exception.class)
publicvoidcreateUserRight2(Stringname)throwsIOException{
    userRepository.save(newUserEntity(name));
    otherTask();
}

运行后,同样可以在日志中看到回滚的提示:

[22:10:47.980][http-nio-45678-exec-4][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:10:47.981][http-nio-45678-exec-4][DEBUG][o.s.orm.jpa.JpaTransactionManag

在这个例子中,我们展现的是一个复杂的业务逻辑,其中有数据库操作、IO 操作,在 IO操作出现问题时希望让数据库事务也回滚,以确保逻辑的一致性。在有些业务逻辑中,可能会包含多次数据库操作,我们不一定希望将两次操作作为一个事务来处理,这时候就需要仔细考虑事务传播的配置了,否则也可能踩坑。

三、请确认事务传播配置是否符合自己的业务逻辑

有这么一个场景:一个用户注册的操作,会插入一个主用户到用户表,还会注册一个关联的子用户。我们希望将子用户注册的数据库操作作为一个独立事务来处理,即使失败也不会影响主流程,即不影响主用户的注册。

接下来,我们模拟一个实现类似业务逻辑的 UserService:

@Autowired
privateUserRepositoryuserRepository;

@Autowired
privateSubUserServicesubUserService;

@Transactional
publicvoidcreateUserWrong(UserEntityentity){
    createMainUser(entity);
    subUserService.createSubUserWithExceptionWrong(entity);
}
privatevoidcreateMainUser(UserEntityentity){
    userRepository.save(entity);
    log.info("createMainUserfinish");
}

SubUserService 的 createSubUserWithExceptionWrong 实现正如其名,因为最后我们抛出了一个运行时异常,错误原因是用户状态无效,所以子用户的注册肯定是失败的。我们期望子用户的注册作为一个事务单独回滚,不影响主用户的注册,这样的逻辑可以实现吗?

@Service
@Slf4j
publicclassSubUserService{
    @Autowired
    privateUserRepositoryuserRepository;
    @Transactional
    publicvoidcreateSubUserWithExceptionWrong(UserEntityentity){
        log.info("createSubUserWithExceptionWrongstart");
        userRepository.save(entity);
        thrownewRuntimeException("invalidstatus");
    }
}

我们在 Controller 里实现一段测试代码,调用 UserService:

@GetMapping("wrong")
publicintwrong(@RequestParam("name")Stringname){
    try{
        userService.createUserWrong(newUserEntity(name));
    }
    catch(Exceptionex){
        log.error("createUserWrongfailed,reason:{}",ex.getMessage());
    }
    returnuserService.getUserCount(name);
}

调用后可以在日志中发现如下信息,很明显事务回滚了,最后 Controller 打出了创建子用户抛出的运行时异常:

[22:50:42.866][http-nio-45678-exec-8][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:50:42.869][http-nio-45678-exec-8][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:50:42.869][http-nio-45678-exec-8][ERROR][t.d.TransactionPropagationCont

你马上就会意识到,不对呀,因为运行时异常逃出了 @Transactional 注解标记的createUserWrong 方法,Spring 当然会回滚事务了。如果我们希望主方法不回滚,应该把子方法抛出的异常捕获了。

也就是这么改,把 subUserService.createSubUserWithExceptionWrong 包裹上catch,这样外层主方法就不会出现异常了:

@Transactional
publicvoidcreateUserWrong2(UserEntityentity){
    createMainUser(entity);
    try{
        subUserService.createSubUserWithExceptionWrong(entity);
    }
    catch(Exceptionex){
        //虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了
        log.error("createsubusererror:{}",ex.getMessage());
    }
}

运行程序后可以看到如下日志:

[22:57:21.722][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.739][http-nio-45678-exec-3][INFO][t.c.transaction.demo3.SubUserSe
[22:57:21.739][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.739][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.740][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.740][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.740][http-nio-45678-exec-3][ERROR][.g.t.c.transaction.demo3.UserSe
[22:57:21.740][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.740][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.743][http-nio-45678-exec-3][DEBUG][o.s.orm.jpa.JpaTransactionManag
[22:57:21.743][http-nio-45678-exec-3][ERROR][t.d.TransactionPropagationContorg.springframework.transaction.UnexpectedRollbackException:Transactionsilent
...

需要注意以下几点:

  • 如第 1 行所示,对 createUserWrong2 方法开启了异常处理;
  • 如第 5 行所示,子方法因为出现了运行时异常,标记当前事务为回滚;
  • 如第 7 行所示,主方法的确捕获了异常打印出了 create sub user error 字样;
  • 如第 9 行所示,主方法提交了事务;
  • 奇怪的是,如第 11 行和 12 行所示,Controller 里出现了一个UnexpectedRollbackException,异常描述提示最终这个事务回滚了,而且是静默回滚的。之所以说是静默,是因为 createUserWrong2 方法本身并没有出异常,只不过提交后发现子方法已经把当前事务设置为了回滚,无法完成提交。

这挺反直觉的。我们之前说,出了异常事务不一定回滚,这里说的却是不出异常,事务也不一定可以提交。原因是,主方法注册主用户的逻辑和子方法注册子用户的逻辑是同一个事务,子逻辑标记了事务需要回滚,主逻辑自然也不能提交了。

看到这里,修复方式就很明确了,想办法让子逻辑在独立事务中运行,也就是改一下SubUserService 注册子用户的方法,为注解加上 propagation =Propagation.REQUIRES_NEW 来设置 REQUIRES_NEW 方式的事务传播策略,也就是执行到这个方法时需要开启新的事务,并挂起当前事务:

@Transactional(propagation=Propagation.REQUIRES_NEW)
publicvoidcreateSubUserWithExceptionRight(UserEntityentity){
    log.info("createSubUserWithExceptionRightstart");
    userRepository.save(entity);
    thrownewRuntimeException("invalidstatus");
}

主方法没什么变化,同样需要捕获异常,防止异常漏出去导致主事务回滚,重新命名为createUserRight:

@Transactional
publicvoidcreateUserRight(UserEntityentity){
    createMainUser(entity);
    try{
        subUserService.createSubUserWithExceptionRight(entity);
    }
    catch(Exceptionex){
        //捕获异常,防止主方法回滚
        log.error("createsubusererror:{}",ex.getMessage());
    }
}

改造后,重新运行程序可以看到如下的关键日志:

  • 第 1 行日志提示我们针对 createUserRight 方法开启了主方法的事务;
  • 第 2 行日志提示创建主用户完成;
  • 第 3 行日志可以看到主事务挂起了,开启了一个新的事务,针对createSubUserWithExceptionRight 方案,也就是我们的创建子用户的逻辑;
  • 第 4 行日志提示子方法事务回滚;
  • 第 5 行日志提示子方法事务完成,继续主方法之前挂起的事务;
  • 第 6 行日志提示主方法捕获到了子方法的异常;
  • 第 8 行日志提示主方法的事务提交了,随后我们在 Controller 里没看到静默回滚的异常。
[23:17:20.935][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
[23:17:21.079][http-nio-45678-exec-1][INFO][.g.t.c.transaction.demo3.UserSe
[23:17:21.082][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
[23:17:21.153][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
[23:17:21.160][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
[23:17:21.161][http-nio-45678-exec-1][ERROR][.g.t.c.transaction.demo3.UserSe
[23:17:21.161][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag
[23:17:21.161][http-nio-45678-exec-1][DEBUG][o.s.orm.jpa.JpaTransactionManag

运行测试程序看到如下结果,getUserCount 得到的用户数量为 1,代表只有一个用户也就是主用户注册完成了,符合预期:

 

四、总结

本文针对业务代码中最常见的使用数据库事务的方式,即 Spring 声明式事务,与你总结了使用上可能遇到的三类坑,包括:

  • 第一,因为配置不正确,导致方法上的事务没生效。我们务必确认调用 @Transactional 注解标记的方法是 public 的,并且是通过 Spring 注入的 Bean 进行调用的。
  • 第二,因为异常处理不正确,导致事务虽然生效但出现异常时没回滚。Spring 默认只会对标记 @Transactional 注解的方法出现了 RuntimeException 和 Error 的时候回滚,如果我们的方法捕获了异常,那么需要通过手动编码处理事务回滚。如果希望 Spring 针对其他异常也可以回滚,那么可以相应配置 @Transactional 注解的 rollbackFor 和noRollbackFor 属性来覆盖其默认设置。
  • 第三,如果方法涉及多次数据库操作,并希望将它们作为独立的事务进行提交或回滚,那么我们需要考虑进一步细化配置事务传播方式,也就是 @Transactional 注解的Propagation 属性。

可见,正确配置事务可以提高业务项目的健壮性。但,又因为健壮性问题往往体现在异常情况或一些细节处理上,很难在主流程的运行和测试中发现,导致业务代码的事务处理逻辑往往容易被忽略,因此我在代码审查环节一直很关注事务是否正确处理。

如果你无法确认事务是否真正生效,是否按照预期的逻辑进行,可以尝试打开 Spring 的部分 Debug 日志,通过事务的运作细节来验证。也建议你在单元测试时尽量覆盖多的异常场景,这样在重构时,也能及时发现因为方法的调用方式、异常处理逻辑的调整,导致的事务失效问题。

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

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

相关文章

试剂的制备丨艾美捷逆转录病毒定量试剂盒方案

QuickTiter逆转录病毒定量试剂盒提供了一种测定逆转录病毒滴度的快速方法。该测定法测量逆转录病毒的病毒核酸含量&#xff0c;可以在纯化病毒之前或之后进行。 Cell Biolabs艾美捷QuickTiter™ 逆转录病毒定量试剂盒不涉及细胞感染&#xff1b;相反&#xff0c;它专门测量纯化…

Linux | 可重入函数 | volatile | SIGCHLD信号

文章目录可重入函数volatilevolatile和const同时修饰变量SIGCHLD信号可重入函数 当一个函数可以被两个流调用&#xff0c;我们称该函数具有重入特征 如果一个函数被重入后可能导致内存泄漏的问题&#xff0c;我们称该函数为不可重入函数&#xff0c;反之&#xff0c;一个函数…

BER转Q

BER转Q Q(2^0.5)*erfcinv(2*BER) Q_dB20*log10(Q) 1、为什么要这样转&#xff1a; 暂时我也不知道&#xff0c;知道了再来补 2、关于erfcinv&#xff1a; yerf(x) 误差函数 yerfc(x) 互补误差函数 yerfinv(x) 逆误差函数(误差函数的反函数) yerfcinv(x) 逆互补误差函数(互补误差…

测试网络、磁盘使用情况和最大性能

1、测最大网络带宽&#xff0c;当前流量 查看网卡信息&#xff1a;ethtool p2p1 最简单的方法是用scp复制一个大文件&#xff0c;例如50G&#xff0c;复制时间要长&#xff0c;至少30分钟。之前在数据库迁移时&#xff0c;发现网速对迁移速度导致了重大影响&#xff0c;我们的…

基于go-micro微服务的实战-Gateway网关层的限流降级(八)

基于go-micro微服务的实战-Gateway网关层的限流降级(八) 文章最后附带完整代码 这一节主要是在Gateway网关层&#xff0c;基于go-micro的装饰器引入限流和降级。限流降级用的是开源库hystrix,类似java的hystrix&#xff0c;这里不做具体介绍和使用&#xff0c;可自行查看文档。…

车载ECU嵌入式设备的诊断测试 – DTC

作者 | 李伟 上海控安安全测评中心安全测评部总监 来源 | 鉴源实验室 01 DTC-Diagnostic Trouble Code&#xff08;诊断故障代码&#xff09; 车辆在运行的过程当中&#xff0c;控制器会监控状态&#xff0c;特定故障发生时控制器会记录这些故障。车辆送4S店进行维修保养时&…

Numpy入门[4]——数组类型

Numpy入门[4]——数组类型 参考&#xff1a; https://ailearning.apachecn.org/ 使用Jupyter进行练习 import numpy as np之前已经看过整数数组和布尔数组&#xff0c;除此之外还有浮点数数组和复数数组。 复数数组 a np.array([1 1j , 2 , 3 , 4]) aarray([1.1.j, 2.0.j, …

Java基于PHP+MySQL干洗店管理系统的设计与实现

干洗店管理系统是信息时代的产物,它是干洗店管理的一个好帮手。有了它不再需要繁重的纸质登记,有了它干洗店管理员不在需要繁重的工作,一些收费标准和干洗业务等基本信息可以由管理人员及时的对信息进行查询、更新、修改和删除,方便简易,且时效性高。 干洗店管理系统是一个典型…

java 中使用BigDecimal 解决科学计数法问题

一 BigDecimal的Api 1.1 常用方法介绍 ROUND_CEILING 向正无穷方向舍入 ROUND_DOWN 向零方向舍入 ROUND_FLOOR 向负无穷方向舍入 ROUND_HALF_DOWN 向&#xff08;距离&#xff09;最近的一边舍入&#xff0c;除非两边&#xff08;的距离&#xff09;是相等,如果是…

在python 深度学习Keras中计算神经网络集成模型

神经网络的训练过程是一个挑战性的优化过程&#xff0c;通常无法收敛。最近我们被客户要求撰写关于深度学习的研究报告&#xff0c;包括一些图形和统计输出。 这可能意味着训练结束时的模型可能不是稳定的或表现最佳的权重集&#xff0c;无法用作最终模型。 解决此问题的一种…

MyBatis-Plus

MyBatis-Plus 1、简介 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 润物无声 只做增强不做改变&#xff0c;引入它不会对现有工程产生影响&#xff0c;…

数据结构学习笔记(Ⅷ):排序

目录 1 排序基础 1.1 排序的基本概念 2 排序算法 2.1 插入排序 1.思想 2.实现 3.效率分析 4.优化 2.2 希尔排序 1.定义 2.实现 3.效率分析 3 交换排序 3.1 冒泡排序 1.定义 2.实现 3.效率分析 3.2 快速排序 1.算法思想 2.实现 3.效率分析 4 选择排序 4.…

第4章 SpringBoot与Web应用

文章目录第4章 SpringBoot与Web应用4.1 配置Tomcat运行4.2 https安全访问4.3 数据验证4.4 配置错误页4.5 全局异常处理4.6 文件上传4.6.1 基础上传4.6.2 上传文件限制4.6.3 上传多个文件4.7 拦截器4.8 AOP拦截器4.9 本章小结4.9 本章小结第4章 SpringBoot与Web应…

[附源码]计算机毕业设计病人跟踪治疗信息管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Linux系统移植二:生成fsbl引导文件并制作BOOT.bin

前情提要 对于ZYNQ而言&#xff0c;在引导过程中&#xff0c;先运行FSBL来设置PS&#xff0c;然后运行U-Boot用于加载Linux内核映像并引导Linux Linux系统移植一&#xff1a;移植U-BOOT 添加自己的板子并编译&#xff08;非petalinux版&#xff09; 一文中已成功生成了u-boot…

基于MPPT的PV光伏发电simulink建模和仿真

目录 1.算法描述 2.matlab算法仿真效果 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 MPPT控制器的全称是“最大功率点跟踪”&#xff08;Maximum Power Point Tracking&#xff09;太阳能控制器&#xff0c;是传统太阳能充放电控制器的升级换代产品。MPPT控制器能够实时侦测…

ManiSkill 2022机器学习顶会ICLR上的世界顶尖机械臂大赛赛题解读,演示轨迹转换,点云查看

1.赛事相关信息 点击查看 2.赛题分析 软体对GPU要求较高&#xff0c;环境配置复杂&#xff0c;选择刚体环境先以模仿学习/强化学习的刚体环境为基础&#xff0c;后期再考虑无限制刚体环境部分任务&#xff08;如将物块移动到指定位置&#xff09;&#xff0c;存在相机之外的…

Day818.电商系统的分布式事务调优 -Java 性能调优实战

电商系统的分布式事务调优 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于电商系统的分布式事务调优。 一个线上事故&#xff0c;在一次 DBA 完成单台数据库线上补丁后&#xff0c;系统偶尔会出现异常报警&#xff0c;开发工程师很快就定位到了数据库异常问题。 具…

SQL通用语法与DDL操作

学习笔记 sql通用语法 1 sql语句可以单行或多行书写&#xff0c;以分号结尾&#xff1b; 2 sql语句可以使用空格/缩进来增强语句的可读性&#xff1b; 3 mysql数据库的sql语句不区分大小写 4 单行注释&#xff1a;-- 内容 或 # 内容 多行注释&#xff1a; /* 内容 */ sql语句…

【地图之vue-baidu-map】点击获取坐标(点Marker)、坐标集(多边形polygon)

点击获取坐标&#xff08;点Marker&#xff09; 官网链接&#xff1a;Vue Baidu Map 需求 1.点击某点设置该点为中心点 2.获取点的经纬度 3.确定选取成功&#xff0c;取消就不赋值。 实现步骤 第一步&#xff1a;设置打开弹窗的地方 <el-button click"clickAdd…