節將詳細介紹es Search API的查詢主體,定制化查詢條件的實現主體。
搜索請求體中查詢條件使用es DSL查詢語法來定義。通過使用query來定義查詢體。
1GET /_search
2{
3 "query" : {
4 "term" : { "user" : "kimchy" }
5 }
6}
es的一種分頁語法。通過使用from和size參數來對結果集進行分頁。
from設置第一條數據的偏移量。size設置返回的條數(針對每個分片生效),由于es天生就是分布式的,通過設置主分片個數來進行數據水平切分,一個查詢請求通常需要從多個后臺節點(分片)進行數據匯聚。
From/Size方式會遇到分布式存儲的一個共性問題:深度分頁,也就是頁數越大需要訪問的數據則越大。es提供了另外一種分頁方式,滾動API(Scroll),后續會詳細分析。
注意:from + size 不能超過index.max_result_window配置項的值,其默認值為10000。
與傳統關系型數據庫類似,es支持根據一個或多個字段進行排序,同時支持asc升序或desc降序。另外es可以按照_score(基于得分)的排序,默認值。
如果使用了排序,響應結果中每一條命中數據將包含一個響應字段sort,其類型為Object[],表示該文檔當前的排序值,該值在ES支持的第三種分頁方式Search After中會使用到。
es提供了兩種排序順序,SortOrder.ASC(asc)升序、SortOrder.DESC(desc)降序,如果排序類型為_score,其默認排序順序為降序(desc),如果排序類型為字段,則默認排序順序為升序(asc)。
es支持按數組或多值字段進行排序。模式選項控制選擇的數組值,以便對它所屬的文檔進行排序。模式選項可以有以下值:
如果是一個數組類型的值參與排序,通常會對該數組元素進行一些計算得出一個最終參與排序的值,例如取平均數、最大值、最小值、求和等運算。es通過排序模型mode來指定。
es還支持在一個或多個嵌套對象內部的字段進行排序。一個嵌套查詢提包含如下選項(參數):
1"sort" : [
2 {
3 "parent.child.age" : { // @1
4 "mode" : "min",
5 "order" : "asc",
6 "nested": { // @2
7 "path": "parent",
8 "filter": {
9 "range": {"parent.age": {"gte": 21}}
10 },
11 "nested": { // @3
12 "path": "parent.child",
13 "filter": {
14 "match": {"parent.child.name": "matt"}
15 }
16 }
17 }
18 }
19 }
20]
代碼@1:排序字段名,支持級聯表示字段名。代碼@2:通過nested屬性定義排序嵌套語法,其中path定義當前的嵌套層級,filter定義過濾上下文。@3內部可以再通過nested屬性再次嵌套定義。
由于es的索引,類型下的字段可以在索引文檔時動態增加,那如果有些文檔沒有包含排序字段,這部分文檔的順序如何確定呢?es通過missing屬性來確定,其可選值為:
默認情況下,如果排序字段為未映射的字段將拋出異常。可通過unmapped_type來忽略該異常,該參數指定一個類型,也就是告訴ES如果未找該字段名的映射,就認為該字段是一個unmapped_type指定的類型,所有文檔都未存該字段的值。
地圖類型排序,該部分將在后續專題介紹geo類型時講解。
默認情況下,對命中的結果會返回_source字段下的所有內容。字段過濾機制允許用戶按需要返回_source字段里面部分字段。其過濾設置機制已在Elasticsearch Document Get API詳解、原理與示例中已詳細介紹,在這里就不重復介紹了。
使用方式如下:
1GET /_search
2{
3 "query" : {
4 "match_all": {}
5 },
6 "docvalue_fields" : [
7 {
8 "field": "my_date_field",
9 "format": "epoch_millis"
10
11 }
12 ]
13}
通過使用docvalue_fields指定需要轉換的字段與格式,它對于在映射文件中定義stored=false的字段同樣生效。字段支持用通配符,例如"field":"myfield*"。
docvalue_fields中指定的字段并不會改變_souce字段中的值,而是使用fields返回值進行額外返回。
java實例代碼片段如下(完整的Demo示例將在文末給出):
1SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
2sourceBuilder.query(QueryBuilders.termQuery("user", "dingw"))
3 .sort(new FieldSortBuilder("post_date").order(SortOrder.DESC))
4 .docValueField("post_date", "epoch_millis")
其返回結果如下:
{
"took":88,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":2,
"max_score":null,
"hits":[
{
"_index":"twitter",
"_type":"_doc",
"_id":"11",
"_score":null,
"_source":{
"post_date":"2009-11-19T14:12:12",
"message":"test bulk update",
"user":"dingw"
},
"fields":{
"post_date":[
"1258639932000"
]
},
"sort":[
1258639932000
]
},
{
"_index":"twitter",
"_type":"_doc",
"_id":"12",
"_score":null,
"_source":{
"post_date":"2009-11-18T14:12:12",
"message":"test bulk",
"user":"dingw"
},
"fields":{
"post_date":[
"1258553532000"
]
},
"sort":[
1258553532000
]
}
]
}
}
post filter對查詢條件命中后的文檔再做一次篩選。
1GET /shirts/_search
2{
3 "query": {
4 "bool": {
5 "filter": {
6 "term": { "brand": "gucci" } // @1
7 }
8 }
9 },
10 "post_filter": { // @2
11 "term": { "color": "red" }
12 }
13}
首先根據@1條件對索引進行檢索,然后得到匹配的文檔后,再利用@2過濾條件對結果再一次篩選。
查詢結果高亮顯示。
用于對查詢結果中對查詢關鍵字進行高亮顯示,高亮顯示查詢條件在查詢結果中匹配的部分。
注意:高亮顯示器在提取要高亮顯示的術語時不能反映查詢的布爾邏輯。因此對于一些復雜的布爾查詢(例如嵌套的布爾查詢,或使用minimum_should_match等查詢)可能高亮顯示會出現一些誤差。
高亮顯示需要字段的實際內容。如果字段沒有存儲(映射沒有將store設置為true),則從_source中提取相關字段。
es支持三種高亮顯示工具,通過為每個字段指定type來使用。
獲取偏移量策略。高亮顯示要解決的一個核心就是高亮顯示的詞根以及該詞根的位置(位置與偏移量)。
ES中提供了3中獲取偏移量信息(Offsets)的策略:
高亮顯示的全局配置會被字段級別的覆蓋。
1public static void testSearch_highlighting() {
2 RestHighLevelClient client = EsClient.getClient();
3 try {
4 SearchRequest searchRequest = new SearchRequest();
5 searchRequest.indices("map_highlighting_01");
6 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
7 sourceBuilder.query(
8 // QueryBuilders.matchAllQuery()
9 QueryBuilders.termQuery("context", "身份證")
10 );
11
12 HighlightBuilder highlightBuilder = new HighlightBuilder();
13 highlightBuilder.field("context");
14
15 sourceBuilder.highlighter(highlightBuilder);
16 searchRequest.source(sourceBuilder);
17 System.out.println(client.search(searchRequest, RequestOptions.DEFAULT));
18 } catch (Exception e) {
19 // TODO: handle exception
20 }
21 }
其返回值如下:
1{
10 "hits":{
11
13 "hits":[
14 {
19 "_source":{
20 "context":"城中西路可以受理外地二代身份證的辦理。"
21 },
22 "highlight":{ // @1
23 "context":[
24 "城中西路可以受理外地二代<em>身份證</em>的辦理。"
25 ]
26 }
27 }
28 ]
29 }
30}
這里主要對highlight再做一次說明,其中每一個字段返回的內容是對應原始數據的子集,最多fragmentSize個待關鍵字的匹配條目,通常,在頁面上顯示文本時,應該用該字段取代原始值,這樣才能有高亮顯示的效果。
重打分機制。一個查詢首先使用高效的算法查找文檔,然后對返回結果的top n 文檔運用另外的查詢算法,通常這些算法效率低效但能提供匹配精度。resoring查詢與原始查詢分數的合計方式如下:
查詢類型,默認值為query_then_fetch。
滾動查詢。es另外一種分頁方式。雖然搜索請求返回結果的單個頁面,但scroll API可以用于從單個搜索請求檢索大量結果(甚至所有結果),這與在傳統數據庫上使用游標的方式非常相似。
scroll api不用于實時用戶請求,而是用于處理大量數據,例如為了將一個索引的內容重新索引到具有不同配置的新索引中。
scroll API使用分為兩步:
1、第一步,首先通過scroll參數,指定該滾動查詢(類似于數據庫的游標的存活時間)
1POST /twitter/_search?scroll=1m
2{
3 "size": 100,
4 "query": {
5 "match" : {
6 "title" : "elasticsearch"
7 }
8 }
9}
該方法會返回一個重要的參數scrollId。
2、第二步,使用該scrollId去es服務器拉取下一批(下一頁數據)
1POST /_search/scroll
2{
3 "scroll" : "1m",
4 "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
5}
循環第三步,可以循環批量處理數據。
3、第三步,清除scrollId,類似于清除數據庫游標,快速釋放資源。
1DELETE /_search/scroll
2{
3 "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
4}
下面給出scoll api的java版本示例程序
1public static void testScoll() {
2 RestHighLevelClient client = EsClient.getClient();
3 String scrollId = null;
4 try {
5 System.out.println("step 1 start ");
6 // step 1 start
7 SearchRequest searchRequest = new SearchRequest();
8 searchRequest.indices("map_highlighting_01");
9 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
10 sourceBuilder.query(
11 QueryBuilders.termQuery("context", "身份證")
12 );
13 searchRequest.source(sourceBuilder);
14 searchRequest.scroll(TimeValue.timeValueMinutes(1));
15 SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
16 scrollId = result.getScrollId();
17 // step 1 end
18
19 // step 2 start
20 if(!StringUtils.isEmpty(scrollId)) {
21 System.out.println("step 2 start ");
22 SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
23 scrollRequest.scroll(TimeValue.timeValueMinutes(1));
24 while(true) { //循環遍歷
25 SearchResponse scollResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
26 if(scollResponse.getHits().getHits() == null ||
27 scollResponse.getHits().getHits().length < 1) {
28 break;
29 }
30 scrollId = scollResponse.getScrollId();
31 // 處理文檔
32 scrollRequest.scrollId(scrollId);
33 }
34 // step 2 end
35 }
36 System.out.println(result);
37 } catch (Exception e) {
38 e.printStackTrace();
39 } finally {
40 if(!StringUtils.isEmpty(scrollId)) {
41 System.out.println("step 3 start ");
42 // step 3 start
43 ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
44 clearScrollRequest.addScrollId(scrollId);
45 try {
46 client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
47 } catch (IOException e) {
48 // TODO Auto-generated catch block
49 e.printStackTrace();
50 }
51 // step 3 end
52 }
53 }
54
55 }
這里重點闡述一下第一次查詢時,不僅返回scrollId,也會返回第一批數據。
scroll參數(傳遞給搜索請求和每個滾動請求)告訴es它應該保持搜索上下文活動多長時間。只需要足夠長的時間來處理前一批結果。
每個scroll請求(帶有scroll參數)設置一個新的過期時間。如果scroll請求沒有傳入scroll,那么搜索上下文將作為scroll請求的一部分被釋放。
scroll其內部實現類似于快照,當第一次收到一個scroll請求時,就會為該搜索上下文所匹配的結果創建一個快照,隨后文檔的變化并不會反映到該API的結果。
對于返回大量文檔的scroll查詢,可以將滾動分割為多個可以獨立使用的片,通過slice指定。例如:
1GET /twitter/_search?scroll=1m // @1
2{
3 "slice": { // @11
4 "id": 0, // @12
5 "max": 2 // @13
6 },
7 "query": {
8 "match" : {
9 "title" : "elasticsearch"
10 }
11 }
12}
13GET /twitter/_search?scroll=1m // @2
14{
15 "slice": {
16 "id": 1,
17 "max": 2
18 },
19 "query": {
20 "match" : {
21 "title" : "elasticsearch"
22 }
23 }
24}
@1,@2兩個并列的查詢,按分片去查詢。
@11:通過slice定義分片查詢。
@12:該分片查詢的ID。
@13:本次查詢總片數。
這個機制非常適合多線程處理數據。
具體分片機制是,首先將請求轉發到各分片節點,然后在每個節點使用匹配到的文檔(hashcode(_uid)%slice片數),然后各分片節點返回數據到協調節點。也就是默認情況下,分片是根據文檔的_uid,為了提高分片過程,可以通過如下方式進行優化,并指定分片字段。
注意,默認slice片數最大為1024,可以通過索引設置項index.max_slices_per_scroll來改變默認值。例如:
從本章開始,我們將根據電商平臺的各個實例項目進行具體的微服務開發,主要包括類目管理、庫存管理、訂單管理等。在這幾個實例項目中,我們將根據項目本身的特點,使用不同的數據庫進行開發。對于類目管理來說,我們將使用二級分類設計,即數據實體之間存在一定的關聯關系,因此最好的選擇就是使用Spring Data JPA進行開發。Spring Data JPA是Spring Boot開發框架中一個默認推薦使用的數據庫開發方法,同時,JPA 也是領域驅動設計的一種具體應用。
本章的項目工程可以通過本文的源代碼在IDEA中使用Git檢出。該項目由三個模塊組成:
領域驅動設計(Domain-Driven Design,DDD)是一種面向對象建模,以業務模型為核心展開的軟件開發方法。面向對象建模的設計方法,相比于面向過程和面向數據結構的設計方法,從根本上解耦了系統分析與系統設計之間相互隔離的狀態,從而提高了軟件開發的工作效率。
我們將使用JPA來實現領域驅動設計的開發方法。JPA通過實體定義建立了領域業務對象的數據模型,然后通過使用存儲庫賦予實體操作行為,從而可以快速進行領域業務功能的開發。
DDD的分層結構
DDD將系統分為用戶接口層、應用層、領域層和基礎設施層,如圖6-1所示。
應用層是很薄的一層,負責接收用戶接口層傳來的參數和路由到對應的領域層,系統的業務邏輯主要集中在領域層中,所以領域層在系統架構中占據了很大的面積。上下層之間應該通過接口進行通信,這樣接口定義的位置就決定了上下層之間的依賴關系。
DDD的基本元素
DDD的基本元素有Entity、Value Object、Service、Aggregate、Repository、Factory、DomainEvent和Moudle等。
JPA(Java Persistence API)即Java持久層API,是Java持久層開發的接口規范。Hibernate、TopLink和 OpenJPA等ORM框架都提供了JPA的實現。Spring Data JPA 的實現使用了Hibernate框架,所以在設計上與直接使用 Hibernate差別不大。但JPA 并不等同于Hibernate,它是在Hibernate之上的一個通用規范。
接下來,我們通過模塊catalog-restapi來說明Spring Data JPA的開發方法。
Druid數據源配置
Druid是阿里巴巴開源的一個數據源服務組件,不僅具有很好的性能,還提供了監控和安全過濾的功能。
我們可以創建一個配置類DruidConfiguration來啟用Druid 的監控和過濾功能,代碼如下所示:
@Configuration
public class DruidConfiguration {
@Bean
public ServletRegistrationBean statviewServle({
ServletRegistrationBean servletRegistrationBean = new
ServletRegistrationBean(new StatViewServlet (), "/druid/*");
//IP地址白名單
servletRegistrationBean.addInitParameter("allow","192.168.0.1,
127.0.0.1");
//IP地址黑名單(共同存在時,deny優先于allow)
servletRegistrationBean.addInitParameter ("deny", "192.168.110.100");//控制臺管理用戶
servletRegistrationBean.addInitParameter ("loginUsername" , "druid");servletRegistrationBean.addInitParameter ("loginPassword","12345678");//是否能夠重置數據
servletRegistrationBean.addInitParameter("resetEnable" ,"false");return servletRegistrationBean;
}
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new
FilterRegistrationBean (new webStatFilter());
//添加過濾規則
filterRegistrationBean.addUrlPatterns("/*");//忽略過濾的格式
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,* .jpg,* .png,*.Css,* .ico,/druid/* ")F
return filterRegistrationBean;
}
}
在使用這個監控配置后,當應用運行時,例如,我們啟動catalog-restapi模塊,即可通過下列鏈接打開監控控制臺頁面:
http://localhost:9095/druid
在登錄認證中輸入前面代碼中配置的用戶和密碼“druid/12345678”,即可打開如圖6-2所示的操作界面。注意,本地的P地址不在前面代碼設置的黑名單之中。
在使用這個監控控制臺之后,通過查看“SQL監控”的結果,即可為我們對應用的SQL設計和優化提供有價值的參考依據。
我們可以使用項目中的配置文件 application.yml 來設置Druid連接數據源,代碼如下所示:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driverurl:
jdbc:mysql://localhost:3306/catalogdb?characterEncoding=utf8&useSSL=false
username: root
password:12345678
#初始化大小,最小值為5,最大值為120initialSize: 5
minIdle: 5
maxActive: 20
#配置獲取連接等待超時的時間maxwait: 60000
#配置間隔多久進行一次檢測,檢測需要關閉的空閑連接,單位是mstimeBetweenEvictionRunsMillis: 60000
#配置一個連接在池中最小生存時間,單位是msminEvictableIdleTimeMillis:300000validationQuery: SELECT 1 FROM DUALtestWhileIdle: true
testOnBorrow: falsetestOnReturn: false
#打開PSCache,指定每個連接上 PSCache的大小poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
#配置監控統計攔截的filters,如果去掉,則監控界面SQL將無法統計, 'wall'用于防火墻filters: stat, wall, log4j
#通過connectProperties屬性打開mergeSql功能;慢sQL記錄connectionProperties:
druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
在上面的配置中,主要設定了所連接的數據庫,以及數據庫的用戶名和密碼,確保數據庫的用戶配置信息正確,并且具有讀寫權限。其他一些配置可由 Druid 的通用參數來設定。
數據源的配置同時適用于MyBatis的開發。
JPA 初始化和基本配置
首先,我們新建一個配置類JpaConfiguration,初始化一些JPA的參數,代碼如下所示:
econfiguration
@EnableTransactionManagement (proxyTargetClass = true)
@EnableJpaRepositories (basePackages = "com.** .repository")CEntityScan(basePackages = "com.* *.entity")
public class JpaConfiguration{
@Bean
PersistenceExceptionTranslationPostProcessorpersistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
在這里,我們設置存儲庫的存放位置為“com.**.repository”,同時設置實體的存放位置為“com.**.entity”,這樣就能讓JPA找到我們定義的存儲庫和實體對象了。
然后,在應用程序的配置文件中,增加如下配置:
spring:
jpa:
database: MYSQLshow-sql: false
## Hibernate ddl auto (validate lcreate l create-drop l update)hibernate:
ddl-auto: update
#naming-strategy:org.hibernate.cfg. ImprovedNamingStrategynaming.physical-strategy:
org.hibernate.boot.model.naming. PhysicalNamingStrategystandardImpl
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialectenable_lazy_load_no_trans: true
其中,“ddl-auto”設置為“update”,表示當實體屬性更改時,將會更新表結構。如果表結構不存在,則創建表結構。注意,不要把“ddl-auto”設置為“create”,否則程序每次啟動時都會重新創建表結構,而之前的數據也會丟失。如果不使用自動功能,則可以設置為“none”。上面配置中的最后一行代碼開啟了Hibernate的延遲加載功能,這可以提高關聯關系查詢時的訪問性能。
在使用Spring Data JPA進行實體建模時,主要使用Hibernate的對象關系映射(ORM)來實現。在類目管理項目中我們需要創建兩個實體,分別為主類和二級分類。
主類由名稱、操作者和創建日期等屬性組成,實現代碼如下所示:
@Entity
@Table(name ="tsorts")
public class Sorts implements java.io.Serializable {
CId
@Generatedvalue(strategy = GenerationType. IDENTITY)private Long id;
private string name;
private string operator;
@DateTimeFormat (pattern = "Yyyy-MM-dd HH:mm:ss")
eColumn (name = "created",columnDefinition = "timestamp defaultcurrent timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
coneToMany(cascade = CascadeType.REMOVE)@OrderBy("created asc")
CJoincolumn (name= "sorts id")
private Set<Subsorts> subsortses = new HashSet<>();
從上面代碼中可以看出,我們使用了表“t_sorts”來存儲數據,并且它與二級分類以一對多的方式建立了關聯關系。建立關聯關系的是“sorts_id”,它將被保存在二級分類的表格中。另外,在查詢這種關系時,我們指定了以創建時間“created”進行排序。
二級分類實體由名稱、操作者和創建日期等屬性組成,代碼如下所示:
@Entity
@Table(name ="tsubsorts")
public class Subsorts implements java.io.Serializable t
@Id
@Generatedvalue(strategy = GenerationType. IDENTITY)private Long id;
private string name;
private String operator;
@DateTimeFormat (pattern = "Yyyy-MM-dd HH:mm:ss")
@Column (name = "created",columnDefinition = "timestamp defaultcurrent_timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;
...
}
二級分類使用了表結構“t_subsorts”來存儲數據,字段定義與主類的定義幾乎相同。
在上面兩個實體對象的設計中,我們通過主類使用一對多的方式與二級分類實現關聯設計,這樣,當在主類中進行查詢時,將可以同時獲取二級分類的數據;而對主類的存儲和更新,也將自動涉及分類的相關操作。
有關實體建模的設計,特別是關聯關系的設計,我們主要說明以下幾個重要的功能。
(1)實體對象必須有一個唯一標識。
這里使用Long類型定義對象的身份標識“id”,并且這個“id”將由數據庫自動生成。在實際應用中,推薦使用UUID作為對象的唯一標識,這樣不僅可以保持這一字段長度的一致性,還能保證這一標識在整個數據庫中的唯一性,而且還將非常有利于數據庫的集群設計。
(2)日期屬性要使用正確的格式。
使用注解“@DateTimeFormat”對日期進行格式化,不僅可以保證日期正常顯示,還能保證在參數傳遞中日期的正確性。注意,上面的創建日期“created”使用了默認值設置。
(3)使用合理的關聯設置。
關聯設置是實體設計的關鍵,為了避免引起遞歸調用,最好使用單向關聯設置,即在互相關聯的兩個對象之中,只在一個對象中進行關聯設置。
一般來說,多對多的關聯可以使用中間表來存儲關聯關系,而一對多或多對一的關聯關系可以使用一個字段來存儲關聯對象的外鍵。例如,在上面的實體設計中,我們使用“sorts_id"作為二級分類與主類關聯的外鍵。
在主類實體的關聯設置中,我們還使用了級聯的操作設置:“CascadeType.REMOVE”。這樣,當主類中的一個類別被刪除時,將會自動刪除與其關聯的所有分類。
有關級聯的設置,可以使用的選項如下所示:
我們將查詢對象設計放在一個公共模塊catalog-object中,這樣,其他兩個模塊都可以進行調用。使用查詢對象(Query Object,qo)是為了與vo進行區分。有人把vo看成值對象(ValueObject),也有人把vo看成視圖對象(View Object),所以很容易引起誤解。這兩種對象的意義和用途是不一樣的,值對象表示的是與實體不同的一些數據,它可以作為視圖顯示;而視圖對象是只能作為視圖顯示的一種數據。
因為實體是有生命周期和狀態的,并且它的狀態會直接影響存儲的數據,所以我們使用一個無狀態的數據對象來存儲實體的數據。這些數據的使用和更改不會直接影響數據存儲,因此它的使用是安全的,也可以用之即棄。
我們既可以將查詢對象作為值對象使用,也可以將查詢對象作為視圖對象使用,還可以將查詢對象作為查詢參數的一個集合來使用,即相當于一個數據傳輸對象(Data Transfer Object, dto)。
我們只要使用一個查詢對象qo,就可以包含vo、dto等對象的功能,這是一種簡化設計。qo有時會包含一些冗余數據,但這對于使用方來說影響不大。例如,在我們的查詢對象中,將會包含分頁所需的頁碼和頁大小等分頁屬性數據,而在視圖顯示中并不需要這些數據,所以它可以不用理會這些數據。
相對于主類實體,它的查詢對象的設計如下所示:
public class SortsQ0 extends PageQ0 {
private Long id;
private String name;
private String operator;
@DateTimeFormat (pattern = "yyyY-MM-dd HH:mm :ss")private Date created;
private List<SubsortsQ0> subsortses = new ArrayList<>();
...
}
其中,它所繼承的PageQo查詢對象將提供兩個分頁查詢參數,實現代碼如下所示:
public class PageQ0 [
private Integer page = 0;private Integer size = 10;
...
}
在分頁參數中,只有一個頁碼和每頁大小的設定兩個字段。
使用JPA進行實體數據持久化設計是比較容易的,只要為實體創建一個存儲庫接口,將實體對象與JPA的存儲庫接口進行綁定,就可以實現實體的數據持久化設計,相當于給實體賦予了一些訪問數據庫的操作行為,包括基本的增刪改查等操作。
除數據存儲的基本操作外,我們還可以根據實體的字段名稱來聲明查詢接口,而對于一些復雜的查詢,也可以使用SQL查詢語言設計。實體主類的存儲接口設計如下所示:
@Repository
public interface SortsRepository extends JpaRepository<Sorts,Long>,JpaSpecificationExecutor<Sorts> {
Page<Sorts> findByNameLike (@Param ( "name ") string name,PageablepageRequest);
@Query("select t from Sorts t where t.name like :name and t.created=:created")
Page<Sorts> findByNameAndCreated(@Param ("name") String name,@Param ("created") Date created, Pageable pageRequest);
Sorts findByName (@Param("name") String name);
@Query ( "select s from Sorts s"+
"left join s.subsortses b"+"where b.id= :id")
Sorts findBySubsortsId(@Param( "id") Long id);
}
這個接口定義是不用我們實現的,只要方法定義符合JPA的規則,后續的工作就可以交給JPA來完成。
在JPA中,可以根據以下方法自定義聲明方法的規則,即在接口中使用關鍵字findBy.readBy、getBy等作為方法名的前綴,然后拼接實體類中的屬性字段(首個字母大寫),最后拼接一些SQL查詢關鍵字(也可不拼接),組成一個查詢方法。下面是一些查詢關鍵字的使用實例:
通過注解@Query使用SQL查詢語言設計的查詢,基本與數據庫的查詢相同,這里只是使用實體對象的名字代替了數據庫表的名字。
在上面的存儲庫接口定義中,我們不但繼承了JPA的基礎存儲庫JpaRepository,還繼承了一個比較特別的存儲庫JpaSpecificationExecutor,通過這個存儲庫可以進行一些復雜的分頁設計。
前面的持久化設計已經在實體與數據庫之間建立了存取關系。為了更好地對外提供數據訪問服務,我們需要對存儲庫的調用再進行一次封裝。在這次封裝中,我們可以實現統一事務管理及其分頁的查詢設計。分類的數據管理服務設計代碼如下所示:
@Service
@Transactional
public class SortsService {
@Autowired
private SortsRepository sortsRepository;
public Sorts findOne (Long id){
Sorts sorts =sortsRepository.findById(id).get();return sorts;
public Sorts findByName (string name){
return sortsRepository.findByName (name) ;
}
public String save (Sorts sorts){
tryi
sortsRepository.save(sorts);
return sorts.getId() .toString();}catch(Exception e){
e.printStackTrace();return e.getMessage();
}
public String delete (Long id){
try{
sortsRepository.deleteById(id);return id.toString();
Jcatch(Exception e){
e.printStackTrace();return e.getMessage ();
public Page<Sorts> findAll (SortsQo sortsQo){
Sort sort = new Sort (Sort.Direction. DESC, "created");
Pageable pageable = PageRequest.of (sortsQo.getPage (), sortsQo.getSize(),
sort);
return sortsRepository.findAll (new Specification<Sorts>()1
@override
public Predicate toPredicate (Root<Sorts> root, CriteriaQuery<?>query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicatesList =new ArrayList<Predicate>();
if(CommonUtils.isNotNull (sortsQo.getName()){
predicatesList.add (criteriaBuilder.like(root.get ("name"),
"號"+sorts0o.getName()+"%"));
}
if(CommonUtils.isNotNull (sortsQo.getCreated())){
predicatesList.add (criteriaBuilder.greaterThan (root.get ("created"),sortsQo.getCreated()));
}
query.where(predicatesList.toArray (new
Predicate[predicatesList.size()]));
return query.getRestriction();
}, pageable);
}
}
在上面的代碼中,使用注解@Transactional 實現了隱式事務管理,對于一些基本的數據操作,可直接調用存儲庫接口的方法。
在上述代碼中,使用findAll方法實現了分頁查詢的設計。在這個設計中,可以定義排序的方法和字段,以及對頁碼和每頁行數的設定,同時,還可以根據查詢參數動態地設置查詢條件。在這里,我們既可以按分類的名稱進行模糊查詢,也可以按分類的創建時間進行限定查詢。
在完成上節的設計之后,我們可以寫一個測試用例驗證領域服務的設計。需要注意的是,因為在前面的JPA配置中已經有了更新表結構的配置,所以如果表結構不存在,則會自動生成;如果表結構更新,則啟動程序也會自動更新。下面的測試用例演示了如何插入分類和主類的數據:
@RunWith(SpringJUnit4ClassRunner.class)
eContextConfiguration (classes = {UpaConfiguration.class,SortsRestApiApplication.class})
@SpringBootTest
public class SortsTest{
private static Logger logger = LoggerFactory.getLogger (SortsTest.class);
CAutowired
private SortsService sortsService;@Autowired
private SubsortsService subsortsService;
@Test
public void insertData() {
Sorts sorts =new Sorts();sorts.setName("圖書");
sorts.setOperator( "editor");sorts.setCreated(new Date());
//
Sorts sorts = sortsService.findByName("圖書");
Subsorts subsorts = new Subsorts();subsorts.setName("計算機");
subsorts.setOperator("editor");subsorts.setCreated(new Date());
subsortsService.save (subsorts);
Assert.notNull(subsorts.getId(), "insert sub error");
sorts.addSubsorts(subsorts);
sortsService.save(sorts);
Assert.notNull (sorts.getId(), "not insert sorts");
}
...
}
其他查詢的測試用例可以參照這個方法設計,如果斷言沒有錯誤,則說明測試符合預期,即不會提示任何錯誤信息。在調試環境中,還可以借助控制臺信息分析測試的過程。
類目接口微服務是一個獨立的微服務應用,它將使用基于REST 協議的方式,對外提供一些有關類目查詢和類目數據管理的接口服務。這個接口服務,既可以用于商家后臺進行類目管理的設計之中,也可以用于移動端、App或其他客戶端程序的設計之中。
當上面的單元測試完成之后,我們就可以使用上面設計中提供的數據服務進行類目接口微服務的開發了。
RESTful接口開發
我們將遵循REST協議的規范設計基于RESTful的接口開發,例如,對于分類來說,我們可以設計如下請求:
下面的代碼展示了分類接口設計的部分實現,完整的代碼可以查看項目工程的相關源代碼:
@RestController
@RequestMapping("/sorts")
public class SortsController {
private static Logger logger = LoggerFactory.getLogger(SortsController.class);
@Autowired
private SortsService sortsService;
@GetMapping (value="/{id] ")
public String fnidById(@PathVariable Long id){
return new Gson().toJson (sortsService.findOne (id));
)
@GetMapping ()
public String findAll(Integer index,Integer size, String name)
try {
SortsQo sortsQ0 = new SortsQ0();if(CommonUtils.isNotNul1(index)){
sortsQo .setPage(index);
}
if(CommonUtils.isNotNull(size)){
sortsQ0.setsize(size);
}
if(CommonUtils.isNotNul1 (name)){
sortsQo. setName(name);
}
Page<Sorts> orderses = sortsService.findAll(sortsQo);
Map<String, 0bject> page = new HashMap<>();
page.put( "content", orderses.getContent();
page.put ("totalPages", orderses.getTotalPages());
page.put ("totalelements",orderses.getTotalElements());
return new Gson() .toJson (page);
}
Jcatch(Exception e){
e.printStackTrace();
}
return null;
)
@PostMapping()
public String save (CRequestBody SortsQo sortsQo) throws Exception{t
Sorts sorts =new sorts();
BeanUtils.copyProperties (sortsQ0,sorts);sorts.setCreated (new Date());
List<Subsorts> subsortsList = new ArrayList<>();//轉換每個分類,然后加入主類的分類列表中
for(SubsortsQo subsortsQ0 : sortsQo.getSubsortses()){
Subsorts subsorts =new Subsorts();
BeanUtils.copyProperties(subsortsQ0,subsorts);subsortsList.add(subsorts);
)
sorts. setSubsortses (subsortsList);
String ret =sortsService.save(sorts);logger.info("新增="+ ret);
return ret;
}
...
}
在上面微服務接口設計中,使用RestController 定義了對外提供服務的URL 接口,而接口之中有關數據的訪問則通過調用SortsService的各種方法來實現。其中,在接口調用中,都使用JSON方式的數據結構來傳輸數據,所以在上面代碼中,顯式或隱式地使用了JSON 的數據結構。對于一個數據對象來說,為了保證其數據的完整性,我們一般使用GSON 工具對數據進行顯式轉換。
需要注意的是,因為在數據傳輸中使用的是查詢對象,所以當進行數據保存和更新操作時,需要將查詢對象轉換為實體對象。
微服務接口調試
當微服務接口開發完成之后,即可啟動項目的應用程序進行簡單調試。對于類目微服務接口,我們可以啟動catalog-restapi模塊中的主程序SortsRestApiApplication進行調試。
在啟動成功之后,對于一些GET請求,可以直接通過瀏覽器進行調試。
例如,通過下列鏈接地址,可以根據分類ID查看一個分類的信息:
http://localhost:9091/sorts/1
如果數據存在,則返回如圖6-3所示的JSON數據。
使用如下鏈接地址可以查詢分頁第一頁的數據:
http://localhost:9091/sorts
如果查詢成功,則可以看到如圖6-4所示的信息。
因為POST 和 PUT等請求在調試時需要傳輸參數,所以不能直接使用瀏覽器進行測試,但是可以通過Postman等工具進行調試。
我們可以使用多種方法調用基于RESTful接口的服務。例如,可以使用HTTP 訪問(例如HttpClient),或者使用RestTemplate的方式進行調用,等等。但是,在微服務應用中,最好的方法是使用聲明式的FeignClient。
因為 FeignClient是為其他微服務進行調用的,所以這里將這些設計都放在模塊catalog-object中進行開發。
聲明式FeignClient 設計
FeignClient是一個聲明式的客戶端,為了使用這個工具組件,我們需要在項目對象模型中引入 FeignClient的依賴,代碼如下所示:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
針對主類的接口調用,我們可以定義一個接口程序SortsClient,根據微服務catalogapi提供的接口服務,使用如下所示的方法聲明一些調用方法:
@FeignClient ("catalogapi")
public interface SortsClient {
@RequestMapping (method = RequestMethod.GET, value = "/sorts/{id}")String findById(CRequestParam("id") Long id);
@RequestMapping (method = RequestMethod.GET,value = "/sorts/findAll")String findList();
CRequestMapping (method = RequestMethod.GET, value = "/sorts",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces =MediaType.APPLICATION_JSON_UTF8_VALUE)
String findPage (CRequestParam("index") Integer index, @RequestParam ("size")Integer size,
@RequestParam( "name") String name);
@RequestMapping (method =RequestMethod.GET, value = "/sorts/findAll",
consumes = MediaType.APPLICATION JSON UTF8_VALUE,
produces = MediaType.APPLICATION_ JSON_UTF8_VALUE)
String findAll();
@RequestMapping (method = RequestMethod. POST, value = "/sorts",
consumes = MediaType.APPLICATION_JSON UTF8 VALUE,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
String create(@RequestBody SortsQ0 sortsQ0) ;
@RequestMapping (method = RequestMethod.PUT,value = "/sorts",
consumes = MediaType.APPLICATION_JSON_UTF8_VALUE,
produces = MediaType.APPLICATION_JSON_ UTF8_VALUE)
String update(@RequestBody SortsQo sortsQo);
@RequestMapping (method = RequestMethod. DELETE,value = "/sorts/{id}")String delete (@RequestParam("id") Long id);
}
在這個實現代碼中,首先通過注解@FeignClient引用微服務catalogapi,然后使用其暴露出來的URL直接聲明調用方法。需要注意的是,這里的數據傳輸,即數據的生產和消費,都是通過JSON格式進行的,所以為了保證中文字符的正確性,我們使用UTF8編碼。
斷路器的使用
基于SortsClient的聲明方法,我們可以創建一個服務類SortsRestService進行調用。然后,使用SortsRestService提供的功能,就可以像使用本地方法一樣使用微服務catalogapi 提供的接口方法。服務類SortsRestService的實現代碼如下所示:
@service
public class SortsRestService {
CAutowired
private SortsClient sortsClient;
@HystrixCommand (fallbackMethod = "findByIdFallback")public String findById(Long id){
return sortsClient.findById(id);
private String findByIdFal1back (Long id){
SortsQo sortsQo = new SortsQ0();
return new Gson() .toJson (sortsQo);
}
...
}
在上面的代碼中,我們實現了對SortsClient的調用,同時增加了一個注解@HystrixCommand。通過這個注解,定義了一個回退方法。而這一回退方法的設計,就是SpringCloud組件提供的斷路器功能的實現方法。斷路器的含義是,當服務調用過載或不可用時,通過降級調用或故障轉移的方法,減輕服務的負載。這里我們使用了回退方法設計,以快速響應來自客戶端的訪問,并保障客戶端對微服務的訪問不會因為出現故障而崩潰。斷路器的設計就像電路的保護開關一樣,對系統服務起到一定的保護作用。與保護開關不同的是,當系統恢復正常時,斷路器會自動失效,不用人為干預。
這里 類目管理是一個基于 PC 端的 Web 應用,它也是一個獨立的微服務應用。這個應用在項目工程的模塊catalog-web 中實現,可以把它看成一個獨立的項目。
在這個應用中,我們將演示如何使用類目管理微服務接口提供的服務,進行相關應用功能的開發,從而實現在PC端提供一個對類目進行操作管理的友好操作界面。
接口調用引用的相關配置
上面的接口調用服務是在模塊catalog-object 中進行開發的,想要在模塊“catalog-web”中使用這些服務,就必須先在項目對象模型中進行引用配置,代碼如下所示:
<dependency>
<groupId>com.demo</groupId>
<artifactId>catalog-object</artifactId><version>${project.version}</version></dependency>
因為兩個模塊處于同一個項目工程之中,所以上面引用配置的版本直接使用了項目的版本。這樣,當接口服務啟動之后,我們就可以在接下來的 Web應用中進行相關調用了。
需要注意的是,如果有多個FeignClient程序調用了同一個微服務接口服務,則必須在項目的配置文件中使用如下所示的配置進行設置,以支持這種調用方式。因為這個Spring Cloud版本的默認配置是不開啟這種調用方式的:
#允許多個接口使用相同的服務
spring:
main:
allow-bean-definition-overriding: true
Spring MVC控制器設計
Spring MVC是 Web應用開發的一個基礎組件,下面我們使用這一組設計一個控制器。在Web應用的主類控制器設計中,我們直接使用上面設計的服務類:SortsRestService。我們可以像使用本地方法一樣使用SortsRestService類,直接調用微服務提供的接口服務,代碼如下所示:
@GRestController
@RequestMapping ( "/sorts")
public class SortsController {
private static Logger logger =
LoggerFactory.getLogger(SortsController.class);
@Autowired
private SortsRestService sortsRestService;
@GetMapping(value=" /index")
public Mode1AndView index(){
return new ModelAndview( "sorts/index");
@GetMapping (value="/{id]")
public ModelAndView findById(@PathVariable Long id){
return new ModelAndView("sorts/show", "sorts",
new Gson() .fromJson (sortsRestService.findById(id),
SortsQo.class));
}
...
}
上面代碼中的findByld方法是一個使用頁面來顯示分類信息的設計。在這個設計中,一方面引用了上面設計的服務類SortsRestService,并調用了它的findByld 方法,進行數據查詢;另一方面將查詢數據通過一個 show頁面顯示出來。這個設計與一般的本地調用不同的是,查詢數據時得到的返回值是一種ISON結構,所以必須將它轉化為一個查詢對象,這樣才能方便使用。
接下來的頁面設計將會用到Thymeleaf模板的功能。
在 Web應用的頁面設計中,我們將使用Thymeleaf 這個模板,因此,必須在catolog-web模塊中引入Thymeleaf 的依賴,代碼如下所示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId><version>2.3.0</version>
</dependency>
有關 Thymeleaf 的配置,使用其默認配置即可,即只要在程序的資源目錄中有static和templates這兩個目錄就可以了。這兩個目錄分別用來存放靜態文件和模板設計及其頁面設計文件,頁面文件的后綴默認使用html。
HTML頁面設計
在6.10節控制器的設計中,類目信息輸出的是一個show頁面,它的設計在show.html文件中,代碼如下所示:
<html xmlns:th="http://www.thymeleaf.org"><div class="addInfBtn">
<h3 class="itemTit"><span>類目信息</span></h3><table class="addNewInfList">
<tr>
<th>名稱</th>
<td width="240"><input class="inp-list w-200 clear-mr f-left"
type="text" th:value="$ {sorts.namel" readonly="true"/></td>
<th>操作者</th>
<td><input class="inp-list w-200 clear-mr f-left" type="text"
th:value="$ {sorts.operator}" readonly="true"/></td>
</tr>
<tr>
<th>子類</th><td>
<select multiple= "multiple" readonly="true">
<option th:each="subsorts:${sorts.subsortses] "
th:text="${#strings. length(subsorts.name)
>20?#strings.substring (subsorts.name,0,20)+'...':subsorts.name} "
th:selected="true"
></option>
</select>
</td>
<th>日期</th><td>
<input onfocus="WdatePicker ({dateFmt:'yyyy-MiM-dd HH :mm:ss'))"
type="text" class="inp-list w-200 clear-mr f-left" th:value="${sorts.created)?$ {#dates.format(sorts.created, 'vyvy-MM-dd HlH:mm:ss')}:''" readonly="true"/>
</td>
</tr></table>
<div class="bottomBtnBox">
<a class="btn-93x38 backBtn" href="javascript:closeDialog (0)">返回</a></div>
</div>
從上面的代碼可以看出,除用到Thymeleaf特有的地方外,其他設計都與一般的HTML標簽語言相同。設計之后,這個頁面的最終效果如圖6-5所示。
統一風格模板設計
Thymeleaf更強大的功能是提供了一個統一風格的模板設計,即整個網站可以使用統一風格的框架結構。在類目管理這個項目中,使用了總體頁面框架設計layout.html,代碼如下所示:
<! DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout">
<body>
<div class="headerBox">
<div class="topBox">
<div class="topLogo f-left">
<a href="#"><img th:src="@{/images/logo.pngl "/></a></div>
</div></div>
<div class="locationLine" layout:fragment="prompt">
當前位置:首頁 > <em>頁面</em>
</div>
<table class="globalMainBox" style="position:relative;z-index:1">
<tr>
<td class="columnLeftBoX" valign="top">
<div th:replace="fragments/nav ::nav"></div></td>
<td class="whiteSpace"></td>
<td class="rightColumnBox" valign="top"><div layout: fragment="content"></div></td>
</tr></table>
<form th:action="@{/logout}" method="post" id="logoutform"></form>
<div class="footBox" th:replace="fragments/footer :: footer"></div></body>
</html>
頁面上方是狀態欄,頁面左側是導航欄,中間部分是內容顯示區域,底端還有一個頁腳設計。在引用這個模板之后,只需對需要更改的區域進行覆蓋就可以了,而不需要更改的地方使用模板的默認設計即可。一般來說,在使用這個模板時,只要更改狀態欄和內容顯示區域就可以了,而導航欄和頁腳,則可以使用通用的頁面設計。
在這個例子中,分類的主頁是通過index.html這個頁面設計來引用這個模板的,代碼如下所示:
<! DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"
layout:decorator="fragments/layout">
<body>
<!--狀態欄-->
<div class="locationLine" layout: fragment="prompt">
當前位置:首頁> <em >類目管理</em>
</div>
<!--主要內容區域-->
<div class="statisticBoX w-782"layout:fragment="content">
...
</div></body></html>
可以看出,在上面的代碼中,我們只更新了狀態欄和主要內容顯示區域的設計,其他部分都沿用了模板的設計。
在上面的一些設計講解和演示中,我們只說明了主類的設計,二級分類的設計與主類的設計大同小異,不再贅述。
至此,類目管理的微服務應用的開發工作就基本完成了。
現在我們可以體驗微服務之間的調用了,因為使用了Spring Cloud工具組件來開發,所以在各個方面的實現都是非常方便的。當然,對于微服務的調用,不僅僅是Web應用的調用,還有其他如App應用、微信公眾號或小程序客戶端,或者其他語言的設計、異構環境的調用,等。不管使用哪種工具來設計,只要能用HTTP,就可以輕易實現對微服務的調用。
在類目管理的微服務接口及其Web微服務應用都開發完成之后,我們就可以進行一個總體測試了。首先確認Consul已經運行就緒,然后先后啟動catalog-restapi和 catalog-web兩個模塊。啟動成功之后,通過瀏覽器訪問如下鏈接地址:
http://localhost:8091
如果一切正常,則可以進入如圖6-6所示的類目管理的主頁。在這里,我們可以分別對主類和二級分類中的所有類目進行增刪改查的所有操作。
在使用IDEA開發工具執行打包時,可以使用 Maven項目管理器執行打包操作,如圖6-7所示。
如果是模塊化的項目,請務必在項目的根(root)目錄中執行打包操作,這樣才能將其所依賴的模塊同時打包在一起。
當打包完成之后,可以使用命令終端,分別切換到catalog-restapi和 catalog-web模塊的 target目錄中執行下列命令,啟動應用進行調試:
java -jar catalog*.jar
以這種方式啟動應用,與上面使用IDEA工具進行調試時的效果是一樣的。如果啟動正常,則可以進行與上面一樣的測試。
這種啟動方式也可以作為一種普通的方式來發布微服務,在生產環境中,可以在上面指令的基礎上增加一些內存和日志存儲方面的參數。
有關微服務應用的部署,將在運維部署部分進行詳細介紹。
本章介紹了電商平臺的類目管理接口和Web類目管理后臺兩個微服務的開發實例,通過這個項目的開發和演示,我們清楚了微服務之間快速通信和相互調用的方法。在類目管理接口開發中,我們通過Spring Data JPA開發工具,了解了DDD開發方法在Spring 開發框架中的工作原理和實現方法。通過類目管理接口的實現,我們將有狀態的數據訪問行為,轉變成沒有狀態的接口服務。
下一章,我們將介紹另一種數據庫開發工具 MyBatis,體驗不同的數據庫開發工具在Spring項目工程中的應用方法。
擊“了解更多”獲取工具
DevExpress WinForms Subscription擁有180+組件和UI庫,能為Windows Forms平臺創建具有影響力的業務解決方案。DevExpress WinForms能完美構建流暢、美觀且易于使用的應用程序,無論是Office風格的界面,還是分析處理大批量的業務數據,它都能輕松勝任!
DevExpress Winforms v20.2日前全新發布,此版本加強了Scheduler、Spreadsheet控件功能,歡迎下載最新版體驗!
Year View
WinForms Scheduler控件附帶Year View顯示選項,它旨在可視化跨越數天和數周的事件/約會。
Year View包含“ MonthCount”屬性,其他與視圖相關的設置與Month View設置相同。
下拉日歷和視圖選擇器
在日期導航欄中添加了兩個新的UI元素。
這兩個UI元素最初都是隱藏的,激活DateNavigationBar.CalendarButton和DateNavigationBar.ShowViewSelectorButton選項以使其可見。
在Timeline View中新的單元格自動高度模式
新版本將CellsAutoHeightOptions.Enabled屬性重命名為AutoHeightMode,AutoHeightMode屬性接受枚舉值,而不是布爾值。'None'和 'Limited' 對應于'false' 和 'true',第三個值 - “ Full”- 激活新的AutoHeight模式。
使用AutoHeight時,時間單元將忽略ResourcesPerPage屬性值,并根據內容調整大小,這還允許用戶對Timeline View進行像素滾動。
Excel 2016圖表(CTP)
WinForms Spreadsheet控件現在支持以下Excel 2016圖表類型:
全面的Spreadsheet API可讓您根據需要創建和編輯Excel 2016圖表,WinForms Spreadsheet控件可以使用Excel 2016圖表打開、打印和導出(導出為PDF)現有工作簿。
HTML格式
現在,您可以使用以下標準HTML標記來格式化字符串:
大多數控件現在都支持<image>標簽,要指定圖像集合,請使用控件的“ HtmlImages”屬性。
疊加層 - 支持DirectX
疊加層現在支持DirectX硬件加速,現在動畫在高分辨率顯示器上的呈現更加流暢(并且內存使用效率更高)。
MVVM - MessageBox表單樣式
我們向MessageBoxService類添加一個新的MessageBoxFormStyle屬性,此屬性使您可以指定MessageBox表單的外觀設置。
C#
var flyoutMsgService = MessageBoxService.CreateFlyoutMessageBoxService();
flyoutMsgService.MessageBoxFormStyle = (form) => {
FlyoutDialog msgFrm = form as FlyoutDialog;
msgFrm.Properties.AppearanceButtons.FontStyleDelta = FontStyle.Bold;
};
mvvmContext1.RegisterService(flyoutMsgService);
Docking - 浮動面板始終位于頂部
浮動面板和DocumentManager文檔(在Tabbed和Widget Views中)可以顯示在其父窗體的上方或下方。 以下新選項使您始終可以將浮動窗口置于最上方:
如果單獨使用浮動窗口(禁用了FloatPanelsAlwaysOnTop選項),它將顯示Minimize按鈕,該按鈕會將窗口折疊到Windows任務欄。 要隱藏Minimize按鈕,請禁用ShowMinimizeButton選項。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。