一招教你解決頁面中關(guān)聯(lián)id的轉(zhuǎn)換
在工作中,我們經(jīng)常有這樣的業(yè)務(wù)情況,實(shí)體間通過id實(shí)現(xiàn)數(shù)據(jù)業(yè)務(wù)上的關(guān)聯(lián),比如訂單和用戶,訂單的創(chuàng)建人id、商品id等,在頁面查詢時(shí)我們需要將對(duì)應(yīng)的id信息轉(zhuǎn)換成對(duì)應(yīng)的中文描述,比如用戶中文名稱,商品中文名稱等。如果是單條數(shù)據(jù)的展示還好,但是設(shè)計(jì)到列表查詢,如何高效、優(yōu)雅地實(shí)現(xiàn)這個(gè)效果呢?
現(xiàn)在接口返回的數(shù)據(jù)基本都是JSON格式,比如spring中使用了jackson,在controller層對(duì)結(jié)果進(jìn)行json序列化,而我們要做的就是在序列化的過程中,實(shí)現(xiàn)id的轉(zhuǎn)換。
使用方式
定義實(shí)體轉(zhuǎn)換接口
由于需要對(duì)訂單實(shí)體中的創(chuàng)建人id進(jìn)行轉(zhuǎn)換:
public interface UserConvert {
String USER_CACHE = "USER_CACHE";
String userId();
default ConvertItem getUserConvert(){
if( userId() == null ){
return null;
}
return new ConvertItem(userId(), USER_CACHE);
}
}
定義接口轉(zhuǎn)換適配器
基于上面UserConvert的處理,基于緩存實(shí)現(xiàn),同時(shí)支持一個(gè)實(shí)體中多個(gè),比如商品名稱、商品分類等!
public class UserConvertProvider extends CacheItemConvertAdapter {
private static String name = UserConvert.USER_CACHE;
public UserConvertProvider() {
super(name, User.class);
}
@Override
public boolean support(ConvertItem convertItem) {
return convertItem != null && convertItem.getName().equals(name);
}
@Override
public String convert(ConvertItem convertItem) {
if( convertItem == null ){
return null;
}
User user = (User) fromCache(convertItem.getId());
return user != null ? user.getCaption() : null;
}
}
需要轉(zhuǎn)換的數(shù)據(jù)緩存
該實(shí)現(xiàn)依賴緩存,需要優(yōu)先對(duì)需要轉(zhuǎn)換的數(shù)據(jù)進(jìn)行緩存,因此示例中添加了緩存示例:
public void init(){
Cache cache = cacheManager.getCache(UserConvert.USER_CACHE);
if( cache != null ){
cache.put("u1", new User("u1","Tom"));
}
}
實(shí)體定義
實(shí)體中需要通過實(shí)現(xiàn)接口UserConvert,這樣對(duì)多個(gè)數(shù)據(jù)項(xiàng)轉(zhuǎn)換時(shí)可以繼續(xù)擴(kuò)展。
public class Order implements UserConvert {
private String id;
private String name;
private LocalDateTime createTime = LocalDateTime.now();
/**
* 創(chuàng)建用戶
*/
private String creator;
@Override
public String userId() {
return creator;
}
}
實(shí)現(xiàn)效果
可以看到,在輸出json中,多了一列userConvert,也就是接口中定義的get*方法:
{
"id": "1",
"name": "測試訂單",
"createTime": "2024-05-08T21:55:51.5747507",
"creator": "u1",
"userConvert": "Tom"
}
實(shí)現(xiàn)原理
上面說的,主要實(shí)現(xiàn)基于緩存,在web查詢結(jié)果進(jìn)行json序列化時(shí),依賴于jackson的擴(kuò)展,對(duì)輸出結(jié)果匹配的類型進(jìn)行轉(zhuǎn)換。
@EnableCaching
@Configuration
public class JacksonCustomConfiguration{
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> configureMapperBuilder(jacksonObjectMapperBuilder);
}
private void configureMapperBuilder(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
jackson2ObjectMapperBuilder.serializers(convertSerializer());
}
@Bean
public ItemConvertSerializer convertSerializer(){
return new ItemConvertSerializer(ConvertItem.class);
}
}
- 在配置文件中基于Jackson2ObjectMapperBuilderCustomizer對(duì)jackson進(jìn)行擴(kuò)展。
- 定義ItemConvertSerializer對(duì)ConvertItem類型的屬性進(jìn)行處理,該類主要繼承于StdSerializer。
- 在ItemConvertSerializer中基于ConvertItem的name屬性來匹配對(duì)應(yīng)的緩存并進(jìn)行轉(zhuǎn)換。
- 注意開啟spring緩存*@EnableCaching*。
- 最后基于spring特性,定義*/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports*來實(shí)現(xiàn)自動(dòng)注入配置。
- ConvertItem示例:
@Getter
@Setter
public class ConvertItem {
private String id;
private String text;
private String name;
public ConvertItem() {
}
public ConvertItem(String id, String name) {
this.id = id;
this.name = name;
}
}
- ItemConvertAdapter擴(kuò)展適配器,主要于ConvertItem搭配擴(kuò)展。
public interface ItemConvertAdapter {
/**
* @param convertItem
* @return
*/
boolean support(ConvertItem convertItem);
/**
*
* @param convertItem
* @return
*/
String convert(ConvertItem convertItem);
}
- ItemConvertSerializer示例:
public class ItemConvertSerializer extends StdSerializer<ConvertItem> implements ApplicationContextAware {
private List<ItemConvertAdapter> itemConvertAdapters;
public ItemConvertSerializer(Class<ConvertItem> t) {
super(t);
}
@Override
public void serialize(ConvertItem value, JsonGenerator gen, SerializerProvider provider) throws IOException {
String text = "";
if(!CollectionUtils.isEmpty(itemConvertAdapters)){
for (ItemConvertAdapter itemConvertAdapter : itemConvertAdapters) {
if( itemConvertAdapter.support(value) ){
text = itemConvertAdapter.convert(value);
break;
}
}
}
gen.writeString(text);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, ItemConvertAdapter> itemConvertAdapterMap
= BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ItemConvertAdapter.class, true, false);
if( !itemConvertAdapterMap.isEmpty() ){
itemConvertAdapters = new ArrayList<>(itemConvertAdapterMap.values());
itemConvertAdapters.sort(OrderComparator.INSTANCE);
}
}
}
優(yōu)缺點(diǎn)
- 使用了jackson序列化的擴(kuò)展,如果使用其他序列化工具,需要單獨(dú)支持。
- 依賴于數(shù)據(jù)緩存,一般針對(duì)通用數(shù)據(jù)才有數(shù)據(jù)轉(zhuǎn)換的需要,比如用戶、部門數(shù)據(jù)等,一般這些數(shù)據(jù)更適合緩存。