SpringData JPA快速上手之關(guān)聯(lián)查詢及JPQL語句書寫詳解
JPA框架
? 在我們之前編寫的項目中,我們不難發(fā)現(xiàn),實際上大部分的數(shù)據(jù)庫交互操作,到最后都只會做一個事情,那就是把數(shù)據(jù)庫中的數(shù)據(jù)映射為Java中的對象。比如我們要通過用戶名去查找對應(yīng)的用戶,或是通過ID查找對應(yīng)的學(xué)生信息,在使用Mybatis時,我們只需要編寫正確的SQL語句就可以直接將獲取的數(shù)據(jù)映射為對應(yīng)的Java對象,通過調(diào)用Mapper中的方法就能直接獲得實體類,這樣就方便我們在Java中數(shù)據(jù)庫表中的相關(guān)信息了。
? 但是以上這些操作都有一個共性,那就是它們都是通過某種條件去進行查詢,而最后的查詢結(jié)果,都是一個實體類,所以你會發(fā)現(xiàn)你寫的很多SQL語句都是一個套路 select * from xxx where xxx=xxx
,實際上對于這種簡單SQL語句,我們完全可以弄成一個模版來使用,那么能否有一種框架,幫我們把這些相同的套路給封裝起來,直接把這類相似的SQL語句給屏蔽掉,不再由我們編寫,而是讓框架自己去組合拼接。
認識SpringData JPA
首先我們來看一個國外的統(tǒng)計:
在國外JPA幾乎占據(jù)了主導(dǎo)地位,而Mybatis并不像國內(nèi)那樣受待見,所以你會發(fā)現(xiàn),JPA都有SpringBoot的官方直接提供的starter,而Mybatis沒有,直到SpringBoot 3才開始加入到官方模版中。
那么,什么是JPA:
JPA(Java Persistence API)和JDBC類似,也是官方定義的一組接口,但是它相比傳統(tǒng)的JDBC,它是為了實現(xiàn)ORM而生的,即Object-Relationl Mapping,它的作用是在關(guān)系型數(shù)據(jù)庫和對象之間形成一個映射,這樣,我們在具體的操作數(shù)據(jù)庫的時候,就不需要再去和復(fù)雜的SQL語句打交道,只要像平時操作對象一樣操作它就可以了。
其中比較常見的JPA實現(xiàn)有:
- Hibernate:Hibernate是JPA規(guī)范的一個具體實現(xiàn),也是目前使用最廣泛的JPA實現(xiàn)框架之一。它提供了強大的對象關(guān)系映射功能,可以將Java對象映射到數(shù)據(jù)庫表中,并提供了豐富的查詢語言和緩存機制。
- EclipseLink:EclipseLink是另一個流行的JPA實現(xiàn)框架,由Eclipse基金會開發(fā)和維護。它提供了豐富的特性,如對象關(guān)系映射、緩存、查詢語言和連接池管理等,并具有較高的性能和可擴展性。
- OpenJPA:OpenJPA是Apache基金會的一個開源項目,也是JPA規(guī)范的一個實現(xiàn)。它提供了高性能的JPA實現(xiàn)和豐富的特性,如延遲加載、緩存和分布式事務(wù)等。
- TopLink:TopLink是Oracle公司開發(fā)的一個對象關(guān)系映射框架,也是JPA規(guī)范的一個實現(xiàn)。雖然EclipseLink已經(jīng)取代了TopLink成為Oracle推薦的JPA實現(xiàn),但TopLink仍然得到廣泛使用。
JPA:具體的實現(xiàn)交給框架,我們只需要一一對應(yīng)自己設(shè)置的實體類就行,方便很多
而實現(xiàn)JPA規(guī)范的框架一般最常用的就是 Hibernate
,它是一個重量級框架,學(xué)習(xí)難度相比Mybatis也更高一些,而SpringDataJPA也是采用Hibernate框架作為底層實現(xiàn),并對其加以封裝。
官網(wǎng):https://spring.io/projects/spring-data-jpa
使用JPA快速上手
同樣的,我們只需要導(dǎo)入stater依賴即可:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
接著我們可以直接創(chuàng)建一個類,比如用戶類,只需要把一個賬號對應(yīng)的屬性全部定義好即可:
@Data public class Account { int id; String username; String password; }
通過注解形式在屬性上添加數(shù)據(jù)庫映射關(guān)系,這樣就能夠讓JPA知道我們的實體類對應(yīng)的數(shù)據(jù)庫表長啥樣,這里用到了很多注解:
@Data @Entity //表示這個類是一個實體類 @Table(name = "account") //對應(yīng)的數(shù)據(jù)庫中表名稱 public class Account { @GeneratedValue(strategy = GenerationType.IDENTITY) //生成策略,這里配置為自增 @Column(name = "id") //對應(yīng)表中id這一列 @Id //此屬性為主鍵 int id; @Column(name = "username") //對應(yīng)表中username這一列 String username; @Column(name = "password") //對應(yīng)表中password這一列 String password; }
接著修改配置文件,把日志打印給打開:
spring: jpa: #開啟SQL語句執(zhí)行日志信息 show-sql: true hibernate: #配置為檢查數(shù)據(jù)庫表結(jié)構(gòu),沒有時會自動創(chuàng)建 ddl-auto: update
ddl-auto
屬性用于設(shè)置自動表定義,可以實現(xiàn)自動在數(shù)據(jù)庫中為我們創(chuàng)建一個表,表的結(jié)構(gòu)會根據(jù)我們定義的實體類決定,它有以下幾種:
none
: 不執(zhí)行任何操作,數(shù)據(jù)庫表結(jié)構(gòu)需要手動創(chuàng)建。create
: 框架在每次運行時都會刪除所有表,并重新創(chuàng)建。create-drop
: 框架在每次運行時都會刪除所有表,然后再創(chuàng)建,但在程序結(jié)束時會再次刪除所有表。update
: 框架會檢查數(shù)據(jù)庫表結(jié)構(gòu),如果與實體類定義不匹配,則會做相應(yīng)的修改,以保持它們的一致性。validate
: 框架會檢查數(shù)據(jù)庫表結(jié)構(gòu)與實體類定義是否匹配,如果不匹配,則會拋出異常。
這個配置項的作用是為了避免手動管理數(shù)據(jù)庫表結(jié)構(gòu),使開發(fā)者可以更方便地進行開發(fā)和測試,但在生產(chǎn)環(huán)境中,更推薦使用數(shù)據(jù)庫遷移工具來管理表結(jié)構(gòu)的變更。
我們可以在日志中發(fā)現(xiàn),在啟動時執(zhí)行了如下SQL語句:
我們的數(shù)據(jù)庫中對應(yīng)的表已經(jīng)自動創(chuàng)建好了
接著來看如何訪問表,需要創(chuàng)建一個Repository實現(xiàn)類:
@Repository public interface AccountRepository extends JpaRepository<Account, Integer> { }
注意JpaRepository有兩個泛型,前者是具體操作的對象實體,也就是對應(yīng)的表,后者是ID的類型,接口中已經(jīng)定義了比較常用的數(shù)據(jù)庫操作。編寫接口繼承即可,可以直接注入此接口獲得實現(xiàn)類:
@Resource AccountRepository repository; @Test void contextLoads() { Account account = new Account(); account.setUsername("小紅"); account.setPassword("1234567"); System.out.println(repository.save(account).getId()); //使用save來快速插入數(shù)據(jù),并且會返回插入的對象,如果存在自增ID,對象的自增id屬性會自動被賦值,這就很方便了 }
執(zhí)行結(jié)果如下:
同時,查詢操作也很方便:
@Test void contextLoads() { //默認通過通過ID查找的方法,并且返回的結(jié)果是Optional包裝的對象,非常人性化 repository.findById(1).ifPresent(System.out::println); }
得到結(jié)果為:
包括常見的一些計數(shù)、刪除操作等都包含在里面,僅僅配置應(yīng)該接口就能完美實現(xiàn)增刪改查:
我們發(fā)現(xiàn),使用了JPA之后,整個項目的代碼中沒有出現(xiàn)任何的SQL語句,可以說是非常方便了,JPA依靠我們提供的注解信息自動完成了所有信息的映射和關(guān)聯(lián)。
相比Mybatis,JPA幾乎就是一個全自動的ORM框架,而Mybatis則頂多算是半自動ORM框架。
方法名稱拼接自定義SQL
雖然接口預(yù)置的方法使用起來非常方便,但是如果我們需要進行條件查詢等操作或是一些判斷,就需要自定義一些方法來實現(xiàn),同樣的,我們不需要編寫SQL語句,而是通過方法名稱的拼接來實現(xiàn)條件判斷,這里列出了所有支持的條件判斷名稱:
屬性 | 拼接方法名稱示例 | 執(zhí)行的語句 |
---|---|---|
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname , findByFirstnameIs , findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull,Null | findByAge(Is)Null | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1(參數(shù)與附加 % 綁定) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1(參數(shù)與前綴 % 綁定) |
Containing | findByFirstnameContaining | … where x.firstname like ?1(參數(shù)綁定以 % 包裝) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | … where x.age not in ?1 |
True | findByActiveTrue | … where x.active = true |
False | findByActiveFalse | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
比如我們想要實現(xiàn)根據(jù)用戶名模糊匹配查找用戶:
@Repository public interface AccountRepository extends JpaRepository<Account, Integer> { //按照表中的規(guī)則進行名稱拼接,不用刻意去記,IDEA會有提示 List<Account> findAllByUsernameLike(String str); }
測試一下:
@Test void contextLoads() { repository.findAllByUsernameLike("%明%").forEach(System.out::println); }
同時根據(jù)用戶名和ID一起查詢:
@Repository public interface AccountRepository extends JpaRepository<Account, Integer> { List<Account> findAllByUsernameLike(String str); Account findByIdAndUsername(int id, String username); //也可以使用Optional類進行包裝,Optional<Account> findByIdAndUsername(int id, String username); }
@Test void contextLoads() { System.out.println(repository.findByIdAndUsername(1, "小明")); }
想判斷數(shù)據(jù)庫中是否存在某個ID的用戶:
@Repository public interface AccountRepository extends JpaRepository<Account, Integer> { List<Account> findAllByUsernameLike(String str); Account findByIdAndUsername(int id, String username); //使用exists判斷是否存在 boolean existsAccountById(int id); }
注意自定義條件操作的方法名稱一定要遵循規(guī)則,不然會出現(xiàn)異常:
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract ...
有了這些操作,我們在編寫一些簡單SQL的時候就很方便了,用久了甚至直接忘記SQL怎么寫。
關(guān)聯(lián)查詢
在實際開發(fā)中,比較常見的場景還有關(guān)聯(lián)查詢,也就是我們會在表中添加一個外鍵字段,而此外鍵字段又指向了另一個表中的數(shù)據(jù),當(dāng)我們查詢數(shù)據(jù)時,可能會需要將關(guān)聯(lián)數(shù)據(jù)也一并獲取,比如我們想要查詢某個用戶的詳細信息,一般用戶簡略信息會單獨存放一個表,而用戶詳細信息會單獨存放在另一個表中。當(dāng)然,除了用戶詳細信息之外,可能在某些電商平臺還會有用戶的購買記錄、用戶的購物車,交流社區(qū)中的用戶帖子、用戶評論等,這些都是需要根據(jù)用戶信息進行關(guān)聯(lián)查詢的內(nèi)容。
在JPA中,每張表實際上就是一個實體類的映射,而表之間的關(guān)聯(lián)關(guān)系,也可以看作對象之間的依賴關(guān)系,比如用戶表中包含了用戶詳細信息的ID字段作為外鍵,那么實際上就是用戶表實體中包括了用戶詳細信息實體對象:
@Data @Entity @Table(name = "users_detail") public class AccountDetail { @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) @Id int id; @Column(name = "address") String address; @Column(name = "email") String email; @Column(name = "phone") String phone; @Column(name = "real_name") String realName; }
而用戶信息和用戶詳細信息之間形成了一對一的關(guān)系,那么這時直接在類中指定這種關(guān)系:
@Data @Entity @Table(name = "users") public class Account { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @Id int id; @Column(name = "username") String username; @Column(name = "password") String password; @JoinColumn(name = "detail_id") //指定存儲外鍵的字段名稱 @OneToOne //聲明為一對一關(guān)系 AccountDetail detail; }
在修改實體類信息后,我們發(fā)現(xiàn)在啟動時也進行了更新,日志如下:
接著往用戶詳細信息中添加一些數(shù)據(jù),一會可以直接進行查詢:
@Test void pageAccount() { repository.findById(1).ifPresent(System.out::println); }
查詢后,可以發(fā)現(xiàn),得到如下結(jié)果:
也就是,在建立關(guān)系之后,我們查詢Account對象時,會自動將關(guān)聯(lián)數(shù)據(jù)的結(jié)果也一并進行查詢。
那要是我們只想要Account的數(shù)據(jù),不想要用戶詳細信息數(shù)據(jù)怎么辦呢?我希望在我要用的時候再獲取詳細信息,這樣可以節(jié)省一些網(wǎng)絡(luò)開銷,我們可以設(shè)置懶加載,這樣只有在需要時才會向數(shù)據(jù)庫獲取:
@JoinColumn(name = "detail_id") @OneToOne(fetch = FetchType.LAZY) //將獲取類型改為LAZY AccountDetail detail;
接著測試一下:
@Transactional //懶加載屬性需要在事務(wù)環(huán)境下獲取,因為repository方法調(diào)用完后Session會立即關(guān)閉 @Test void pageAccount() { repository.findById(1).ifPresent(account -> { System.out.println(account.getUsername()); //獲取用戶名 System.out.println(account.getDetail()); //獲取詳細信息(懶加載) }); }
接著來看看控制臺輸出了什么:
Hibernate: select account0_.id as id1_0_0_, account0_.detail_id as detail_i4_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from users account0_ where account0_.id=?
Test
Hibernate: select accountdet0_.id as id1_1_0_, accountdet0_.address as address2_1_0_, accountdet0_.email as email3_1_0_, accountdet0_.phone as phone4_1_0_, accountdet0_.real_name as real_nam5_1_0_ from users_detail accountdet0_ where accountdet0_.id=?
AccountDetail(id=1, address=四川省成都市青羊區(qū), email=8371289@qq.com, phone=1234567890, realName=盧本)
可以看到,獲取用戶名之前,并沒有去查詢用戶的詳細信息,而是當(dāng)我們獲取詳細信息時才進行查詢并返回AccountDetail對象。
也可以在添加數(shù)據(jù)時,利用實體類之間的關(guān)聯(lián)信息,一次性添加兩張表的數(shù)據(jù):需要稍微修改一下級聯(lián)關(guān)聯(lián)操作設(shè)定:
@JoinColumn(name = "detail_id") @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //設(shè)置關(guān)聯(lián)操作為ALL AccountDetail detail;
- ALL:所有操作都進行關(guān)聯(lián)操作
- PERSIST:插入操作時才進行關(guān)聯(lián)操作
- REMOVE:刪除操作時才進行關(guān)聯(lián)操作
- MERGE:修改操作時才進行關(guān)聯(lián)操作
可以多個并存,接著進行一下測試:
@Test void addAccount(){ Account account = new Account(); account.setUsername("Nike"); account.setPassword("123456"); AccountDetail detail = new AccountDetail(); detail.setAddress("重慶市渝中區(qū)解放碑"); detail.setPhone("1234567890"); detail.setEmail("73281937@qq.com"); detail.setRealName("張三"); account.setDetail(detail); account = repository.save(account); System.out.println("插入時,自動生成的主鍵ID為:"+account.getId()+",外鍵ID為:"+account.getDetail().getId()); }
可以看到日志結(jié)果:
Hibernate: insert into users_detail (address, email, phone, real_name) values (?, ?, ?, ?)
Hibernate: insert into users (detail_id, password, username) values (?, ?, ?)
插入時,自動生成的主鍵ID為:6,外鍵ID為:3
結(jié)束后會發(fā)現(xiàn)數(shù)據(jù)庫中兩張表都同時存在數(shù)據(jù)。
接著我們來看一對多關(guān)聯(lián),比如每個用戶的成績信息:
@JoinColumn(name = "uid") //注意這里的name指的是Score表中的uid字段對應(yīng)的就是當(dāng)前的主鍵,會將uid外鍵設(shè)置為當(dāng)前的主鍵 @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE) //在移除Account時,一并移除所有的成績信息,依然使用懶加載 List<Score> scoreList;
@Data @Entity @Table(name = "users_score") //成績表,注意只存成績,不存學(xué)科信息,學(xué)科信息id做外鍵 public class Score { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @Id int id; @OneToOne //一對一對應(yīng)到學(xué)科上 @JoinColumn(name = "cid") Subject subject; @Column(name = "socre") double score; @Column(name = "uid") int uid; }
@Data @Entity @Table(name = "subjects") //學(xué)科信息表 public class Subject { @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "cid") @Id int cid; @Column(name = "name") String name; @Column(name = "teacher") String teacher; @Column(name = "time") int time; }
在數(shù)據(jù)庫中填寫相應(yīng)數(shù)據(jù),接著我們就可以查詢用戶的成績信息了:
@Transactional @Test void test() { repository.findById(1).ifPresent(account -> { account.getScoreList().forEach(System.out::println); }); }
成功得到用戶所有的成績信息,包括得分和學(xué)科信息。
同樣的,我們還可以將對應(yīng)成績中的教師信息單獨分出一張表存儲,并建立多對一的關(guān)系,因為多門課程可能由同一個老師教授(千萬別搞暈了,一定要理清楚關(guān)聯(lián)關(guān)系,同時也是考驗?zāi)愕幕A(chǔ)扎不扎實):
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "tid") //存儲教師ID的字段,和一對一是一樣的,也會當(dāng)前表中創(chuàng)個外鍵 Teacher teacher;
接著就是教師實體類了:
@Data @Entity @Table(name = "teachers") public class Teacher { @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) @Id int id; @Column(name = "name") String name; @Column(name = "sex") String sex; }
最后我們再進行一下測試:
@Transactional @Test void test() { repository.findById(3).ifPresent(account -> { account.getScoreList().forEach(score -> { System.out.println("課程名稱:"+score.getSubject().getName()); System.out.println("得分:"+score.getScore()); System.out.println("任課教師:"+score.getSubject().getTeacher().getName()); }); }); }
成功得到多對一的教師信息。
最后我們再來看最復(fù)雜的情況,現(xiàn)在我們一門課程可以由多個老師教授,而一個老師也可以教授多個課程,那么這種情況就是很明顯的多對多場景,現(xiàn)在又該如何定義呢?我們可以像之前一樣,插入一張中間表表示教授關(guān)系,這個表中專門存儲哪個老師教哪個科目:
@ManyToMany(fetch = FetchType.LAZY) //多對多場景 @JoinTable(name = "teach_relation", //多對多中間關(guān)聯(lián)表 joinColumns = @JoinColumn(name = "cid"), //當(dāng)前實體主鍵在關(guān)聯(lián)表中的字段名稱 inverseJoinColumns = @JoinColumn(name = "tid") //教師實體主鍵在關(guān)聯(lián)表中的字段名稱 ) List<Teacher> teacher;
接著,JPA會自動創(chuàng)建一張中間表,并自動設(shè)置外鍵,我們就可以將多對多關(guān)聯(lián)信息編寫在其中了。
JPQL自定義SQL語句
雖然SpringDataJPA能夠簡化大部分數(shù)據(jù)獲取場景,但是難免會有一些特殊的場景,需要使用復(fù)雜查詢才能夠去完成,這時你又會發(fā)現(xiàn),如果要實現(xiàn),只能用回Mybatis了,因為我們需要自己手動編寫SQL語句,過度依賴SpringDataJPA會使得SQL語句不可控。
使用JPA,我們也可以像Mybatis那樣,直接編寫SQL語句,不過它是JPQL語言,與原生SQL語句很類似,但是它是面向?qū)ο蟮?,?dāng)然我們也可以編寫原生SQL語句。
比如我們要更新用戶表中指定ID用戶的密碼:
@Repository public interface AccountRepository extends JpaRepository<Account, Integer> { @Transactional //DML操作需要事務(wù)環(huán)境,可以不在這里聲明,但是調(diào)用時一定要處于事務(wù)環(huán)境下 @Modifying //表示這是一個DML操作 @Query("update Account set password = ?2 where id = ?1") //這里操作的是一個實體類對應(yīng)的表,參數(shù)使用?代表,后面接第n個參數(shù) int updatePasswordById(int id, String newPassword); }
@Test void updateAccount(){ repository.updatePasswordById(1, "654321"); }
現(xiàn)在我想使用原生SQL來實現(xiàn)根據(jù)用戶名稱修改密碼:
@Transactional @Modifying @Query(value = "update users set password = :pwd where username = :name", nativeQuery = true) //使用原生SQL,和Mybatis一樣,這里使用 :名稱 表示參數(shù),當(dāng)然也可以繼續(xù)用上面那種方式。 int updatePasswordByUsername(@Param("name") String username, //我們可以使用@Param指定名稱 @Param("pwd") String newPassword);
@Test void updateAccount(){ repository.updatePasswordByUsername("Admin", "654321"); }
通過編寫原生SQL,在一定程度上彌補了SQL不可控的問題。
雖然JPA能夠為我們帶來非常便捷的開發(fā)體驗,但是正是因為太便捷了,尤其是一些國內(nèi)用到復(fù)雜查詢業(yè)務(wù)的項目,可能開發(fā)到后期特別龐大時,就只能從底層SQL語句開始進行優(yōu)化,而由于JPA盡可能地在屏蔽我們對SQL語句的編寫,所以后期優(yōu)化是個大問題,并且Hibernate相對于Mybatis來說,更加重量級。不過,在微服務(wù)的時代,單體項目一般不會太大,JPA的劣勢并沒有太明顯地體現(xiàn)出來。
到此這篇關(guān)于SpringData JPA快速上手,關(guān)聯(lián)查詢,JPQL語句書寫的文章就介紹到這了,更多相關(guān)SpringData JPA關(guān)聯(lián)查詢內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
@Schedule?如何解決定時任務(wù)推遲執(zhí)行
這篇文章主要介紹了@Schedule?如何解決定時任務(wù)推遲執(zhí)行問題。具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09SpringBoot3使用?自定義注解+Jackson實現(xiàn)接口數(shù)據(jù)脫敏的步驟
本文介紹了一種以優(yōu)雅的方式實現(xiàn)對接口返回的敏感數(shù)據(jù),如手機號、郵箱、身份證等信息的脫敏處理,這種方法也是企業(yè)常用方法,話不多說我們一起來看一下吧2024-03-03Spring Security實現(xiàn)微信公眾號網(wǎng)頁授權(quán)功能
這篇文章主要介紹了Spring Security中實現(xiàn)微信網(wǎng)頁授權(quán),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫法
這篇文章主要介紹了MyBatis之傳入?yún)?shù)為list、數(shù)組、map的寫法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11使用Spring靜態(tài)注入實現(xiàn)讀取配置工具類新方式
這篇文章主要介紹了使用Spring靜態(tài)注入實現(xiàn)讀取配置工具類新方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02