• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mybatis / mybatis-3 / 2726

26 Feb 2025 01:36PM UTC coverage: 87.221% (+0.004%) from 87.217%
2726

Pull #3379

github

web-flow
Merge bcb00f835 into b90a0f870
Pull Request #3379: Resolve type handler based on `java.lang.reflect.Type` instead of `Class` and respect runtime JDBC type

3820 of 4639 branches covered (82.35%)

608 of 675 new or added lines in 61 files covered. (90.07%)

14 existing lines in 4 files now uncovered.

9890 of 11339 relevant lines covered (87.22%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.58
/src/main/java/org/apache/ibatis/type/TypeHandlerRegistry.java
1
/*
2
 *    Copyright 2009-2025 the original author or authors.
3
 *
4
 *    Licensed under the Apache License, Version 2.0 (the "License");
5
 *    you may not use this file except in compliance with the License.
6
 *    You may obtain a copy of the License at
7
 *
8
 *       https://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *    Unless required by applicable law or agreed to in writing, software
11
 *    distributed under the License is distributed on an "AS IS" BASIS,
12
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *    See the License for the specific language governing permissions and
14
 *    limitations under the License.
15
 */
16
package org.apache.ibatis.type;
17

18
import java.io.InputStream;
19
import java.io.Reader;
20
import java.lang.reflect.Constructor;
21
import java.lang.reflect.Modifier;
22
import java.lang.reflect.ParameterizedType;
23
import java.lang.reflect.Type;
24
import java.math.BigDecimal;
25
import java.math.BigInteger;
26
import java.sql.Time;
27
import java.sql.Timestamp;
28
import java.time.Instant;
29
import java.time.LocalDate;
30
import java.time.LocalDateTime;
31
import java.time.LocalTime;
32
import java.time.Month;
33
import java.time.OffsetDateTime;
34
import java.time.OffsetTime;
35
import java.time.Year;
36
import java.time.YearMonth;
37
import java.time.ZonedDateTime;
38
import java.time.chrono.JapaneseDate;
39
import java.util.Arrays;
40
import java.util.Collection;
41
import java.util.Collections;
42
import java.util.Date;
43
import java.util.EnumMap;
44
import java.util.HashMap;
45
import java.util.Map;
46
import java.util.Map.Entry;
47
import java.util.Set;
48
import java.util.concurrent.ConcurrentHashMap;
49

50
import org.apache.ibatis.binding.MapperMethod.ParamMap;
51
import org.apache.ibatis.io.ResolverUtil;
52
import org.apache.ibatis.io.Resources;
53
import org.apache.ibatis.reflection.TypeParameterResolver;
54
import org.apache.ibatis.session.Configuration;
55

56
/**
57
 * @author Clinton Begin
58
 * @author Kazuki Shimizu
59
 */
60
public final class TypeHandlerRegistry {
61

62
  private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
1✔
63
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
1✔
64
  private final ConcurrentHashMap<Type, Constructor<?>> smartHandlers = new ConcurrentHashMap<>();
1✔
65
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
1✔
66

67
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
1✔
68

69
  @SuppressWarnings("rawtypes")
1✔
70
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
71

72
  /**
73
   * The default constructor.
74
   */
75
  public TypeHandlerRegistry() {
76
    this(new Configuration());
1✔
77
  }
1✔
78

79
  /**
80
   * The constructor that pass the MyBatis configuration.
81
   *
82
   * @param configuration
83
   *          a MyBatis configuration
84
   *
85
   * @since 3.5.4
86
   */
87
  public TypeHandlerRegistry(Configuration configuration) {
1✔
88
    // If a handler is registered against null JDBC type, it is the default handler for the Java type. Users can
89
    // override the default handler (e.g. `register(boolean.class, null, new YNBooleanTypeHandler())` or register a
90
    // custom handler for a specific Java-JDBC type combination (e.g. `register(boolean.class, JdbcType.CHAR, new
91
    // YNBooleanTypeHandler())`).
92
    register(new Type[] { Boolean.class, boolean.class }, new JdbcType[] { null }, BooleanTypeHandler.INSTANCE);
1✔
93
    register(new Type[] { Byte.class, byte.class }, new JdbcType[] { null }, ByteTypeHandler.INSTANCE);
1✔
94
    register(new Type[] { Short.class, short.class }, new JdbcType[] { null }, ShortTypeHandler.INSTANCE);
1✔
95
    register(new Type[] { Integer.class, int.class }, new JdbcType[] { null }, IntegerTypeHandler.INSTANCE);
1✔
96
    register(new Type[] { Long.class, long.class }, new JdbcType[] { null }, LongTypeHandler.INSTANCE);
1✔
97
    register(new Type[] { Float.class, float.class }, new JdbcType[] { null }, FloatTypeHandler.INSTANCE);
1✔
98
    register(new Type[] { Double.class, double.class }, new JdbcType[] { null }, DoubleTypeHandler.INSTANCE);
1✔
99
    register(new Type[] { Character.class, char.class }, new JdbcType[] { null }, new CharacterTypeHandler());
1✔
100
    register(String.class, null, StringTypeHandler.INSTANCE);
1✔
101
    register(Reader.class, null, new ClobReaderTypeHandler());
1✔
102
    register(BigInteger.class, null, new BigIntegerTypeHandler());
1✔
103
    register(BigDecimal.class, null, BigDecimalTypeHandler.INSTANCE);
1✔
104
    register(InputStream.class, null, new BlobInputStreamTypeHandler());
1✔
105
    register(Byte[].class, null, new ByteObjectArrayTypeHandler());
1✔
106
    register(byte[].class, null, ByteArrayTypeHandler.INSTANCE);
1✔
107
    register(Date.class, null, DateTypeHandler.INSTANCE);
1✔
108
    register(java.sql.Date.class, null, new SqlDateTypeHandler());
1✔
109
    register(Time.class, null, new SqlTimeTypeHandler());
1✔
110
    register(Timestamp.class, null, new SqlTimestampTypeHandler());
1✔
111
    register(Instant.class, null, new InstantTypeHandler());
1✔
112
    register(LocalDateTime.class, null, new LocalDateTimeTypeHandler());
1✔
113
    register(LocalDate.class, null, new LocalDateTypeHandler());
1✔
114
    register(LocalTime.class, null, new LocalTimeTypeHandler());
1✔
115
    register(OffsetDateTime.class, null, new OffsetDateTimeTypeHandler());
1✔
116
    register(OffsetTime.class, null, new OffsetTimeTypeHandler());
1✔
117
    register(ZonedDateTime.class, null, new ZonedDateTimeTypeHandler());
1✔
118
    register(Month.class, null, new MonthTypeHandler());
1✔
119
    register(Year.class, null, new YearTypeHandler());
1✔
120
    register(YearMonth.class, null, new YearMonthTypeHandler());
1✔
121
    register(JapaneseDate.class, null, new JapaneseDateTypeHandler());
1✔
122

123
    // These type handlers are used only for specific combinations of Java type and JDBC type.
124
    register(String.class, JdbcType.CLOB, ClobTypeHandler.INSTANCE);
1✔
125
    register(String.class, JdbcType.NCLOB, NClobTypeHandler.INSTANCE);
1✔
126
    register(new Type[] { String.class }, new JdbcType[] { JdbcType.NCHAR, JdbcType.NVARCHAR, JdbcType.LONGNVARCHAR },
1✔
127
        NStringTypeHandler.INSTANCE);
128
    register(new Type[] { Byte[].class }, new JdbcType[] { JdbcType.BLOB, JdbcType.LONGVARBINARY },
1✔
129
        new BlobByteObjectArrayTypeHandler());
130
    register(new Type[] { byte[].class }, new JdbcType[] { JdbcType.BLOB, JdbcType.LONGVARBINARY },
1✔
131
        BlobTypeHandler.INSTANCE);
132
    register(Date.class, JdbcType.DATE, DateOnlyTypeHandler.INSTANCE);
1✔
133
    register(Date.class, JdbcType.TIME, TimeOnlyTypeHandler.INSTANCE);
1✔
134
    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
1✔
135

136
    // Type handlers in the `jdbcTypeHandlerMap` are used when Java type is unknown or
137
    // as a last resort when no matching handler is found for the target Java type.
138
    // It is also used in some internal purposes like creating cache keys.
139
    // Although it is possible for users to override these mappings via register(JdbcType, TypeHandler),
140
    // it might have unexpected side-effect.
141
    // To configure type handlers for mapping to Map, for example, it is recommended to call the 3-args
142
    // version of register method. e.g. register(Object.class, JdbcType.DATE, new DateTypeHandler())
143
    jdbcTypeHandlerMap.put(JdbcType.BOOLEAN, BooleanTypeHandler.INSTANCE);
1✔
144
    jdbcTypeHandlerMap.put(JdbcType.BIT, BooleanTypeHandler.INSTANCE);
1✔
145
    jdbcTypeHandlerMap.put(JdbcType.TINYINT, ByteTypeHandler.INSTANCE);
1✔
146
    jdbcTypeHandlerMap.put(JdbcType.SMALLINT, ShortTypeHandler.INSTANCE);
1✔
147
    jdbcTypeHandlerMap.put(JdbcType.INTEGER, IntegerTypeHandler.INSTANCE);
1✔
148
    jdbcTypeHandlerMap.put(JdbcType.BIGINT, LongTypeHandler.INSTANCE);
1✔
149
    jdbcTypeHandlerMap.put(JdbcType.REAL, FloatTypeHandler.INSTANCE); // As per JDBC spec
1✔
150
    jdbcTypeHandlerMap.put(JdbcType.FLOAT, DoubleTypeHandler.INSTANCE); // As per JDBC spec
1✔
151
    jdbcTypeHandlerMap.put(JdbcType.DOUBLE, DoubleTypeHandler.INSTANCE);
1✔
152
    jdbcTypeHandlerMap.put(JdbcType.DECIMAL, BigDecimalTypeHandler.INSTANCE);
1✔
153
    jdbcTypeHandlerMap.put(JdbcType.NUMERIC, BigDecimalTypeHandler.INSTANCE);
1✔
154
    jdbcTypeHandlerMap.put(JdbcType.CHAR, StringTypeHandler.INSTANCE);
1✔
155
    jdbcTypeHandlerMap.put(JdbcType.VARCHAR, StringTypeHandler.INSTANCE);
1✔
156
    jdbcTypeHandlerMap.put(JdbcType.LONGVARCHAR, StringTypeHandler.INSTANCE);
1✔
157
    jdbcTypeHandlerMap.put(JdbcType.CLOB, ClobTypeHandler.INSTANCE);
1✔
158
    jdbcTypeHandlerMap.put(JdbcType.NVARCHAR, NStringTypeHandler.INSTANCE);
1✔
159
    jdbcTypeHandlerMap.put(JdbcType.NCHAR, NStringTypeHandler.INSTANCE);
1✔
160
    jdbcTypeHandlerMap.put(JdbcType.LONGNVARCHAR, NStringTypeHandler.INSTANCE);
1✔
161
    jdbcTypeHandlerMap.put(JdbcType.NCLOB, NClobTypeHandler.INSTANCE);
1✔
162
    jdbcTypeHandlerMap.put(JdbcType.ARRAY, new ArrayTypeHandler());
1✔
163
    jdbcTypeHandlerMap.put(JdbcType.BINARY, ByteArrayTypeHandler.INSTANCE);
1✔
164
    jdbcTypeHandlerMap.put(JdbcType.VARBINARY, ByteArrayTypeHandler.INSTANCE);
1✔
165
    jdbcTypeHandlerMap.put(JdbcType.LONGVARBINARY, ByteArrayTypeHandler.INSTANCE);
1✔
166
    jdbcTypeHandlerMap.put(JdbcType.BLOB, BlobTypeHandler.INSTANCE);
1✔
167
    jdbcTypeHandlerMap.put(JdbcType.TIMESTAMP, DateTypeHandler.INSTANCE);
1✔
168
    jdbcTypeHandlerMap.put(JdbcType.DATE, DateOnlyTypeHandler.INSTANCE);
1✔
169
    jdbcTypeHandlerMap.put(JdbcType.TIME, TimeOnlyTypeHandler.INSTANCE);
1✔
170
  }
1✔
171

172
  /**
173
   * Set a default {@link TypeHandler} class for {@link Enum}. A default {@link TypeHandler} is
174
   * {@link org.apache.ibatis.type.EnumTypeHandler}.
175
   *
176
   * @param typeHandler
177
   *          a type handler class for {@link Enum}
178
   *
179
   * @since 3.4.5
180
   */
181
  public void setDefaultEnumTypeHandler(@SuppressWarnings("rawtypes") Class<? extends TypeHandler> typeHandler) {
182
    this.defaultEnumTypeHandler = typeHandler;
×
183
  }
×
184

185
  public boolean hasTypeHandler(Type javaType) {
186
    return hasTypeHandler(javaType, null);
1✔
187
  }
188

189
  @Deprecated(since = "3.6.0", forRemoval = true)
190
  public boolean hasTypeHandler(TypeReference<?> javaTypeReference) {
191
    return hasTypeHandler(javaTypeReference, null);
×
192
  }
193

194
  public boolean hasTypeHandler(Type javaType, JdbcType jdbcType) {
195
    return javaType != null && getTypeHandler(javaType, jdbcType) != null;
1!
196
  }
197

198
  @Deprecated(since = "3.6.0", forRemoval = true)
199
  public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
200
    return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
×
201
  }
202

203
  @Deprecated(since = "3.6.0", forRemoval = true)
204
  public TypeHandler<?> getMappingTypeHandler(Class<? extends TypeHandler<?>> handlerType) {
205
    return allTypeHandlersMap.get(handlerType);
1✔
206
  }
207

208
  public TypeHandler<?> getTypeHandler(Type type) {
209
    return getTypeHandler(type, null);
1✔
210
  }
211

212
  @Deprecated(since = "3.6.0", forRemoval = true)
213
  public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference) {
214
    return getTypeHandler(javaTypeReference, null);
1✔
215
  }
216

217
  public TypeHandler<?> getTypeHandler(JdbcType jdbcType) {
218
    return jdbcTypeHandlerMap.get(jdbcType);
1✔
219
  }
220

221
  @SuppressWarnings("unchecked")
222
  @Deprecated(since = "3.6.0", forRemoval = true)
223
  public <T> TypeHandler<T> getTypeHandler(TypeReference<T> javaTypeReference, JdbcType jdbcType) {
224
    return (TypeHandler<T>) getTypeHandler(javaTypeReference.getRawType(), jdbcType);
1✔
225
  }
226

227
  public TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType, Class<? extends TypeHandler<?>> typeHandlerClass) {
228
    TypeHandler<?> typeHandler = getTypeHandler(type, jdbcType);
1✔
229
    if (typeHandler != null && (typeHandlerClass == null || typeHandler.getClass().equals(typeHandlerClass))) {
1✔
230
      return typeHandler;
1✔
231
    }
232
    if (typeHandlerClass == null) {
1✔
233
      typeHandler = getSmartHandler(type, jdbcType);
1✔
234
    } else {
235
      typeHandler = getMappingTypeHandler(typeHandlerClass);
1✔
236
      if (typeHandler == null) {
1✔
237
        typeHandler = getInstance(type, typeHandlerClass);
1✔
238
      }
239
    }
240
    return typeHandler;
1✔
241
  }
242

243
  public TypeHandler<?> getTypeHandler(Type type, JdbcType jdbcType) {
244
    if (ParamMap.class.equals(type)) {
1✔
245
      return null;
1✔
246
    } else if (type == null) {
1✔
247
      return getTypeHandler(jdbcType);
1✔
248
    }
249

250
    TypeHandler<?> handler = null;
1✔
251
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
1✔
252

253
    if (Object.class.equals(type)) {
1✔
254
      if (jdbcHandlerMap != null) {
1✔
255
        handler = jdbcHandlerMap.get(jdbcType);
1✔
256
      }
257
      return handler;
1✔
258
    }
259

260
    if (jdbcHandlerMap != null) {
1✔
261
      handler = jdbcHandlerMap.get(jdbcType);
1✔
262
      if (handler == null) {
1✔
263
        handler = jdbcHandlerMap.get(null);
1✔
264
      }
265
      if (handler == null) {
1✔
266
        // #591
267
        handler = pickSoleHandler(jdbcHandlerMap);
1✔
268
      }
269
    }
270
    if (handler == null) {
1✔
271
      handler = getSmartHandler(type, jdbcType);
1✔
272
    }
273
    if (handler == null && type instanceof ParameterizedType) {
1✔
274
      handler = getTypeHandler((Class<?>) ((ParameterizedType) type).getRawType(), jdbcType);
1✔
275
    }
276
    return handler;
1✔
277
  }
278

279
  private TypeHandler<?> getSmartHandler(Type type, JdbcType jdbcType) {
280
    Constructor<?> candidate = null;
1✔
281

282
    for (Entry<Type, Constructor<?>> entry : smartHandlers.entrySet()) {
1✔
283
      Type registeredType = entry.getKey();
1✔
284
      if (registeredType.equals(type)) {
1✔
285
        candidate = entry.getValue();
1✔
286
        break;
1✔
287
      }
288
      if (registeredType instanceof Class) {
1✔
289
        if (type instanceof Class && ((Class<?>) registeredType).isAssignableFrom((Class<?>) type)) {
1!
290
          candidate = entry.getValue();
1✔
291
        }
292
      } else if (registeredType instanceof ParameterizedType) {
1!
293
        Class<?> registeredClass = (Class<?>) ((ParameterizedType) registeredType).getRawType();
1✔
294
        if (type instanceof ParameterizedType) {
1✔
295
          Class<?> clazz = (Class<?>) ((ParameterizedType) type).getRawType();
1✔
296
          if (registeredClass.isAssignableFrom(clazz)) {
1✔
297
            candidate = entry.getValue();
1✔
298
          }
299
        }
300
      }
301
    }
1✔
302

303
    if (candidate == null) {
1✔
304
      if (type instanceof Class) {
1✔
305
        Class<?> clazz = (Class<?>) type;
1✔
306
        if (Enum.class.isAssignableFrom(clazz)) {
1✔
307
          Class<?> enumClass = clazz.isAnonymousClass() ? clazz.getSuperclass() : clazz;
1!
308
          TypeHandler<?> enumHandler = getInstance(enumClass, defaultEnumTypeHandler);
1✔
309
          register(new Type[] { enumClass }, new JdbcType[] { jdbcType }, enumHandler);
1✔
310
          return enumHandler;
1✔
311
        }
312
      }
313
      return null;
1✔
314
    }
315

316
    try {
317
      TypeHandler<?> typeHandler = (TypeHandler<?>) candidate.newInstance(type);
1✔
318
      register(type, jdbcType, typeHandler);
1✔
319
      return typeHandler;
1✔
NEW
320
    } catch (ReflectiveOperationException e) {
×
NEW
321
      throw new TypeException("Failed to invoke constructor " + candidate.toString(), e);
×
322
    }
323
  }
324

325
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) {
326
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(type);
1✔
327
    if (jdbcHandlerMap != null) {
1✔
328
      return NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap) ? null : jdbcHandlerMap;
1✔
329
    }
330
    if (type instanceof Class) {
1✔
331
      Class<?> clazz = (Class<?>) type;
1✔
332
      if (!Enum.class.isAssignableFrom(clazz)) {
1✔
333
        jdbcHandlerMap = getJdbcHandlerMapForSuperclass(clazz);
1✔
334
      }
335
    }
336
    typeHandlerMap.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap);
1✔
337
    return jdbcHandlerMap;
1✔
338
  }
339

340
  private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMapForSuperclass(Class<?> clazz) {
341
    Class<?> superclass = clazz.getSuperclass();
1✔
342
    if (superclass == null || Object.class.equals(superclass)) {
1✔
343
      return null;
1✔
344
    }
345
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = typeHandlerMap.get(superclass);
1✔
346
    if (jdbcHandlerMap != null) {
1✔
347
      return jdbcHandlerMap;
1✔
348
    }
349
    return getJdbcHandlerMapForSuperclass(superclass);
1✔
350
  }
351

352
  private TypeHandler<?> pickSoleHandler(Map<JdbcType, TypeHandler<?>> jdbcHandlerMap) {
353
    TypeHandler<?> soleHandler = null;
1✔
354
    for (TypeHandler<?> handler : jdbcHandlerMap.values()) {
1✔
355
      if (soleHandler == null) {
1!
356
        soleHandler = handler;
1✔
UNCOV
357
      } else if (!handler.getClass().equals(soleHandler.getClass())) {
×
358
        // More than one type handlers registered.
UNCOV
359
        return null;
×
360
      }
361
    }
1✔
362
    return soleHandler;
1✔
363
  }
364

365
  public void register(JdbcType mappedJdbcType, TypeHandler<?> handler) {
NEW
366
    jdbcTypeHandlerMap.put(mappedJdbcType, handler);
×
UNCOV
367
  }
×
368

369
  //
370
  // REGISTER INSTANCE
371
  //
372

373
  // Only handler
374

375
  public <T> void register(TypeHandler<T> handler) {
376
    register(mappedJavaTypes(handler.getClass()), mappedJdbcTypes(handler.getClass()), handler);
1✔
377
  }
1✔
378

379
  // java type + handler
380

381
  public void register(Class<?> mappedJavaType, TypeHandler<?> handler) {
382
    register((Type) mappedJavaType, handler);
1✔
383
  }
1✔
384

385
  private void register(Type mappedJavaType, TypeHandler<?> handler) {
386
    register(new Type[] { mappedJavaType }, mappedJdbcTypes(handler.getClass()), handler);
1✔
387
  }
1✔
388

389
  @Deprecated(since = "3.6.0", forRemoval = true)
390
  public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
391
    register(javaTypeReference.getRawType(), handler);
1✔
392
  }
1✔
393

394
  // java type + jdbc type + handler
395

396
  public void register(Type mappedJavaType, JdbcType mappedJdbcType, TypeHandler<?> handler) {
397
    register(new Type[] { mappedJavaType }, new JdbcType[] { mappedJdbcType }, handler);
1✔
398
  }
1✔
399

400
  private void register(Type[] mappedJavaTypes, JdbcType[] mappedJdbcTypes, TypeHandler<?> handler) {
401
    for (Type javaType : mappedJavaTypes) {
1✔
402
      if (javaType == null) {
1!
NEW
403
        continue;
×
404
      }
405

406
      typeHandlerMap.compute(javaType, (k, v) -> {
1✔
407
        Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
1✔
408
        for (JdbcType jdbcType : mappedJdbcTypes) {
1✔
409
          map.put(jdbcType, handler);
1✔
410
        }
411
        return map;
1✔
412
      });
413

414
      if (javaType instanceof ParameterizedType) {
1✔
415
        // MEMO: add annotation to skip this?
416
        Type rawType = ((ParameterizedType) javaType).getRawType();
1✔
417
        typeHandlerMap.compute(rawType, (k, v) -> {
1✔
418
          Map<JdbcType, TypeHandler<?>> map = (v == null || v == NULL_TYPE_HANDLER_MAP ? new HashMap<>() : v);
1!
419
          for (JdbcType jdbcType : mappedJdbcTypes) {
1✔
420
            map.merge(jdbcType, handler, (handler1, handler2) -> handler1.equals(handler2) ? handler1
1!
421
                : new ConflictedTypeHandler((Class<?>) rawType, jdbcType, handler1, handler2));
1✔
422
          }
423
          return map;
1✔
424
        });
425
      }
426
    }
427

428
    allTypeHandlersMap.put(handler.getClass(), handler);
1✔
429
  }
1✔
430

431
  //
432
  // REGISTER CLASS
433
  //
434

435
  // Only handler type
436

437
  public void register(Class<?> handlerClass) {
438
    register(mappedJavaTypes(handlerClass), mappedJdbcTypes(handlerClass), handlerClass);
1✔
439
  }
1✔
440

441
  // java type + handler type
442

443
  @Deprecated(since = "3.6.0", forRemoval = true)
444
  public void register(String javaTypeClassName, String typeHandlerClassName) throws ClassNotFoundException {
445
    register(Resources.classForName(javaTypeClassName), Resources.classForName(typeHandlerClassName));
×
446
  }
×
447

448
  public void register(Type mappedJavaType, Class<?> handlerClass) {
449
    register(new Type[] { mappedJavaType }, mappedJdbcTypes(handlerClass), handlerClass);
1✔
450
  }
1✔
451

452
  // java type + jdbc type + handler type
453

454
  public void register(Type mappedJavaType, JdbcType mappedJdbcType, Class<?> handlerClass) {
455
    register(new Type[] { mappedJavaType }, new JdbcType[] { mappedJdbcType }, handlerClass);
1✔
456
  }
1✔
457

458
  private void register(Type[] mappedJavaTypes, JdbcType[] mappedJdbcTypes, Class<?> handlerClass) {
459
    if (!TypeHandler.class.isAssignableFrom(handlerClass)) {
1!
NEW
460
      throw new IllegalArgumentException(String.format("'%s' does not implement TypeHandler.", handlerClass.getName()));
×
461
    }
462
    for (Constructor<?> constructor : handlerClass.getConstructors()) {
1✔
463
      if (constructor.getParameterCount() != 1) {
1✔
464
        continue;
1✔
465
      }
466
      Class<?> argType = constructor.getParameterTypes()[0];
1✔
467
      if (Type.class.equals(argType) || Class.class.equals(argType)) {
1!
468
        for (Type javaType : mappedJavaTypes) {
1✔
469
          smartHandlers.computeIfAbsent(javaType, k -> constructor);
1✔
470
        }
471
        return;
1✔
472
      }
473
    }
474
    // It is not a smart handler
475
    register(mappedJavaTypes, mappedJdbcTypes, getInstance(null, handlerClass));
1✔
476
  }
1✔
477

478
  private Type[] mappedJavaTypes(Class<?> clazz) {
479
    MappedTypes mappedTypesAnno = clazz.getAnnotation(MappedTypes.class);
1✔
480
    if (mappedTypesAnno != null) {
1✔
481
      return mappedTypesAnno.value();
1✔
482
    }
483
    return TypeParameterResolver.resolveClassTypeParams(TypeHandler.class, clazz);
1✔
484
  }
485

486
  private JdbcType[] mappedJdbcTypes(Class<?> clazz) {
487
    MappedJdbcTypes mappedJdbcTypesAnno = clazz.getAnnotation(MappedJdbcTypes.class);
1✔
488
    if (mappedJdbcTypesAnno != null) {
1✔
489
      JdbcType[] jdbcTypes = mappedJdbcTypesAnno.value();
1✔
490
      if (mappedJdbcTypesAnno.includeNullJdbcType()) {
1✔
491
        int newLength = jdbcTypes.length + 1;
1✔
492
        jdbcTypes = Arrays.copyOf(jdbcTypes, newLength);
1✔
493
        jdbcTypes[newLength - 1] = null;
1✔
494
      }
495
      return jdbcTypes;
1✔
496
    }
497
    return new JdbcType[] { null };
1✔
498
  }
499

500
  // Construct a handler (used also from Builders)
501

502
  @SuppressWarnings("unchecked")
503
  public <T> TypeHandler<T> getInstance(Type javaType, Class<?> handlerClass) {
504
    Constructor<?> c;
505
    try {
506
      if (javaType != null) {
1✔
507
        try {
508
          c = handlerClass.getConstructor(Type.class);
1✔
509
          return (TypeHandler<T>) c.newInstance(javaType);
1✔
510
        } catch (NoSuchMethodException ignored) {
1✔
511
        }
512
        if (javaType instanceof Class) {
1!
513
          try {
514
            c = handlerClass.getConstructor(Class.class);
1✔
515
            return (TypeHandler<T>) c.newInstance(javaType);
1✔
516
          } catch (NoSuchMethodException ignored) {
1✔
517
          }
518
        }
519
      }
520
      try {
521
        c = handlerClass.getConstructor();
1✔
522
        return (TypeHandler<T>) c.newInstance();
1✔
NEW
523
      } catch (NoSuchMethodException e) {
×
NEW
524
        throw new TypeException("Unable to find a usable constructor for " + handlerClass, e);
×
525
      }
NEW
526
    } catch (ReflectiveOperationException e) {
×
NEW
527
      throw new TypeException("Failed to invoke constructor for handler " + handlerClass, e);
×
528
    }
529
  }
530

531
  // scan
532

533
  public void register(String packageName) {
534
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
1✔
535
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
1✔
536
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
1✔
537
    for (Class<?> type : handlerSet) {
1✔
538
      // Ignore inner classes and interfaces (including package-info.java) and abstract classes
539
      if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
1!
540
        register(type);
1✔
541
      }
542
    }
1✔
543
  }
1✔
544

545
  // get information
546

547
  /**
548
   * Gets the type handlers. Used by mybatis-guice.
549
   *
550
   * @return the type handlers
551
   *
552
   * @since 3.2.2
553
   */
554
  public Collection<TypeHandler<?>> getTypeHandlers() {
555
    return Collections.unmodifiableCollection(allTypeHandlersMap.values());
×
556
  }
557

558
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc