使用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年了,源码看的迷迷糊糊的。难过....你们有过这样的感觉吗?










 

posted @ 2019-11-05 11:00  你说累不累  阅读(11246)  评论(0编辑  收藏  举报