注意:
学习源码一定一定不要太关注代码的编写,而是注意代码实现思想:
通过设问方式来体现代码中的思想;方法:5W+1H
源代码: https://gitee.com/xbhog/mybatis-xbhog ; https://github.com/xbhog/mybatis-xbhog ;交个朋友,有价值欢迎star。
上一局实现Mapper接口和映射器通过代理类的方式进行链接。
上一局测试类:【简写MyBatis】01-简单映射器;虽然基本功能实现了,但是还不智能,可以发现该测试类中的映射器代理工厂只能实现单一的接口代理,SqlSession也需要规范化处理;将映射器代理和方法的调用类似服务进行包装。
@Test
public void test_MapperProxyFactory() {
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
Map<String, String> sqlSession = new HashMap<>();
sqlSession .put("com.xbhog.IUserDao.getUserName","模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
IUserDao userDao = factory.newInstance(sqlSession);
String res = userDao.queryUserName("10001");
logger.info("测试结果:{}", res);
}
项目结构:
└─src
├─main
│ └─java
│ └─com
│ └─xbhog
│ ├─binding
│ │ ├─MapperProxy.java
│ │ ├─MapperProxyFactory.java
│ │ └─MapperRegistry.java
│ └─session
│ ├─DefaultSqlSession.java
│ ├─DefaultSqlSessionFactory.java
│ ├─SqlSession.java
│ └─SqlSessionFactory.java
└─test
└─java
└─com
└─xbhog
├─AppTest.java
└─IUserDao.java
项目类图
可以通过自定义MapperRegistry这个类实现该功能,当然你也可以叫其他的名字(zhangsan、lisi😅);我们只需要将上一局的MapperProxyFactory封装到MapperRegistry,把需要扫描和注册的接口保存到Map中进行预处理,最后代理进行随时使用就可以了;铺垫结束,开始上代码。
先扫描包下所有的Class类,然后保存到Map中。
package com.xbhog;
import cn.hutool.core.lang.ClassScanner;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author xbhog
* @describe: 接口注册器
* @date 2024/2/25
*/
public class MapperRegistry {
private final Map<Class<?>,MapperProxyFactory<?>> interfaceMaps = new HashMap<>();
public void addMapper(String packageName){
Set<Class<?>> scanPackage = ClassScanner.scanPackage(packageName);
scanPackage.forEach(clazz -> {
addMappers(clazz);
});
}
private void addMappers(Class<?> clazz) {
if(clazz.isInterface()){
//判断是否重复添加
if(haveInterface(clazz)){
throw new RuntimeException("Type " + clazz + " is already known to the MapperRegistry.");
}
}
// 注册映射器代理工厂
interfaceMaps.put(clazz, new MapperProxyFactory<>(clazz));
}
private boolean haveInterface(Class<?> clazz) {
return interfaceMaps.containsKey(clazz);
}
}
然后将上一局的接口和代理工厂操作封装进方法中。
public <T> T getMapper(Class<T> type, SqlSession sqlSession){
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) interfaceMaps.get(type);
if(Objects.isNull(mapperProxyFactory)){
throw new RuntimeException("Type " + type + " is not known to the MapperRegistry.");
}
return (T)mapperProxyFactory.newInstance(sqlSession);
}
先定义一个执行Sql、获取映射器的标准接口:
/**
* @author xbhog
* @describe: 定义简单的Mapper操作方法
* @date 2024/2/25
*/
public interface SqlSession {
<T> T selectOne(String statement,Object parameter);
/**
*得到接口映射器
* @param type 接口类型
* @return
*/
<T> T getMapper(Class<T> type);
}
接口实现方式:
package com.xbhog.session;
import com.xbhog.binding.MapperRegistry;
/**
* @author xbhog
* @describe:
* @date 2024/2/25
*/
public class DefaultSqlSession implements SqlSession{
private MapperRegistry mapperRegistry;
public DefaultSqlSession(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@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);
}
}
测试一下:
package com.xbhog;
import com.xbhog.binding.MapperProxyFactory;
import com.xbhog.binding.MapperRegistry;
import com.xbhog.session.DefaultSqlSession;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.util.HashMap;
import java.util.Map;
/**
* Unit test for simple App.
*/
public class AppTest extends TestCase {
/**
* Rigourous Test :-)
*/
public void testApp() {
MapperRegistry mapperRegistry = new MapperRegistry();
mapperRegistry.addMapper("com.xbhog");
DefaultSqlSession sqlSession = new DefaultSqlSession(mapperRegistry);
IUserDao user = sqlSession.getMapper(IUserDao.class);
String userName = user.getUserName("xbhog");
System.out.println("输出的信息:"+userName);
}
}
到这里其实已经可以满足需求了,但是看了下源码发现还是不行,它最外层又封装了一层代理工厂;应该是为了后续的代码扩展,简单工厂模式有助于代码的模块性和可维护性,功能上后续会有配置管理、资源管理、执行器选择和插件等需求;走一步看三步的老狐狸(┬┬﹏┬┬)。先抄作业。
package com.xbhog.session;
import com.xbhog.binding.MapperRegistry;
/**
* @author xbhog
* @describe:
* @date 2024/2/25
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private final MapperRegistry mapperRegistry;
public DefaultSqlSessionFactory(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(mapperRegistry);
}
}
package com.xbhog;
import com.xbhog.binding.MapperProxyFactory;
import com.xbhog.binding.MapperRegistry;
import com.xbhog.session.DefaultSqlSession;
import com.xbhog.session.DefaultSqlSessionFactory;
import com.xbhog.session.SqlSession;
import com.xbhog.session.SqlSessionFactory;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.util.HashMap;
import java.util.Map;
/**
* Unit test for simple App.
*/
public class AppTest extends TestCase {
/**
* Rigourous Test :-)
*/
public void testApp() {
MapperRegistry mapperRegistry = new MapperRegistry();
mapperRegistry.addMapper("com.xbhog");
DefaultSqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(mapperRegistry);
SqlSession sqlSession = sqlSessionFactory.openSession();
IUserDao user = sqlSession.getMapper(IUserDao.class);
String userName = user.getUserName("xbhog");
System.out.println("输出的信息:"+userName);
}
}
需要注意的是:通过这两节可以看到mybatis中运用了大量的工厂模式;对外提供统一的方法,屏蔽细节以及上下文的关联关系,最终目的服务于用户,简化使用。
https://mp.weixin.qq.com/s/o6lnWJqU_6FNO8HpxAs9gA
ChatGPT问答