2 Mybatis基础支持层
2.1 类型转换模块
JDBC的数据类型与Java的数据类型并不是完全对应的,在PreparedStatement
为SQL绑定参数时,需要从Java类型转换成JDBC类型,当查询出结果时,又需要将JDBC类型转成Java类型。
该模块的功能作用非常明确,也很容易理解,此处只是学习源码怎么设计,怎么优雅的去实现这个功能。
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
有许多实现子类,每个子类负责一个类型的转换。
以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;
}
}
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;
}
}