Spring 事務常見錯誤(上)

通過上一章的學習,我們了解了 Spring Data 操作數據庫的一些常見問題。這一章我們聊一聊數據庫操作中的一個非常重要的話題——事務管理。

Spring 事務管理包含兩種配置方式,第一種是使用 XML 進行模糊匹配,綁定事務管理;第二種是使用注解,這種方式可以對每個需要進行事務處理的方法進行單獨配置,你只需要添加上 @Transactional,然后在注解內添加屬性配置即可。在我們的錯誤案例示范中,我們統一使用更為方便的注解式方式。

另外,補充一點,Spring 在初始化時,會通過掃描攔截對事務的方法進行增強。如果目標方法存在事務,Spring 就會創建一個 Bean 對應的代理(Proxy)對象,并進行相關的事務處理操作。

在正式開始講解事務之前,我們需要搭建一個簡單的 Spring 數據庫的環境。這里我選擇了當下最為流行的 MySQL + Mybatis 作為數據庫操作的基本環境。為了正常使用,我們還需要引入一些配置文件和類,簡單列舉一下。

1.數據庫配置文件 jdbc.properties,配置了數據連接信息。

jdbc.driver=com.mysql.cj.jdbc.Driver

jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false

jdbc.username=root
jdbc.password=pass

?2.JDBC 的配置類,從上述 jdbc.properties 加載相關配置項,并創建 JdbcTemplate、DataSource、TransactionManager 相關的 Bean 等。

public class JdbcConfig {@Value("${jdbc.driver}")private String driver;@Value("${jdbc.url}")private String url;@Value("${jdbc.username}")private String username;@Value("${jdbc.password}")private String password;@Bean(name = "jdbcTemplate")public JdbcTemplate createJdbcTemplate(DataSource dataSource) {return new JdbcTemplate(dataSource);}@Bean(name = "dataSource")public DataSource createDataSource() {DriverManagerDataSource ds = new DriverManagerDataSource();ds.setDriverClassName(driver);ds.setUrl(url);ds.setUsername(username);ds.setPassword(password);return ds;}@Bean(name = "transactionManager")public PlatformTransactionManager      createTransactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}

?3.應用配置類,通過注解的方式,配置了數據源、MyBatis Mapper 的掃描路徑以及事務等。

@Configuration
@ComponentScan
@Import({JdbcConfig.class})
@PropertySource("classpath:jdbc.properties")
@MapperScan("com.spring.puzzle.others.transaction.example1")
@EnableTransactionManagement
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AppConfig {public static void main(String[] args) throws Exception {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);}
}

完成了上述基礎配置和代碼后,我們開始進行案例的講解。

案例 1:unchecked 異常與事務回滾

在系統中,我們需要增加一個學生管理的功能,每一位新生入學后,都會往數據庫里存入學生的信息。我們引入了一個學生類 Student 和與之相關的 Mapper。

其中,Student 定義如下:

public class Student implements Serializable {private Integer id;private String realname;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getRealname() {return realname;}public void setRealname(String realname) {this.realname = realname;}
}

Student 對應的 Mapper 類定義如下:

@Mapper
public interface StudentMapper {@Insert("INSERT INTO `student`(`realname`) VALUES (#{realname})")void saveStudent(Student student);
}

對應數據庫表的 Schema 如下:

CREATE TABLE `student` (`id` int(11) NOT NULL AUTO_INCREMENT,`realname` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

業務類 StudentService,其中包括一個保存的方法 saveStudent。執行一下保存,一切正常。

接下來,我們想要測試一下這個事務會不會回滾,于是就寫了這樣一段邏輯:如果發現用戶名是小明,就直接拋出異常,觸發事務的回滾操作。

@Service
public class StudentService {@Autowiredprivate StudentMapper studentMapper;@Transactionalpublic void saveStudent(String realname) throws Exception {Student student = new Student();student.setRealname(realname);studentMapper.saveStudent(student);if (student.getRealname().equals("小明")) {throw new Exception("該學生已存在");}}
}

然后使用下面的代碼來測試一下,保存一個叫小明的學生,看會不會觸發事務的回滾。

public class AppConfig {public static void main(String[] args) throws Exception {ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);StudentService studentService = (StudentService) context.getBean("studentService");studentService.saveStudent("小明");}
}

?執行結果打印出了這樣的信息:

Exception in thread "main" java.lang.Exception: 該學生已存在

at?com.spring.puzzle.others.transaction.example1.StudentService.saveStudent(StudentService.java:23)

可以看到,異常確實被拋出來,但是檢查數據庫,你會發現數據庫里插入了一條新的記錄。

但是我們的常規思維可能是:在 Spring 里,拋出異常,就會導致事務回滾,而回滾以后,是不應該有數據存入數據庫才對啊。而在這個案例中,異常也拋了,回滾卻沒有如期而至,這是什么原因呢?我們需要研究一下 Spring 的源碼,來找找答案。

案例解析

我們通過 debug 沿著 saveStudent 繼續往下跟,得到了一個這樣的調用棧:

?從這個調用棧中我們看到了熟悉的 CglibAopProxy,另外事務本質上也是一種特殊的切面,在創建的過程中,被 CglibAopProxy 代理。事務處理的攔截器是 TransactionInterceptor,它支撐著整個事務功能的架構,我們來分析下這個攔截器是如何實現事務特性的。

首先,TransactionInterceptor 繼承類 TransactionAspectSupport,實現了接口 MethodInterceptor。當執行代理類的目標方法時,會觸發 invoke()。由于我們的關注重點是在異常處理上,所以直奔主題,跳到異常處理相關的部分。當它 catch 到異常時,會調用 completeTransactionAfterThrowing 方法做進一步處理。

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {//省略非關鍵代碼Object retVal;try {retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {completeTransactionAfterThrowing(txInfo, ex);throw ex;}finally {cleanupTransactionInfo(txInfo);}//省略非關鍵代碼
}

?在 completeTransactionAfterThrowing 的代碼中,有這樣一個方法 rollbackOn(),這是事務的回滾的關鍵判斷條件。當這個條件滿足時,會觸發 rollback 操作,事務回滾。

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {//省略非關鍵代碼//判斷是否需要回滾if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {try {//執行回滾txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());}catch (TransactionSystemException ex2) {ex2.initApplicationException(ex);throw ex2;}catch (RuntimeException | Error ex2) {throw ex2;}}//省略非關鍵代碼
}

rollbackOn() 其實包括了兩個層級,具體可參考如下代碼:

public boolean rollbackOn(Throwable ex) {// 層級 1:根據"rollbackRules"及當前捕獲異常來判斷是否需要回滾RollbackRuleAttribute winner = null;int deepest = Integer.MAX_VALUE;if (this.rollbackRules != null) {for (RollbackRuleAttribute rule : this.rollbackRules) {// 當前捕獲的異常可能是回滾“異常”的繼承體系中的“一員”int depth = rule.getDepth(ex);if (depth >= 0 && depth < deepest) {deepest = depth;winner = rule;}}}// 層級 2:調用父類的 rollbackOn 方法來決策是否需要 rollbackif (winner == null) {return super.rollbackOn(ex);}return !(winner instanceof NoRollbackRuleAttribute);
}

1. RuleBasedTransactionAttribute 自身的 rollbackOn()

當我們在 @Transactional 中配置了 rollbackFor,這個方法就會用捕獲到的異常和 rollbackFor 中配置的異常做比較。如果捕獲到的異常是 rollbackFor 配置的異常或其子類,就會直接 rollback。在我們的案例中,由于在事務的注解中沒有加任何規則,所以這段邏輯處理其實找不到規則(即 winner == null),進而走到下一步。

2. RuleBasedTransactionAttribute 父類 DefaultTransactionAttribute 的 rollbackOn()

如果沒有在@Transactional 中配置 rollback 屬性,或是捕獲到的異常和所配置異常的類型不一致,就會繼續調用父類的 rollbackOn() 進行處理。

而在父類的 rollbackOn() 中,我們發現了一個重要的線索,只有在異常類型為 RuntimeException 或者 Error 的時候才會返回 true,此時,會觸發 completeTransactionAfterThrowing 方法中的 rollback 操作,事務被回滾。

public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);
}

查到這里,真相大白,Spring 處理事務的時候,如果沒有在 @Transactional 中配置 rollback 屬性,那么只有捕獲到 RuntimeException 或者 Error 的時候才會觸發回滾操作。而我們案例拋出的異常是 Exception,又沒有指定與之匹配的回滾規則,所以我們不能觸發回滾。

問題修正

從上述案例解析中,我們了解到,Spring 在處理事務過程中,并不會對 Exception 進行回滾,而會對 RuntimeException 或者 Error 進行回滾。

這么看來,修改方法也可以很簡單,只需要把拋出的異常類型改成 RuntimeException 就可以了。于是這部分代碼就可以修改如下:

@Service
public class StudentService {@Autowiredprivate StudentMapper studentMapper;@Transactionalpublic void saveStudent(String realname) throws Exception {Student student = new Student();student.setRealname(realname);studentMapper.saveStudent(student);if (student.getRealname().equals("小明")) {throw new RuntimeException("該用戶已存在");}}

再執行一下,這時候異常會正常拋出,數據庫里不會有新數據產生,表示這時候 Spring 已經對這個異常進行了處理,并將事務回滾。

但是很明顯,這種修改方法看起來不夠優美,畢竟我們的異常有時候是固定死不能隨意修改的。所以結合前面的案例分析,我們還有一個更好的修改方式。

具體而言,我們在解析 RuleBasedTransactionAttribute.rollbackOn的代碼時提到過 rollbackFor 屬性的處理規則。也就是我們在@Transactional 的 rollbackFor 加入需要支持的異常類型(在這里是 Exception)就可以匹配上我們拋出的異常,進而在異常拋出時進行回滾。

于是我們可以完善下案例中的注解,修改后代碼如下:

@Transactional(rollbackFor = Exception.class)

再次測試運行,你會發現一切符合預期了。

案例 2:試圖給 private 方法添加事務

接著上一個案例,我們已經實現了保存學生信息的功能。接下來,我們來優化一下邏輯,讓學生的創建和保存邏輯分離,于是我就對代碼做了一些重構,把 Student 的實例創建和保存邏輯拆到兩個方法中分別進行。然后,把事務的注解 @Transactional 加在了保存數據庫的方法上。

@Service
public class StudentService {@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate StudentService studentService;public void saveStudent(String realname) throws Exception {Student student = new Student();student.setRealname(realname);studentService.doSaveStudent(student);}@Transactionalprivate void doSaveStudent(Student student) throws Exception {studentMapper.saveStudent(student);if (student.getRealname().equals("小明")) {throw new RuntimeException("該用戶已存在");}}
}

執行的時候,繼續傳入參數“小明”,看看執行結果是什么樣子?

異常正常拋出,事務卻沒有回滾。明明是在方法上加上了事務的注解啊,為什么沒有生效呢?我們還是從 Spring 源碼中找答案。

案例解析

通過 debug,我們一步步尋找到了問題的根源,得到了以下調用棧。我們通過 Spring 的源碼來解析一下完整的過程。

前一段是 Spring 創建 Bean 的過程。當 Bean 初始化之后,開始嘗試代理操作,這個過程是從 AbstractAutoProxyCreator 里的 postProcessAfterInitialization 方法開始處理的:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}

?我們一路往下找,暫且略過那些非關鍵要素的代碼,直到到了 AopUtils 的 canApply 方法。這個方法就是針對切面定義里的條件,確定這個方法是否可以被應用創建成代理。其中有一段 methodMatcher.matches(method, targetClass) 是用來判斷這個方法是否符合這樣的條件:

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {//省略非關鍵代碼for (Class<?> clazz : classes) {Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {if (introductionAwareMethodMatcher != null ?introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :methodMatcher.matches(method, targetClass)) {return true;}}}return false;
}

從 matches() 調用到了 AbstractFallbackTransactionAttributeSource 的 getTransactionAttribute:

public boolean matches(Method method, Class<?> targetClass) {//省略非關鍵代碼TransactionAttributeSource tas = getTransactionAttributeSource();return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}

其中,getTransactionAttribute 這個方法是用來獲取注解中的事務屬性,根據屬性確定事務采用什么樣的策略。

public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {//省略非關鍵代碼TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);//省略非關鍵代碼}
}

接著調用到 computeTransactionAttribute 這個方法,其主要功能是根據方法和類的類型確定是否返回事務屬性,執行代碼如下:

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {//省略非關鍵代碼if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {return null;}//省略非關鍵代碼
}

這里有這樣一個判斷 allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers()) ,當這個判斷結果為 true 的時候返回 null,也就意味著這個方法不會被代理,從而導致事務的注解不會生效。那此處的判斷值到底是不是 true 呢?我們可以分別看一下。

條件 1:allowPublicMethodsOnly()

allowPublicMethodsOnly 返回了 AnnotationTransactionAttributeSource 的 publicMethodsOnly 屬性。

protected boolean allowPublicMethodsOnly() {return this.publicMethodsOnly;
}

而這個 publicMethodsOnly 屬性是通過 AnnotationTransactionAttributeSource 的構造方法初始化的,默認為 true。

public AnnotationTransactionAttributeSource() {this(true);
}

條件 2:Modifier.isPublic()

這個方法根據傳入的 method.getModifiers() 獲取方法的修飾符。該修飾符是 java.lang.reflect.Modifier 的靜態屬性,對應的幾類修飾符分別是:PUBLIC: 1,PRIVATE: 2,PROTECTED: 4。這里面做了一個位運算,只有當傳入的方法修飾符是 public 類型的時候,才返回 true。

public static boolean isPublic(int mod) {return (mod & PUBLIC) != 0;
}

綜合上述兩個條件,你會發現,只有當注解為事務的方法被聲明為 public 的時候,才會被 Spring 處理。

問題修正

了解了問題的根源以后,解決它就變得很簡單了,我們只需要把它的修飾符從 private 改成 public 就可以了。

不過需要額外補充的是,我們調用這個加了事務注解的方法,必須是調用被 Spring AOP 代理過的方法,也就是不能通過類的內部調用或者通過 this 的方式調用。所以我們的案例的 StudentService,它含有一個自動裝配(Autowired)了自身(StudentService)的實例來完成代理方法的調用。這個問題我們在之前 Spring AOP 的代碼解析中重點強調過,此處就不再詳述了。

@Service
public class StudentService {@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate StudentService studentService;public void saveStudent(String realname) throws Exception {Student student = new Student();student.setRealname(realname);studentService.doSaveStudent(student);}@Transactionalpublic void doSaveStudent(Student student) throws Exception {studentMapper.saveStudent(student);if (student.getRealname().equals("小明")) {throw new RuntimeException("該學生已存在");}}
}

重新運行一下,異常正常拋出,數據庫也沒有新數據產生,事務生效了,問題解決。

Exception in thread "main" java.lang.RuntimeException:該學生已存在?

at com.spring.puzzle.others.transaction.example2.StudentService.doSaveStudent(StudentService.java:27)
?

重點回顧

通過以上兩個案例,相信你對 Spring 的聲明式事務機制已經有了進一步的了解,最后總結下重點:

  • Spring 支持聲明式事務機制,它通過在方法上加上 @Transactional,表明該方法需要事務支持。于是,在加載的時候,根據 @Transactional 中的屬性,決定對該事務采取什么樣的策略;
  • @Transactional 對 private 方法不生效,所以我們應該把需要支持事務的方法聲明為 public 類型;
  • Spring 處理事務的時候,默認只對 RuntimeException 和 Error 回滾,不會對 Exception 回滾,如果有特殊需要,需要額外聲明,例如指明 Transactional 的屬性 rollbackFor 為 Exception.class

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/711496.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/711496.shtml
英文地址,請注明出處:http://en.pswp.cn/news/711496.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

洗澡、泡腳真的能養生? 皮膚科醫生來科普

現如今人們越來越注重健康與養生&#xff0c;除了枸杞、生姜等食補外&#xff0c;各種保健方法和保健產品也層出不窮&#xff0c;還有泡腳、洗涼水澡等養生延緩衰老的方式也廣泛流行&#xff0c;那么泡腳與洗涼水澡真的有用嗎?西安國際醫學中心醫院皮膚科主任高鵬程特意進行了…

Timeplus-proton流處理器調研

概念 Timeplus是一個流處理器。它提供強大的端到端功能&#xff0c;利用開源流引擎Proton來幫助數據團隊快速直觀地處理流數據和歷史數據&#xff0c;可供各種規模和行業的組織使用。它使數據工程師和平臺工程師能夠使用 SQL 釋放流數據價值。 Timeplus 控制臺可以輕松連接到不…

K8S相關小技巧《一》

在實際使用Kubernetes的時候有一些常用的小技巧&#xff0c;在此分享給大家&#xff1a; 獲取用于拉取docker的密鑰的原本值&#xff0c;k8s docker registry pull secret decode&#xff1a; kubectl get secret/registry-pull-secret -n kube-iapply-qa -o json | jq .data…

女性三八節禮物攻略:她無法抗拒的五大禮物

隨著春風的溫柔拂面&#xff0c;我們即將迎來一年一度的三八國際婦女節。這個特別的日子&#xff0c;不僅是對女性貢獻的認可和慶祝&#xff0c;也是向我們生命中的女性表達感激和愛意的絕佳時機。在這個充滿溫馨和敬意的時刻&#xff0c;我們常常在思考&#xff0c;如何用一份…

信息學奧賽一本通1310:【例2.2】車廂重組

1310&#xff1a;【例2.2】車廂重組 時間限制: 1000 ms 內存限制: 65536 KB 提交數: 48051 通過數: 28919 【題目描述】 在一個舊式的火車站旁邊有一座橋&#xff0c;其橋面可以繞河中心的橋墩水平旋轉。一個車站的職工發現橋的長度最多能容納兩節車廂&#xff0c…

elementUI el-table中的對齊問題

用elementUI時&#xff0c;遇到了一個無法對齊的問題&#xff1a;代碼如下&#xff1a; <el-table :data"form.dataList" <el-table-column label"驗收結論" prop"checkResult" width"200"> <template slot-sco…

0005TS函數類型詳解

TypeScript 中的函數類型用于為函數定義參數類型和返回值類型。這提供了一個清晰的契約&#xff0c;指明函數應該如何被調用和期望返回什么類型的結果。以下是 TypeScript 中函數類型的一些基本用法和概念&#xff1a; 函數聲明 在 TypeScript 中&#xff0c;你可以為函數的參…

揭秘!Excel如何成為職場中的價值創造利器

文章目錄 一、Excel在生產力提升中的作用二、Excel在創造價值方面的應用案例三、Excel實用技巧分享四、Excel與其他工具的協同應用五、Excel學習的建議與展望《Excel函數與公式應用大全》亮點內容簡介作者簡介目錄 在當今信息爆炸的時代&#xff0c;數據處理和分析能力已成為職…

AI智能分析網關V4智慧商場方案,打造智慧化商業管理生態

AI智能視頻檢測技術在商場樓宇管理中的應用越來越廣泛。通過實時監控、自動識別異常事件和智能預警&#xff0c;這項技術為商場管理提供了更高效、更安全的保障。今天我們以TSINGSEE青犀視頻AI智能分析網關為例&#xff0c;給大家介紹一下AI視頻智能分析技術如何應用在商場樓宇…

搶單情況下的均衡分配機制

背景&#xff1a; 1、工單有多種類型。 2、客戶提交工單。 3、不同客服受理不同類型工單&#xff0c;受理工單類型存在交叉。 4、按照類型維度實現均衡分配。 方案&#xff1a; 1、為每種類型創建一個工單池&#xff0c;使用隊列&#xff0c;左進右出&#xff1b;客戶提交…

Android AIDL RemoteCallbackLIst

RemoteCallbackLIst 參考地址 RemoteCallbackList 是 Android SDK 中的一個類&#xff0c;用于幫助管理進程之間的回調。它專為進程間通信 (IPC) 場景而設計&#xff0c;在該場景中&#xff0c;應用程序的不同部分甚至不同的應用程序可能在不同的進程中運行。 以下是其關鍵功能…

將所有字母轉化為該字母后的第三個字母,即A->D,B->E

//編寫加密程序&#xff0c;規則&#xff1a;將所有字母轉化為該字母后的第三個字母&#xff0c;即A->D,B->E,C->F,…Y->B,Z->C //小寫字母同上&#xff0c;其他字符不做轉化。輸入&#xff1a;I love 007 輸出&#xff1a;L oryh 007 代碼&#xff1a; #inc…

GVA快速使用

1. clone 代碼&#xff0c; 使用goland打開Server目錄&#xff0c; 使用vsc打開前端web目錄&#xff0c;運行后端&#xff0c;前端 gin-vue-admin后臺管理系統 - 知乎 (zhihu.com) 2.了解端口配置 參考&#xff0c; 基于Go的后臺管理框架Gin-vue-admin_go vue admin-CSDN博客…

配置MMDetection的solov2攻略整理

目錄 一、MMDetection 特性 常見用法 二、ubuntu20.04配置solov2 三、Windows11配置solov2 一、MMDetection MMDetection是一個用于目標檢測的開源框架&#xff0c;由OpenMMLab開發和維護。它提供了豐富的預訓練模型和模塊&#xff0c;可以用于各種目標檢測任務&#xff…

kamacoder 11.共同祖先的C語言奇妙解法

11. 共同祖先 時間限制&#xff1a;1.000S 空間限制&#xff1a;32MB 題目描述 小明發現和小宇有共同祖先&#xff01;現在小明想知道小宇是他的長輩&#xff0c;晚輩&#xff0c;還是兄弟。 輸入描述 輸入包含多組測試數據。每組首先輸入一個整數N&#xff08;N<10&a…

redis的基本數據類型(一)

redis的基本數據類型 1、redis1.1、數據庫分類1.2、NoSQL分類1.3、redis簡介1.4、redis應用1.5、如何學習redis 2、redis的安裝2.1、Windows安裝2.2.1、客戶端redis管理工具 2.2、Linux安裝&#x1f525;2.2.1、redis核心文件2.2.2、啟動方式2.2.3、redis桌面客戶端1、redis命令…

定義類的成員比較函數,并在類的成員函數里面調用

定義一個自定義排序規則的成員函數&#xff0c;然后在類的成員函數中調用 文章目錄 1.聲明為static函數2.使用function3.使用匿名函數 1.聲明為static函數 #include <iostream> #include <algorithm> #include <list> class A { public:A(){std::list<i…

Python進階學習:Pickle模塊--dump()和load()的用法

Python進階學習&#xff1a;Pickle模塊–dump()和load()的用法 &#x1f308; 個人主頁&#xff1a;高斯小哥 &#x1f525; 高質量專欄&#xff1a;Matplotlib之旅&#xff1a;零基礎精通數據可視化、Python基礎【高質量合集】、PyTorch零基礎入門教程&#x1f448; 希望得到您…

MyBatis-Plus 框架中的自定義元對象處理器

目錄 一、代碼展示二、代碼解讀 一、代碼展示 package com.minster.yanapi.handler;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component;import java.util…

JavaScript解構賦值--數組解構賦值與對象解構賦值

前言 解構賦值是JavaScript的一個強大特性&#xff0c;允許從數組或對象中提取數據&#xff0c;并賦值給定義的變量。 對象解構 直接根據屬性名來解構賦值&#xff1a; const person { name: 張三, age: 30 };const { name, age } person;console.log(name); console.lo…