从零实现Mybatis框架(一)
调用一个dao的接口到方法执行
从一个dao下的接口,到调用就能返回数据查询结果中间到底发生了什么?java本身接口是不能实例化的,必须要有具体的类,而一个类要直接使用只能使用其静态方法,要么对其类进行实例化对象。所有有点清楚的概念是,调用dao的接口返回数据,其中一定利用了代理模式。
先定义一个dao接口
public interface IUserDao {
Integer queryUserAge(String uid);
String queryUserName(String uid);
}
最简单MapperProxy的实现方式:
public void test_proxy_class(){
IUserDao userDao = (IUserDao) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{IUserDao.class},
(((proxy, method, args) -> "你被代理来了"))
);
String result = userDao.queryUserName("");
logger.info("测试结果:{}" , JSON.toJSONString(result));
}
对每一个接口进行代理,生成对应的代理对象,代理指定方法。
根据结果来看,成功实现了代理,能够通过一个接口调用其方法,获取结果。
但是目前有个问题,一般来说,我们会有多个dao接口,如果都要一个个自己定义代理对象就会很麻烦,所有我们通过一个MapperProxy工厂,批量来创建MapperProxy。
对于一个MapperProxy来说,它可以是能不同接口的,所以需要一个变量表示当前代理对象的接口。另外我们都知道一个方法应该有对应的sql结果之类的,不能是固定的文本,我们先定义一个Map来模拟这个过程,后续再具体实现。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -642454912998906586L;
// 模拟sqlSession
// 每个接口的方法对应一个sql执行等
private Map<String, String> sqlSession;
// 接口
private final Class<T> mapperInterface;
public MapperProxy(Map<String, String> sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断是否是Object类中的方法
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}else {
// 核心代理
return "你被代理了: " + sqlSession.get(mapperInterface.getName() + "." + method.getName());
}
}
}
对其进行简单工厂封装
public class MapperProxyFactory <T>{
public T newInstance(Map<String, String> sqlSession, Class<T> mapperInterface) {
// 创建代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
// 进行代理,返回代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
我们进行调用尝试
public void test_MapperProxyFactory(){
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>();
Map<String, String> sqlSession = new HashMap<>();
sqlSession.put("dao.IUserDao.queryUserName", "模拟执行Mapper.xml的queryUserName结果");
sqlSession.put("dao.IUserDao.queryUserAge", "模拟执行Mapper.xml的结果");
IUserDao userDao = factory.newInstance(sqlSession, IUserDao.class);
String result = userDao.queryUserName("10001");
logger.info("测试结果:{}" , JSON.toJSONString(result));
}
执行之后可以看到,我们成功完成了对MapperProxyFactory的实现,不用再一个个实现,只需要传入接口类,和sqlSession就能完成指定dao接口的方法调用。
自动扫描dao和SqlSession
进一步完善上面的,上面的调用接口方法还需要我们硬编码调用工厂,sqlSession之类的,不太方便。我们定义一个MapperRegistry自动扫描dao下面的接口完成MapperProxy,
public class MapperRegistry {
/*
将已添加的映射器代理加入HashMap中
*/
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new RuntimeException("Error getting mapper instance. Cause: "+ e, e);
}
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new RuntimeException("Type " + type + " is already known to the MapperRegistry.");
}
knownMappers.put(type, new MapperProxyFactory<>(type));
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public void addMappers(String packageName) {
Set<Class<?>> mapperSet = ClassScanner.scanPackage(packageName);
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
}
而对于之前的SqlSession,我们定义专门接口,主要是将sql执行方法拆分为两类,不带参数和带参数。在具体的SqlSession类里去实现具体的操作。
public interface SqlSession {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<T> T getMapper(Class<T> type);
}
例如:
public class DefaultSqlSession implements SqlSession {
private final MapperRegistry mapperRegistry;
public DefaultSqlSession(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@Override
public <T> T selectOne(String statement) {
return (T)("你被代理了!" + statement);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
return (T) ("你被代理了!" + "方法:"+ statement + "入参:" + parameter);
}
@Override
public <T> T getMapper(Class<T> type) {
return mapperRegistry.getMapper(type, this);
}
}
这样就是将SqlSession从Map变成的类的方法,方便后续进行扩展,根据指定参数执行具体的sql行为
这次我们在使用的时候,不需要手动定义一个个的MapperProxy和SqlSession,会自动扫描,和自动检测。
public void test_MapperProxyFactory(){
// 1. 注册Mapper
MapperRegistry registry = new MapperRegistry();
registry.addMappers("dao");
// 2. 从SqlSession工厂获取SqlSession
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(registry);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. 获取映射器对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
// 4. 测试验证
String result = userDao.queryUserName();
logger.info("测试结果:{}" , JSON.toJSONString(result));
}
进行测试,成功输出