Mybatis技术内幕(2)基础支持层

本文深入探讨Mybatis的基础支持层,包括类型转换模块和缓存模块。类型转换涉及JdbcType枚举、TypeHandler接口及其注册类TypeHandlerRegistry,详细解释了类型转换的过程。缓存模块介绍了Cache接口及常见的缓存装饰器如BlockingCache和SoftCache,展示了Mybatis如何实现高效的缓存策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2 Mybatis基础支持层

2.1 类型转换模块

JDBC的数据类型与Java的数据类型并不是完全对应的,在PreparedStatement为SQL绑定参数时,需要从Java类型转换成JDBC类型,当查询出结果时,又需要将JDBC类型转成Java类型。

image-20200801095150728

该模块的功能作用非常明确,也很容易理解,此处只是学习源码怎么设计,怎么优雅的去实现这个功能。

2.1.1 JdbcType枚举类

首先,JDBC的所有数据类型都记录在java.sql.Types类中,每一种类型都用final static int表示。

public class Types {
        public final static int BIT             =  -7;

        public final static int TINYINT         =  -6;

        public final static int SMALLINT        =   5;

在Mybatis中,使用了一个JdbcType枚举类去代表java.sql.Types,其实就是包装了一下sql中的Types类而已。

public enum JdbcType {
  //就是包装一下java.sql.Types
  ARRAY(Types.ARRAY),
  BIT(Types.BIT),
  TINYINT(Types.TINYINT),
  SMALLINT(Types.SMALLINT),
    ......
}

JdbcType类中还定义了一个静态Map集合维护常量编码与JdbcType之间的对应关系

public final int TYPE_CODE;
private static Map<Integer,JdbcType> codeLookup = new HashMap<Integer,JdbcType>();

//一开始就将数字对应的枚举型放入hashmap
static {
  for (JdbcType type : JdbcType.values()) {
    codeLookup.put(type.TYPE_CODE, type);
  }
}

2.1.2 TypeHandler

TypeHandler为类型转换器的接口,定义了JdbcType与Java数据类型之间互转的方法。

为了方便用户自定义TypeHandler,Mybatis提供了BaseTypeHandler这个抽象类,只需要重写setNonNullParameter方法和getNullableResult方法。

  • setNonNullParameter:主要负责将Java类型的参数转换成JdbcType类型,并绑定到SQL。
  • getNullableResult:将JdbcType类型的数据转成Java类型并封装成Java对象。

BaseTypeHandler有许多实现子类,每个子类负责一个类型的转换。

image-20200801101643978

以IntegerTypeHandler为例,看一下源码实现:

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    return rs.getInt(columnName);
  }

  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    return rs.getInt(columnIndex);
  }

  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    return cs.getInt(columnIndex);
  }
}

基本上依赖于JDBC实现即可。

2.1.3 TypeHandlerRegistry

我们已经知道,要将JdbcType与Java Type之间的转换只需要使用对应的TypeHandler实现类即可,但是如何知道该使用哪个实现类呢?这个功能就是由TypeHandlerRegistry完成。

public final class TypeHandlerRegistry {
	//记录JdbcType与TypeHandler之间的映射关系,JdbcType为2.1.1中定义的枚举类型。
    //主要用于JDBC类型转向Java类型
    private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
    
    //用于Java类型转成JDBC类型,由于Java类型转成JDBC类型可能有多种情况,如string可能转成char和varchar
    //所以Map中的value还是一个Map,要根据具体转成哪种JDBC类型来确定
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
    
  	//用于记录所有的TypeHandler类型以及该类型对应的对象,用于快速找到对象执行转换
    private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

    //空TypeHandler标识
    private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
}

再看一下TypeHandlerRegistry的构造函数,在构造函数中,初始化好以上Map,注册所有的TypeHandler。

public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());
    .......
}

register()方法实现了注册TypeHandler对象的功能,注册即向上面几个Map添加对应的键值对。

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
        Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
        if (map == null || map == NULL_TYPE_HANDLER_MAP) {
            map = new HashMap<>();
        }
        map.put(jdbcType, handler);
        typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
}

2.2 缓存模块

Mybatis中的缓存是两层结构的:一级缓存和二级缓存,但是本质相同,都是Cache接口的实现。

2.2.1 Cache接口

public interface Cache {

  String getId();  //该缓存对象的ID

  void putObject(Object key, Object value);  //添加缓存

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();

  default ReadWriteLock getReadWriteLock() {
    return null;
  }

}

Cache的实现类非常多,但是只有一个类是直接实现了Cache的功能,其他的都是装饰器类。PerpetualCache直接实现了缓存的功能,实现也非常简单,定义Map用于增删查即可。

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

要想实现各种不同功能的缓存,就需要在对象上加装饰器!这就是典型的装饰器模式。

2.2.2 常见的缓存装饰器

BlockingCache 阻塞版本的缓存装饰器

通过构造函数传入一个Cache实例对象,然后对该实例对象进行装饰。

public class BlockingCache implements Cache {

    private long timeout;
    private final Cache delegate;  //被装饰对象
    private final ConcurrentHashMap<Object, ReentrantLock> locks;  //每个key都有一把锁

    public BlockingCache(Cache delegate) {
        this.delegate = delegate;
        this.locks = new ConcurrentHashMap<>();
    }

    @Override
    public void putObject(Object key, Object value) {
        try {
            delegate.putObject(key, value);
        } finally {
            releaseLock(key);  //当放入key时释放该key的锁
        }
    }
    @Override
    public Object getObject(Object key) {
        acquireLock(key);  //当查询key时加锁
        Object value = delegate.getObject(key);
        if (value != null) {  //如果没有查询到缓存时,不会释放锁,此时阻塞其他查询该key的线程。直到查询到该key才释放锁
            releaseLock(key);
        }
        return value;
    }
}

image-20200801115117037

Cache cache = new PerpetualCache("123456");  //创建一个缓存对象
cache = new BlockingCache(cache);  //给该缓存对象加一个装饰器

SoftCache 软引用

Java的四种引用类型中,软引用的作用在于,当内存不足时,会考虑对软引用对象进行垃圾回收,这样就不会因为缓存的原因造成OOM而终止程序。

public class SoftCache implements Cache {
    private final Deque<Object> hardLinksToAvoidGarbageCollection;
    //引用队列,用于记录已经被垃圾回收的软引用对象
    private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
    private final Cache delegate;
    private int numberOfHardLinks;

    public SoftCache(Cache delegate) {
        this.delegate = delegate;
        this.numberOfHardLinks = 256;
        this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
        this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
    }
}

在SoftCache中,定义了一个SoftEntry内部类,用于缓存项中的value,在SoftEntry中,key为强引用,value为软引用。

private static class SoftEntry extends SoftReference<Object> {
    private final Object key;

    SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
        super(value, garbageCollectionQueue);
        this.key = key;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值