在阅读本文之前,我相信你已经是一个 Mybatis ORM 框架工具使用的熟练工了,那你是否清楚这个 ORM 框架是怎么屏蔽我们对数据库操作的细节的?
比如我们使用 JDBC 的时候,需要手动建立数据库链接、编码 SQL 语句、执行数据库操作、自己封装返回结果等。
但在使用 ORM 框架后,只需要通过简单配置即可对定义的 DAO 接口进行数据库的操作了。
通常如果能找到大家所在事情的共性内容,具有统一的流程处理,那么它就是可以被凝聚和提炼的,做成通用的组件或者服务,被所有人进行使用,减少重复的人力投入。
而参考我们最开始使用 JDBC 的方式,从连接、查询、封装、返回,其实都一个固定的流程,那么这个过程就可以被提炼以及封装和补全大家所需要的功能。
当我们来设计一个 ORM 框架的过程中,首先要考虑怎么把用户定义的数据库操作接口、xml 配置的 SQL 语句、数据库三者联系起来。其实最适合的操作就是使用代理的方式进行处理,因为代理可以封装一个复杂的流程,来作用于接口的实现类,设计图如下:
来看一下这个设计图的流程:
MapperProxy
,通过代理类包装对数据库的操作,目前我们本章节会先提供一个简单的包装,模拟对数据库的调用。MapperProxy
代理类,提供工厂实例化操作 MapperProxyFactory#newInstance
#newInstance 意思是 MapperProxyFactory 中有一个 newInstance 方法,用来创建 MapperProxy 实例,为每个 IDAO
接口生成代理类。这块其实用到的就是一个简单工厂模式。接下来我们就按照这个设计实现一个简单的映射器代理操作,编码过程比较简单。如果对代理知识不熟悉可以先补充下代理的知识。
step-01
└─src
├─main
│ ├─java
│ │ └─top
│ │ └─it6666
│ │ └─mybatis
│ │ └─binding
│ └─resources
└─test
└─java
└─top
└─it6666
└─test
└─dao
工程源码:https://github.com/BNTang/Java-All/tree/main/mybatis-source-code/step-01
MyBatis 映射器代理类关系:
MapperProxy
负责实现 InvocationHandler
接口的 invoke
方法,最终所有的实际调用都会调用到这个方法包装的逻辑。MapperProxyFactory
是对 MapperProxy
的包装,对外提供实例化对象的操作。当我们后面开始给每个操作数据库的接口映射器注册代理的时候,就需要使用到这个工厂类了。
源码详见:top.it6666.mybatis.binding.MapperProxy
/**
* 映射器代理类
*
* @author BNTang
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final 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 {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return "你的被代理了!" + sqlSession.get(mapperInterface.getName() + "." + method.getName());
}
}
}
InvocationHandler#invoke
(#后面紧跟的是方法名,意思说的是 InvocationHandler
接口中的 invoke
方法)代理类接口,封装操作逻辑的方式,对外接口提供数据库操作对象。sqlSession
的 Map
对象,你可以想象成所有的数据库语句操作,都是通过 接口名称+方法名
称作为 key
,操作作为逻辑的方式进行使用的。那么在反射调用中则获取对应的操作直接执行并返回结果即可。当然这还只是最核心的简化流程,后续不断补充内容后,会看到对数据库的操作。Object.class.equals(method.getDeclaringClass())
判断。源码详见:top.it6666.mybatis.binding.MapperProxyFactory
/**
* 代理类工厂
* @author BNTang
*/
public class MapperProxyFactory<T> {
private final Class<?> mapperInterface;
public MapperProxyFactory(Class<?> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(Map<String, String> sqlSession) {
final MapperProxy<T> mapperProxy = (MapperProxy<T>) new MapperProxy<>(sqlSession, mapperInterface);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
}
Proxy.newProxyInstance
进行处理,那么这样的操作方式就显得比较麻烦了。JDK Proxy
的内容做几个案例补充下这块的内容,或者可以去看看我之前写的代理的文章。(https://www.cnblogs.com/BNTang/articles/13769281.html)top.it6666.test.dao.IUserDao:
public interface IUserDao {
String queryUserName(String uId);
Integer queryUserAge(String uId);
}
top.it6666.test:
/**
* @author BNTang
* @version 1.0
* @description 测试类
* @since 2024/4/16 星期二
**/
public class ApiTest {
private final Logger logger = LoggerFactory.getLogger(ApiTest.class);
@Test
public void test_MapperProxyFactory() {
MapperProxyFactory<IUserDao> factory = new MapperProxyFactory<>(IUserDao.class);
Map<String, String> sqlSession = new HashMap<>();
sqlSession.put("top.it6666.test.dao.IUserDao.queryUserName", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名");
sqlSession.put("top.it6666.test.dao.IUserDao.queryUserAge", "模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户年龄");
IUserDao userDao = factory.newInstance(sqlSession);
String res = userDao.queryUserName("10001");
logger.info("测试结果:{}", res);
}
}
MapperProxyFactory
工厂,并手动给 sqlSession Map
赋值,这里的赋值相当于模拟数据库中的操作。测试结果:
00:33:41.903 [main] INFO top.it6666.test.ApiTest - 测试结果:你的被代理了!模拟执行 Mapper.xml 中 SQL 语句的操作:查询用户姓名
从测试结果可以看到,我们的接口已经被代理类实现了,同时我们可以在代理类中进行自己的操作封装。那么在我们后续实现的数据库操作中,就可以对这部分内容进行扩展了。
着急和快,是最大的障碍!慢下来,慢下来,只有慢下来,你才能看到更全的信息,才能学到更扎实的技术。而那些满足你快的短篇内容虽然有时候更抓眼球,但也容易把人在技术学习上带偏,总想着越快越好。
如果您觉得文章对您有所帮助,欢迎您点赞、评论、转发,也欢迎您关注我的公众号『BNTang』,我会在公众号中分享更多的技术文章。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有