使用MyBatis返回map對象,字段值爲null時不返回或返回null,目標返回自定義的默認值...

在項目開發中,爲了減少json傳輸的數據量,加快響應速度,通常當字段值爲null時,我們不會把字段返回給前端。但在實際開發中可能像Android 與iOS 更希望我們可以返回完整的數據,

在mybatis 中,返回map字段值爲null 時是有返回的,例如:

<result column="name" property="name" jdbcType="VARCHAR" javaType="java.lang.String"/>

在mapper.xml 文件中使用以上的格式返回名稱爲name的數據,如果name的值爲null ,那麼返回值也爲null,並不會無故的消失掉,所以我們如果需要字段值爲null的字段不回傳,需要用到另外的jar 包
我當前項目中使用的jar 爲:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>

以下貼上json 代碼

  1 package module.spring;
  2 
  3 import com.fasterxml.jackson.annotation.JsonInclude;
  4 import com.fasterxml.jackson.core.JsonGenerator;
  5 import com.fasterxml.jackson.databind.JsonSerializer;
  6 import com.fasterxml.jackson.databind.ObjectMapper;
  7 import com.fasterxml.jackson.databind.SerializationFeature;
  8 import com.fasterxml.jackson.databind.SerializerProvider;
  9 import com.fasterxml.jackson.databind.introspect.Annotated;
 10 import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector;
 11 import com.fasterxml.jackson.databind.module.SimpleModule;
 12 import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
 13 import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
 14 import message.Message;
 15 import org.apache.commons.lang3.ClassUtils;
 16 import org.springframework.http.HttpOutputMessage;
 17 import org.springframework.http.converter.HttpMessageNotWritableException;
 18 import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
 19 
 20 import java.io.IOException;
 21 import java.lang.reflect.Type;
 22 import java.util.*;
 23 
 24 public class JSON extends MappingJackson2HttpMessageConverter {
 25 
 26     private static final ThreadLocal<Map<Class, Filter>> LOCAL_FILTER = new ThreadLocal<>();
 27 
 28     private ObjectMapper ObjectMapperCopy;
 29     
 30     public JSON() {
 31     }
 32 
 33     public JSON(ObjectMapper objectMapper) {
 34         super(objectMapper);
 35         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
 36         objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
 37         objectMapper.registerModule(new SimpleModule().addSerializer(Boolean.class,new BooleanJsonSerializer()));
 38 //        objectMapper.registerModule(new SimpleModule().addSerializer(Message.class,new MessageJsonSerializer()));
 39         this.ObjectMapperCopy = objectMapper.copy();
 40     }
 41 
 42     @Override
 43     protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
 44         this.objectMapper = ObjectMapperCopy.copy();
 45         SimpleFilterProvider filterProvider  = new SimpleFilterProvider();
 46         if (getLocalFilter().size() > 0) {
 47             for (Map.Entry<Class, Filter> filter : getLocalFilter().entrySet()) {
 48                 if(filter.getValue().mode == Filter.Mode.EXCEPT){
 49                     filterProvider.addFilter(filter.getKey().getName(), SimpleBeanPropertyFilter.serializeAllExcept(filter.getValue().field));
 50                 }else if(filter.getValue().mode == Filter.Mode.INCLUDE){
 51                     filterProvider.addFilter(filter.getKey().getName(), SimpleBeanPropertyFilter.filterOutAllExcept(filter.getValue().field));
 52                 }
 53             }
 54             this.objectMapper.setFilterProvider(filterProvider);
 55             this.objectMapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
 56                 @Override
 57                 public Object findFilterId(Annotated a) {
 58                     return (ClassUtils.isAssignable(a.getClass(), com.fasterxml.jackson.databind.introspect.AnnotatedClass.class) && getLocalFilter().containsKey(a.getType().getRawClass())) ? a.getName() : null;
 59                 }
 60             });
 61         }
 62         super.writeInternal(object, type, outputMessage);
 63         clearLocalFilter();
 64     }
 65 
 66 
 67     public static void except(Class aClass, String... field){
 68         if(getLocalFilter().containsKey(aClass)){
 69             throw new FilterException("filter exists");
 70         }
 71         getLocalFilter().put(aClass,new Filter(field, Filter.Mode.EXCEPT));
 72     }
 73 
 74     public static void include(Class aClass, String... field){
 75         if(getLocalFilter().containsKey(aClass)){
 76             throw new FilterException("filter exists");
 77         }
 78         getLocalFilter().put(aClass,new Filter(field, Filter.Mode.INCLUDE));
 79     }
 80 
 81     private static Map<Class,Filter> getLocalFilter() {
 82         if(LOCAL_FILTER.get() == null){
 83             LOCAL_FILTER.set(new HashMap<>());
 84         }
 85         return LOCAL_FILTER.get();
 86     }
 87 
 88     private static void clearLocalFilter(){
 89         LOCAL_FILTER.remove();
 90     }
 91 
 92     private static class Filter {
 93         private String[] field;
 94         private Filter.Mode mode;
 95 
 96         private Filter(String[] field,Filter.Mode mode){
 97             this.field = field;
 98             this.mode = mode;
 99         }
100 
101         private enum Mode{
102             EXCEPT,
103             INCLUDE
104         }
105     }
106 
107     private final static class FilterException extends RuntimeException {
108 
109         private FilterException(String message) {
110             super(message);
111         }
112     }
113 
114     private final class BooleanJsonSerializer extends JsonSerializer<Boolean> {
115 
116         @Override
117         public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
118             gen.writeNumber(value ? 1 : 0);
119         }
120     }
121 
122     private final class MessageJsonSerializer extends JsonSerializer<Message> {
123 
124         @Override
125         public void serialize(Message message, JsonGenerator gen, SerializerProvider serializers) throws IOException {
126             if( message.getData() instanceof List){
127                 gen.writeNumber(0);
128             }
129             if( message.getData() instanceof Map){
130                 Map map=(Map)message.getData();
131                 Set<String> set = map.keySet();
132                 for (String str:set
133                      ) {
134                     if(map.get(str) instanceof String){
135                        map.put(str,"");
136                     }
137                     if(map.get(str) instanceof Date){
138                        map.put(str,0);
139                     }
140                     if(map.get(str) instanceof Number){
141                         map.put(str,0);
142                     }
143 
144                 }
145             }
146             gen.writeNumber(com.alibaba.fastjson.JSON.toJSONString(message));
147         }
148     }
149 }

上面被我註釋的代碼並不是錯誤,我註釋它只因爲我根本用不到它,跟我的需求不符:

 我是爲了給返回值設定默認值,剛開始我的思路爲如果返回的字段類型爲String 類型並且返回值爲null,則返回 “” ,返回的字段類型爲Integer 類型並且返回值爲null,則返回 0 ,""和 0 是我自定義的可以隨意的更改。。

但是我發現返回值爲null時 字段沒有類型,對是沒有類型 就是null,我只能對null進行處理。

JsonSerializer<Message> 這裏的 Message 類型是我返回值得包裝類,裏面有返回值 ,狀態碼等...

不過也不是全無收穫,起碼如果我想要對返回值中的一些字段的值進行過濾的話可能比較方便...

既然這兒不行我只能往更上游,可以拿到返回的字段的類型的地方實現我返回自定義默認值的目標。

我找到了mybatis 的 BaseTypeHandler 在這兒對mybatis 從數據庫取出來的字段進行設置默認值。BaseTypeHandler 有很多的子類,分別處理不同的返回類型。例如:DateTypeHandler、StringTypeHandler ..

所有的數據類型都有它的子類的實現。 我直接繼承了類DateTypeHandler,把默認返回null 改爲 默認返回 new Date(0), 發現好使??????注意這裏我並沒有更改配置,也沒有在mapper.xml 中使用  typeHandler=""  這個屬性引用我寫好的子類

 

package dao.typehandler.myTypeHander;

import exception.SimpleException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.apache.ibatis.type.DateTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;

import java.sql.*;
import java.util.Date;

/**
 * @author tianyuting
 * @date 2019/11/110:59
 */
//@MappedJdbcTypes({JdbcType.DATE,JdbcType.TIMESTAMP}) 這個註解在一開始是沒有加的。如果不加需要在mapper.xml中使用typeHandler="" 進行引用
public class MyDateTypeHandler extends DateTypeHandler { @Override public Date getResult(ResultSet rs, String columnName) throws SQLException { Date result; try { result = getNullableResult(rs, columnName); } catch (Exception e) { throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e); } return result; } @Override public Date getNullableResult(ResultSet rs, String columnName) throws SQLException { Timestamp sqlTimestamp = rs.getTimestamp(columnName); if (sqlTimestamp != null) { // throw new SimpleException("fffff"); return new Date(sqlTimestamp.getTime()); } return new Date(0); } @Override public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException { Timestamp sqlTimestamp = rs.getTimestamp(columnIndex); if (sqlTimestamp != null) { return new Date(sqlTimestamp.getTime()); } return new Date(0); } @Override public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { Timestamp sqlTimestamp = cs.getTimestamp(columnIndex); if (sqlTimestamp != null) { return new Date(sqlTimestamp.getTime()); } return new Date(0); } }

 

想着既然這樣可行  那就把String 也弄個默認爲“” 的子類。好嘛..不行,String死活不行,必須得使用 typeHandler="" 在mapper.xml 中進行引用。可我就想不明白了,爲什麼其中一個可以一個就不行呢?如果有知道的大佬,還請您不吝指點一番...在下感激不盡

按我的理解Java的繼承 與多態 在這兒完全不夠用,代碼jar 已經寫好了我現在的子類實現就是一個沒人調用的存在,多態的父類引用指向子類對象在我這裏完全的被違背。感覺哪兒出了問題,是不是我的眼界太窄,或許人家使用了反射什麼的???

如果有大佬有思路實現一個只通過繼承就可以覆蓋父類的方法執行子類方法,如果沒有子類就執行父類方法的程序,可不可以分享一下 ...我是完全沒有思路啊。只能說自己辣雞一個。

 

 1 package dao.typehandler.myTypeHander;
 2 
 3 import org.apache.ibatis.executor.result.ResultMapException;
 4 import org.apache.ibatis.type.JdbcType;
 5 import org.apache.ibatis.type.MappedJdbcTypes;
 6 import org.apache.ibatis.type.StringTypeHandler;
 7 
 8 import java.sql.*;
 9 
10 /**
11  *
12  */
13 //@MappedJdbcTypes(JdbcType.VARCHAR) 這個註解在一開始是沒有加的。如果不加需要在mapper.xml中使用typeHandler="" 進行引用
14 public class MyStringTypeHandler extends StringTypeHandler {
15 
16   @Override
17   public String getResult(ResultSet rs, String columnName) throws SQLException {
18     String result;
19     try {
20       result = getNullableResult(rs, columnName);
21     } catch (Exception e) {
22       throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
23     }
24     return result;
25 
26   }
27   @Override
28   public String getNullableResult(ResultSet rs, String columnName)
29       throws SQLException {
30     String rsString = rs.getString(columnName);
31     if(rsString==null){
32       return "";
33     }
34     return rsString;
35   }
36 
37   @Override
38   public String getNullableResult(ResultSet rs, int columnIndex)
39       throws SQLException {
40     String rsString = rs.getString(columnIndex);
41     if(rsString==null){
42       return "";
43     }
44     return rsString;
45   }
46 
47   @Override
48   public String getNullableResult(CallableStatement cs, int columnIndex)
49       throws SQLException {
50     String rsString = cs.getString(columnIndex);
51     if(rsString==null){
52       return "";
53     }
54     return rsString;
55   }
56 }

 

我感覺在mapper.xml中使用 typeHandler="" 是有一點太麻煩了,每一個我都要加一個typeHandler="",還要把字段一一的都寫到 resultMap 中去,實在是感覺不咋地.

 

所以我繼續上網搜索 typeHandler 這個東西,順便說一句,看官方文檔,啥都有,有不瞭解的直接找官方文檔是最好的。想走捷徑,除非這個東西到處都是,分分鐘就已經解決..分分鐘不了就去看官方吧.貼個鏈接mybatis官方文檔(中文)

好了,需要把配置也該一改,

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="LOG4J2"/>
<!--<setting name="logImpl" value="NO_LOGGING" />-->
<!--<setting name="cacheEnabled" value="false"/>-->
<setting name="useGeneratedKeys" value="true"/>
<setting name="defaultExecutorType" value="REUSE"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true"/>
<setting name="callSettersOnNulls" value="true"/>
</settings>

<!--<typeHandlers>-->
<!--<package name="dao.typehandler"/>-->
<!--</typeHandlers>-->
<typeHandlers>
<!--
當配置package的時候,mybatis會去配置的package掃描TypeHandler
<package name="com.dy.demo"/>
-->
<!-- handler屬性直接配置我們要指定的TypeHandler -->
<!--<typeHandler handler=""/>-->

<!-- javaType 配置java類型,例如String, 如果配上javaType, 那麼指定的typeHandler就只作用於指定的類型 -->
<typeHandler javaType="java.lang.String" handler="dao.typehandler.myTypeHander.MyStringTypeHandler"/>

<!-- jdbcType 配置數據庫基本數據類型,例如varchar, 如果配上jdbcType, 那麼指定的typeHandler就只作用於指定的類型 -->
<typeHandler javaType="java.util.Date" handler="dao.typehandler.myTypeHander.MyDateTypeHandler"/>

</typeHandlers>

<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
<!--
<property name="offsetAsPageNum" value="true"/>
<property name="rowBoundsWithCount" value="true"/>
-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>

</configuration>



改完不好使,還得再類上加註解 @MappedJdbcTypes ,好了這個東西可以自定義默認值了。。。 除了業務代碼,難一點的東西就很吃力,現在工作都快2年了,源碼看的迷迷糊糊的。難過....你們有過這樣的感覺嗎?










 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章