Springboot整合spring-boot-starter-data-elasticsearch的過程
前言
<font style="color:rgb(36, 41, 47);">spring-boot-starter-data-elasticsearch</font>
是 Spring Boot 提供的一個(gè)起始依賴,旨在簡化與 Elasticsearch 交互的開發(fā)過程。它集成了 Spring Data Elasticsearch,提供了一套完整的 API,用于與 Elasticsearch 進(jìn)行 CRUD 操作、查詢、索引等
相對于es原生依賴的話,進(jìn)行了一下封裝,在使用過程中相對便捷
項(xiàng)目源碼
項(xiàng)目源碼:https://gitee.com/shen-chuhao/walker_open_java.git
elasticsearch和springboot版本要求
在使用 <font style="color:rgb(36, 41, 47);">spring-boot-starter-data-elasticsearch</font>
時(shí),Spring Boot 版本和 Elasticsearch 版本不一致可能導(dǎo)致以下問題:
兼容性問題:
- Spring Data Elasticsearch 依賴于特定版本的 Elasticsearch 客戶端庫。如果版本不兼容,可能會導(dǎo)致運(yùn)行時(shí)異?;蚍椒ㄎ凑业降腻e(cuò)誤。
功能缺失或錯(cuò)誤:
- 一些新功能可能在不兼容的版本中不可用,或者可能會存在實(shí)現(xiàn)上的差異,導(dǎo)致某些功能無法正常工作。
配置問題:
- 某些配置選項(xiàng)可能會因版本不一致而有所不同。例如,某些配置參數(shù)在新版本中可能被棄用,或者在舊版本中可能不可用。
序列化和反序列化問題:
- 數(shù)據(jù)模型的變化可能導(dǎo)致在 Elasticsearch 中存儲的數(shù)據(jù)格式與預(yù)期不符,從而引發(fā)解析錯(cuò)誤。
性能問題:
- 不兼容的版本可能導(dǎo)致性能下降或不穩(wěn)定,尤其是在高并發(fā)環(huán)境下。
相關(guān)版本的要求
我的es是7.3.2的,介于6.8.127.6.2之間,所以springboot版本為2.2.x2.3.x即可
所以在使用spring-boot-starter-data-elasticsearch的過程中,還是要保持版本一致
整合步驟
依賴添加
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
yaml配置添加
spring: elasticsearch: rest: uris: localhost:19200 # 賬號密碼配置,如果沒有則不需要 password: elastic username: elastic
實(shí)體類添加
package com.walker.es.entity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Transient; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; @Data //索引 // boolean createIndex() default true; 默認(rèn)會創(chuàng)建索引 @Document(indexName = "alarm_record", shards = 2, replicas = 2,createIndex = true) public class AlarmRecordEntity { // 配置使用的id @Id private Long id; // 事件 // 配置屬性,分詞器 @Field(type = FieldType.Text, analyzer = "ik_max_word") private String title; // 設(shè)備 @Field(type = FieldType.Keyword) private String deviceCode; // 時(shí)間 需要使用keyword,否則無法實(shí)現(xiàn)范圍查詢 @Field(type = FieldType.Keyword) private String time; // 在es中進(jìn)行忽略的 @Transient private String msg; }
- @Document注解:可以設(shè)置索引的名稱,分片、副本,是否自動創(chuàng)建索引等
- @Id:指定文檔的id
- @Field(type = FieldType.Text, analyzer = “ik_max_word”) 屬性配置,可以用于配置字段的類型,分詞器等等,具體的可以查看后面的
相關(guān)注解
Repository類
package com.walker.es.repository; import com.walker.es.entity.AlarmRecordEntity; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; // 添加注解 @Repository // 繼承類 ElasticsearchRepository public interface AlarmRecordRepository extends ElasticsearchRepository<AlarmRecordEntity, Long> { // 你可以在這里添加自定義查詢方法 }
繼承ElasticsearchRepository類,可以使用ElasticsearchRepository封裝好的增刪改查等方法
測試類
先注入repository
@Autowired private AlarmRecordRepository alarmRecordRepository;
新增數(shù)據(jù)
// 創(chuàng)建新警報(bào)記錄 @PostMapping public ResponseEntity<AlarmRecordEntity> createAlarmRecord(@RequestBody AlarmRecordEntity alarmRecord) { // 自動生成雪花id alarmRecord.setId(IdUtil.getSnowflakeNextId()); AlarmRecordEntity savedRecord = alarmRecordRepository.save(alarmRecord); return ResponseEntity.ok(savedRecord); }
請求參數(shù):
{ "title": "打架告警", "deviceCode": "A001", "time": "2024-11-10 10:10:00", "msg": "消息描述" }
執(zhí)行后,如果第一次插入數(shù)據(jù),則會自動生成索引。
副本、分片
字段映射情況
查詢插入的數(shù)據(jù)
修改數(shù)據(jù)
@Autowired private DocumentOperations documentOperations; @Autowired private ElasticsearchConverter elasticsearchConverter; // 更新警報(bào)記錄 @PutMapping("/{id}") public ResponseEntity<AlarmRecordEntity> updateAlarmRecord(@PathVariable Long id, @RequestBody AlarmRecordEntity alarmRecord) { //使用ElasticsearchConverter 將對象轉(zhuǎn)成Document對象 Document document = elasticsearchConverter.mapObject(alarmRecord); // 修改方法 UpdateQuery updateQuery = UpdateQuery.builder(String.valueOf(id)).withDocument(document).build(); UpdateResponse updateResponse = documentOperations.update(updateQuery, IndexCoordinates.of("alarm_record")); log.info("修改數(shù)據(jù)結(jié)果:{}", JSONUtil.toJsonStr(updateResponse)); return ResponseEntity.ok(alarmRecord); }
- Repository中是沒有修改數(shù)據(jù)的方法的
- 所有得使用操作類DocumentOperations、或者ElasticsearchOperations
實(shí)踐結(jié)果:
修改結(jié)果如下:
刪除
// 刪除警報(bào)記錄 @DeleteMapping("/{id}") public ResponseEntity<Void> deleteAlarmRecord(@PathVariable Long id) { if (!alarmRecordRepository.existsById(id)) { return ResponseEntity.notFound().build(); } alarmRecordRepository.deleteById(id); return ResponseEntity.noContent().build(); }
使用repository的deleteById
批量刪除
使用repository進(jìn)行基礎(chǔ)查詢
// 獲取所有警報(bào)記錄 @GetMapping("/findByDeviceCode") public List<AlarmRecordEntity> findByDeviceCode(String deviceCode) { return alarmRecordRepository.findAlarmRecordEntitiesByDeviceCodeEquals(deviceCode); }
- 繼承ElasticsearchRepository類后,可以使用findXXByXX,進(jìn)行查詢,類似于jpa
- 例如下面的方法
package com.walker.es.repository; import com.walker.es.entity.AlarmRecordEntity; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface AlarmRecordRepository extends ElasticsearchRepository<AlarmRecordEntity, Long> { // 根據(jù)deviceCode=xx進(jìn)行查詢,不需要去重寫方法等,相對簡便 List<AlarmRecordEntity> findAlarmRecordEntitiesByDeviceCodeEquals(String deviceCode); }
- 具體的關(guān)鍵詞使用,可以查看附錄1
repository關(guān)鍵詞
分頁查詢、條件搜索
這個(gè)是實(shí)際業(yè)務(wù)場景中用的比較多的了
但是_ElasticsearchRepository 無法實(shí)現(xiàn)復(fù)雜的查詢,所以還是得使用ElasticsearchTemplate 或者_(dá)<font style="color:rgb(36, 41, 47);">ElasticsearchOperations</font>
進(jìn)行處理
spring-data-elasticsearch的操作類主要有以下幾種:
<font style="color:rgb(36, 41, 47);">IndexOperations</font>
定義了在索引級別執(zhí)行的操作,比如創(chuàng)建或刪除索引。
<font style="color:rgb(36, 41, 47);">DocumentOperations</font>
定義了基于實(shí)體 ID 存儲、更新和檢索實(shí)體的操作。
<font style="color:rgb(36, 41, 47);">SearchOperations</font>
定義了使用查詢搜索多個(gè)實(shí)體的操作。
<font style="color:rgb(36, 41, 47);">ElasticsearchOperations</font>
結(jié)合了 <font style="color:rgb(36, 41, 47);">DocumentOperations</font>
和 <font style="color:rgb(36, 41, 47);">SearchOperations</font>
接口的功能。
__
__
__
請求體
@Data public class AlarmSearchDTO { // 注意,頁碼需要從0開始,否則會跳過 private Integer pageIndex=0; private Integer pageSize=10; private String title; private String deviceCode; private String startTime; private String endTime; }
__
controllerfangfa
@PostMapping("/page") public PageVo<AlarmRecordEntity> getAlarmRecords(@RequestBody AlarmSearchDTO dto) { // 分頁類 將頁碼和頁數(shù)參數(shù)填入 Pageable pageable = PageRequest.of(dto.getPageIndex(),dto.getPageSize()); // 構(gòu)建查詢 在spring-data-elastic中有三種查詢方式,具體可以查看后面的查詢方式 NativeSearchQueryBuilder query = new NativeSearchQueryBuilder() .withPageable(pageable); // 如果設(shè)備編碼非空 if(StrUtil.isNotEmpty(dto.getDeviceCode())){ // 精確查詢 termQuery query.withQuery(QueryBuilders.termQuery("deviceCode",dto.getDeviceCode() )); } if(StrUtil.isNotEmpty(dto.getTitle())){ // 模糊查詢 fuzzyQuery 如果是要根據(jù)分詞拆分查詢的話,得使用matchQuery query.withQuery(QueryBuilders.fuzzyQuery("title",dto.getTitle() )); } // 范圍查詢 if (StrUtil.isNotEmpty(dto.getStartTime())) { // range范圍查詢需要keyword才能生效,如果使用text則會不生效 query.withQuery(QueryBuilders.rangeQuery("time").gte(dto.getStartTime()).lte(dto.getEndTime())); } NativeSearchQuery searchQuery = query.build(); // 排序:根據(jù)id倒敘 searchQuery.addSort(Sort.sort(AlarmRecordEntity.class).by(AlarmRecordEntity::getId).descending()); // 執(zhí)行查詢 SearchHits<AlarmRecordEntity> search = elasticsearchTemplate.search(searchQuery, AlarmRecordEntity.class); PageVo<AlarmRecordEntity> pageVo = new PageVo<>(); if(search!=null){ //設(shè)置總數(shù) pageVo.setTotal(search.getTotalHits()); // 返回的行數(shù) List<SearchHit<AlarmRecordEntity>> searchHits = search.getSearchHits(); if(CollUtil.isNotEmpty(searchHits)){ ArrayList<AlarmRecordEntity> rows = new ArrayList<>(); for (SearchHit<AlarmRecordEntity> searchHit : searchHits) { AlarmRecordEntity content = searchHit.getContent(); rows.add(content); } pageVo.setList(rows); } } return pageVo; }
Query查詢方式主要有以下幾種
1. CriteriaQuery
定義:<font style="color:rgb(36, 41, 47);">CriteriaQuery</font>
是一個(gè)用于構(gòu)建類型安全查詢的方式。它允許通過構(gòu)建查詢條件來進(jìn)行靈活的查詢。
使用案例:
CodeCriteriaQuery criteriaQuery = new CriteriaQuery(); criteriaQuery.addCriteria(Criteria.where("fieldName").is("value")); List<MyEntity> results = elasticsearchOperations.queryForList(criteriaQuery, MyEntity.class);
2. StringQuery
定義:<font style="color:rgb(36, 41, 47);">StringQuery</font>
是一種基于字符串的查詢方式,允許直接使用 Elasticsearch 的查詢 DSL(Domain Specific Language)。
使用案例:
CodeStringQuery stringQuery = new StringQuery("{\"match\":{\"fieldName\":\"value\"}}"); List<MyEntity> results = elasticsearchOperations.queryForList(stringQuery, MyEntity.class);
3. NativeQuery
定義:<font style="color:rgb(36, 41, 47);">NativeQuery</font>
允許執(zhí)行原生的 Elasticsearch 查詢,通常用于需要更復(fù)雜或特定的查詢場景。
使用案例:
CodeNativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder(); searchQueryBuilder.withQuery(QueryBuilders.matchQuery("fieldName", "value")); NativeSearchQuery searchQuery = searchQueryBuilder.build(); List<MyEntity> results = elasticsearchOperations.queryForList(searchQuery, MyEntity.class);
總結(jié)
<font style="color:rgb(36, 41, 47);">CriteriaQuery</font>
適用于類型安全且靈活的查詢構(gòu)建。<font style="color:rgb(36, 41, 47);">StringQuery</font>
提供直接使用查詢 DSL 的能力。<font style="color:rgb(36, 41, 47);">NativeQuery</font>
適合復(fù)雜的查詢需求,允許使用原生的 Elasticsearch 查詢。
可以發(fā)現(xiàn),在查詢條件中,還是需要手動寫出屬性的名稱,使用起來相對會麻煩一些。
具體的查詢方式,可以自己去查找一下,例如范圍查詢,模糊查詢,分詞查詢等等。
總結(jié)
- spring-data-elasticsearch的查詢,相當(dāng)于原生的es依賴而言,多了一些注解封裝、repository類等,但是對于復(fù)雜查詢還是沒有辦法很簡便,因此便有了第三方的依賴Easy-es的誕生,類似于Mybatis-plus的方式,對于新手對es的使用比較友好,后面會進(jìn)行講解
最后:我是Walker,一個(gè)熱愛分享的程序員,希望我的輸出能夠幫助到你
附錄 repository關(guān)鍵詞
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Or | findByNameOrPrice | { "query" : { "bool" : { "should" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } }, { "query_string" : { "query" : "?", "fields" : [ "price" ] } } ] } }} |
Is | findByName | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Not | findByNameNot | { "query" : { "bool" : { "must_not" : [ { "query_string" : { "query" : "?", "fields" : [ "name" ] } } ] } }} |
Between | findByPriceBetween | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
LessThan | findByPriceLessThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : false } } } ] } }} |
LessThanEqual | findByPriceLessThanEqual | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
GreaterThan | findByPriceGreaterThan | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : false, "include_upper" : true } } } ] } }} |
GreaterThanEqual | findByPriceGreaterThanEqual | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Before | findByPriceBefore | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : null, "to" : ?, "include_lower" : true, "include_upper" : true } } } ] } }} |
After | findByPriceAfter | { "query" : { "bool" : { "must" : [ {"range" : {"price" : {"from" : ?, "to" : null, "include_lower" : true, "include_upper" : true } } } ] } }} |
Like | findByNameLike | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
StartingWith | findByNameStartingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
EndingWith | findByNameEndingWith | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
Contains/Containing | findByNameContaining | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "*?*", "fields" : [ "name" ] }, "analyze_wildcard": true } ] } }} |
In (when annotated as FieldType.Keyword) | findByNameIn(Collection<String>names) | { "query" : { "bool" : { "must" : [ {"bool" : {"must" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
In | findByNameIn(Collection<String>names) | { "query": {"bool": {"must": [{"query_string":{"query": "\"?\" \"?\"", "fields": ["name"]}}]}}} |
NotIn (when annotated as FieldType.Keyword) | findByNameNotIn(Collection<String>names) | { "query" : { "bool" : { "must" : [ {"bool" : {"must_not" : [ {"terms" : {"name" : ["?","?"]}} ] } } ] } }} |
NotIn | findByNameNotIn(Collection<String>names) | {"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}} |
True | findByAvailableTrue | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }} |
False | findByAvailableFalse | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "false", "fields" : [ "available" ] } } ] } }} |
OrderBy | findByAvailableTrueOrderByNameDesc | { "query" : { "bool" : { "must" : [ { "query_string" : { "query" : "true", "fields" : [ "available" ] } } ] } }, "sort":[{"name":{"order":"desc"}}] } |
Exists | findByNameExists | {"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}} |
IsNull | findByNameIsNull | {"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}} |
IsNotNull | findByNameIsNotNull | {"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}} |
IsEmpty | findByNameIsEmpty | {"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}} |
IsNotEmpty | findByNameIsNotEmpty | {"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}} |
相關(guān)注解
@Document:應(yīng)用于類級別,指示該類可以映射到數(shù)據(jù)庫。最重要的屬性包括(檢查 API 文檔以獲取完整屬性列表):
- indexName:存儲此實(shí)體的索引名稱??梢园?
<font style="color:rgb(36, 41, 47);">"log-#{T(java.time.LocalDate).now().toString()}"</font>
這樣的 SpEL 模板表達(dá)式。 - createIndex:標(biāo)記是否在倉庫引導(dǎo)時(shí)創(chuàng)建索引。默認(rèn)值為 true。請參閱與相應(yīng)映射的自動創(chuàng)建索引相關(guān)內(nèi)容。
@Id:應(yīng)用于字段級別,用于標(biāo)記用于身份目的的字段。
@Transient、@ReadOnlyProperty、@WriteOnlyProperty:請參見以下部分“控制哪些屬性被寫入和從 Elasticsearch 讀取”的詳細(xì)信息。
@PersistenceConstructor:標(biāo)記在從數(shù)據(jù)庫實(shí)例化對象時(shí)使用的構(gòu)造函數(shù)——即使是包保護(hù)的構(gòu)造函數(shù)。構(gòu)造函數(shù)參數(shù)通過名稱映射到檢索到的文檔中的鍵值。
@Field:應(yīng)用于字段級別,定義字段的屬性,大多數(shù)屬性映射到相應(yīng)的 Elasticsearch 映射定義(以下列表并不完整,請檢查注解 Javadoc 以獲取完整參考):
- name:字段在 Elasticsearch 文檔中的表示名稱,如果未設(shè)置,則使用 Java 字段名稱。
- type:字段類型,可以是 Text、Keyword、Long、Integer、Short、Byte、Double、Float、Half_Float、Scaled_Float、Date、Date_Nanos、Boolean、Binary、Integer_Range、Float_Range、Long_Range、Double_Range、Date_Range、Ip_Range、Object、Nested、Ip、TokenCount、Percolator、Flattened、Search_As_You_Type 之一。請參閱 Elasticsearch 映射類型。如果未指定字段類型,則默認(rèn)為 FieldType.Auto。這意味著不會為該屬性寫入映射條目,并且 Elasticsearch 會在首次存儲該屬性的數(shù)據(jù)時(shí)動態(tài)添加映射條目(請檢查 Elasticsearch 文檔中的動態(tài)映射規(guī)則)。
- format:一個(gè)或多個(gè)內(nèi)置日期格式,請參見下一節(jié)日期格式映射。
- pattern:一個(gè)或多個(gè)自定義日期格式,請參見下一節(jié)日期格式映射。
- store:標(biāo)記原始字段值是否應(yīng)存儲在 Elasticsearch 中,默認(rèn)值為 false。
- analyzer、searchAnalyzer、normalizer:用于指定自定義分析器和規(guī)范化器。
@GeoPoint:標(biāo)記字段為 geo_point 數(shù)據(jù)類型。如果字段是 GeoPoint 類的實(shí)例,則可以省略此注解。
@ValueConverter:定義一個(gè)類,用于轉(zhuǎn)換給定屬性。與注冊的 Spring 轉(zhuǎn)換器不同,這僅轉(zhuǎn)換注解的屬性,而不是給定類型的所有屬性。
@Transient:該注解表示該屬性不會包含在映射中。其值不會發(fā)送到 Elasticsearch,并且在從 Elasticsearch 檢索文檔時(shí),該屬性不會在實(shí)體中被填充。
@ReadOnlyProperty:此注解標(biāo)記一個(gè)屬性,該屬性不會寫入 Elasticsearch,但在數(shù)據(jù)檢索時(shí),將根據(jù) Elasticsearch 文檔中的值填充該屬性。適用于索引映射中定義的運(yùn)行時(shí)字段。
@WriteOnlyProperty:該注解指示一個(gè)屬性會存儲在 Elasticsearch 中,但在讀取文檔時(shí)不會被填充。通常用于需要索引到 Elasticsearch 但在其他地方不需要使用的合成字段。
參考
官方文檔
https://docs.spring.io/spring-data/elasticsearch/docs/
到此這篇關(guān)于Springboot整合spring-boot-starter-data-elasticsearch的文章就介紹到這了,更多相關(guān)Springboot整合spring-boot-starter-data-elasticsearch內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java如何將可運(yùn)行jar打包成exe可執(zhí)行文件
Java程序完成以后,對于Windows操作系統(tǒng)習(xí)慣總是想雙擊某個(gè)exe文件就可以直接運(yùn)行程序,這篇文章主要給大家介紹了關(guān)于java如何將可運(yùn)行jar打包成exe可執(zhí)行文件的相關(guān)資料,需要的朋友可以參考下2023-11-11CountDownLatch源碼解析之countDown()
這篇文章主要為大家詳細(xì)解析了CountDownLatch源碼之countDown方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04詳解spring applicationContext.xml 配置文件
本篇文章主要介紹了詳解spring applicationContext.xml 配置文件 ,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02Java數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之二叉樹
今天給大家?guī)淼氖顷P(guān)于Java數(shù)據(jù)結(jié)構(gòu)的相關(guān)知識,文章圍繞著Java二叉樹展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06處理Log4j2不能打印行號的問題(AsyncLogger)
這篇文章主要介紹了處理Log4j2不能打印行號的問題(AsyncLogger),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12HttpClient的RedirectStrategy重定向處理核心機(jī)制
這篇文章主要為大家介紹了HttpClient的RedirectStrategy重定向處理核心機(jī)制源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10java中struts2實(shí)現(xiàn)簡單的文件上傳與下載
這篇文章主要為大家詳細(xì)介紹了java中struts2實(shí)現(xiàn)簡單的文件上傳與下載的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05