😀前言
本篇博文是MyBatis 底层机制的核心实现,简单的手法让MyBatis 不再神秘,希望能够帮助到你😊
🏠个人主页:晨犀主页
🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉
💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客,感谢大家的观看🥰
如果文章有什么需要改进的地方还请大佬不吝赐教 先在次感谢啦😊
文章目录
- 自己实现MyBatis 底层机制[下]
- 实现任务阶段3- 将Sqlsession 封装到执行器
- 分析示意图
- 代码实现
- 完成测试
- 实现任务阶段4- 开发Mapper 接口和Mapper.xml
- 分析【示意图】
- 代码实现
- 实现任务阶段5- 开发和Mapper 接口相映射的MapperBean
- 分析示意图
- 代码实现
- 实现任务阶段6
- 运行结果示意
- 代码实现
- 完成测试
- 实现任务阶段7- 实现动态代理Mapper 的方法
- 分析示意图
- 代码实现
- 完成测试
- 😄总结
自己实现MyBatis 底层机制[下]
实现任务阶段3- 将Sqlsession 封装到执行器
分析示意图
先观察原生MyBatis 的SqlSession 接口和默认实现
代码实现
- 创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSqlsession.java
/**
* NlcSqlSession: 搭建Configuration(连接)和Executor之间桥梁
* 这里有操作DB的方法-
*/
public class NlcSqlSession {
//属性
//执行器
private Executor executor = new NlcExecutor();
//配置
private NlcConfiguration nlcConfiguration =
new NlcConfiguration();
//编写方法SelectOne 返回一条记录-对象 [做了简化]
//说明: 在原生的Mybatis中 statement 不是sql ,而是要执行的接口方法
public <T> T selectOne(String statement, Object parameter) {
return executor.query(statement, parameter);
}
}
完成测试
- 修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java , 增加测试方法
@Test
public void testNlcSqlsesion() {
NlcSqlsession nlcSqlsession = new NlcSqlsession();
Monster monster = nlcSqlsession.selectOne("select * from monster where id=?", 2);
System.out.println("monster== " + monster);
}
- 测试效果
实现任务阶段4- 开发Mapper 接口和Mapper.xml
分析【示意图】
代码实现
1 、创建nlc-mybatis\src\main\java\com\nlc\mapper\MonsterMapper.java
//MonsterMapper: 声明对db的crud方法
public interface MonsterMapper {
//查询方法
public Monster getMonsterById(Integer id);
}
2、创建nlc-mybatis\src\main\resources\MonsterMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.nlc.mapper.MonsterMapper">
<!--实现配置接口方法getMonsterById-->
<select id="getMonsterById" resultType="com.nlc.entity.Monster">
select * from monster where id = ?
</select>
</mapper>
实现任务阶段5- 开发和Mapper 接口相映射的MapperBean
分析示意图
代码实现
1 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\config\Function.java , 对应Mapper 的方法信息
// Function: 记录对应的Mapper的方法信息
public class Function {
//属性
private String sqlType; //sql类型,比如select,insert,update, delete
private String funcName; //方法名
private String sql;//执行的sql语句
private Object resultType;//返回类型
private String parameterType;//参数类型
public String getSqlType() {
return sqlType;
}
public void setSqlType(String sqlType) {
this.sqlType = sqlType;
}
public String getFuncName() {
return funcName;
}
public void setFuncName(String funcName) {
this.funcName = funcName;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Object getResultType() {
return resultType;
}
public void setResultType(Object resultType) {
this.resultType = resultType;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
@Override
public String toString() {
return "Function{" +
"sqlType='" + sqlType + '\'' +
", funcName='" + funcName + '\'' +
", sql='" + sql + '\'' +
", resultType=" + resultType +
", parameterType='" + parameterType + '\'' +
'}';
}
}
2 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\config\MapperBean.java, 将Mapper 的信息,进行封装
//MapperBean: 将Mapper信息,进行封装
public class MapperBean {
private String interfaceName;//接口名
//接口下的所有方法-集合
private List<Function> functions;
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
public List<Function> getFunctions() {
return functions;
}
public void setFunctions(List<Function> functions) {
this.functions = functions;
}
}
实现任务阶段6
任务:在NlcConfiguration, 读取XxxMapper.xml,能够创建MappperBean对象。
运行结果示意
mapperBean--MapperBean{interfaceName='com.nlc.mapper.MonsterMapper',
list=[Function{sqlType='select',
funcName='getMonsterById',
sql='select * from monster where id=?',
resultType=com.nlc.mapper.MonsterMapper,
parameterType='null'}]}
代码实现
- 修改nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcConfiguration.java, 增加方法
public MapperBean readMapper(String path) {
MapperBean mapperBean = new MapperBean();
try {
//获取到xml文件对应的InputStream
InputStream stream = loader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
//获取到xml文件对应的document-dom4j
Document document = reader.read(stream);
//得到xml文件的根元素/根节点
/*
<mapper namespace="com.nlc.mapper.MonsterMapper">
<!--实现配置接口方法getMonsterById-->
<select id="getMonsterById" resultType="com.nlc.entity.Monster">
select * from monster where id = ?
</select>
</mapper>
*/
Element root = document.getRootElement();
//获取到namespace
String namespace = root.attributeValue("namespace").trim();
//设置mapperBean的属性interfaceName
mapperBean.setInterfaceName(namespace);
//得到root的迭代器-可以遍历它的子节点/子元素-生成Function
Iterator rootIterator = root.elementIterator();
//保存接口下所有的方法信息
List<Function> list = new ArrayList<>();
//遍历它的子节点/子元素-生成Function
while (rootIterator.hasNext()) {
//取出一个子元素- dom4j Element
/**
* <select id="getMonsterById" resultType="com.nlc.entity.Monster">
* select * from monster where id = ?
* </select>
*/
Element e = (Element) rootIterator.next();
Function function = new Function();
String sqlType = e.getName().trim();
String funcName = e.attributeValue("id").trim();
//resultType是返回类型的全路径-即全类名
String resultType = e.attributeValue("resultType").trim();
String sql = e.getText().trim();
//开始封装
function.setSql(sql);
function.setFuncName(funcName);
function.setSqlType(sqlType);
//这里 function-private Object resultType; 是resultType实例
//使用反射生成一个对象, setResultType
Object newInstance = Class.forName(resultType).newInstance();
function.setResultType(newInstance);
//将封装好的function对象加入到 list
list.add(function);
}
//while循环结束后, 将function的list设置
mapperBean.setFunctions(list);
} catch (Exception e) {
e.printStackTrace();
}
return mapperBean;
}
完成测试
1 、修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java, 增加测试方法
@Test
public void readMapper() {
NlcConfiguration nlcConfiguration = new NlcConfiguration();
MapperBean mapperBean =
nlcConfiguration.readMapper("MonsterMapper.xml");
System.out.println("mapperBean---" + mapperBean);
}
2、测试效果
数据表没有填充数据,不影响测试
实现任务阶段7- 实现动态代理Mapper 的方法
分析示意图
代码实现
1 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcMapperProxy.java
//NlcMapperProxy: 动态代理生成Mapper对象,调用NlcExecutor方法
public class NlcMapperProxy implements InvocationHandler {
//属性
private NlcSqlSession nlcSqlSession;
private String mapperFile;
private NlcConfiguration nlcConfiguration;
//构造器
public NlcMapperProxy(NlcConfiguration nlcConfiguration, NlcSqlSession nlcSqlSession, Class clazz) {
this.nlcConfiguration = nlcConfiguration;
this.nlcSqlSession = nlcSqlSession;
this.mapperFile = clazz.getSimpleName() + ".xml";
}
//当执行Mapper接口的代理对象方法时,会执行到invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = nlcConfiguration.readMapper(this.mapperFile);
//判断是否是xml文件对应的接口
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())) {
return null;
}
//取出mapperBean的functions
List<Function> functions = mapperBean.getFunctions();
//判断当前mapperBean解析对应MappperXML后 , 有方法
if (null != functions && 0 != functions.size()) {
for (Function function : functions) {
//当前要执行的方法和function.getFuncName()一样
//说明我们可以从当前遍历的function对象中,取出相应的信息sql, 并执行方法
if(method.getName().equals(function.getFuncName())) {
//如果我们当前的function 要执行的sqlType是select
//我们就去执行selectOne
/**
* 1. 如果要执行的方法是select , 就对应执行selectOne
* 2. 因为在NlcSqlSession就写了一个 selectOne
* 3. 实际上NlcSqlSession 应该对应不同的方法(多个方法)
* , 根据不同的匹配情况调用不同方法, 并且还需要进行参数解析处理, 还有比较复杂的字符串处理,拼接sql ,处理返回类型等等工作
* 4. 因为主要是mybatis 生成mapper动态代理对象, 调用方法的机制,所以这里做了简化
*/
if("select".equalsIgnoreCase(function.getSqlType())) {
return nlcSqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
}
}
}
}
return null;
}
}
2 、修改nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSqlsession.java
/**
*解读
* 1. 当调用getMapper(MonsterMapper.class)
* 2. 这里的T 就是MonsterMapper
* 3. 所有接收MonsterMapper monsterMapper = getMapper(MonsterMapper.class)
* 4. 在程序中,就可以通过monsterMapper.xxx() 方法
* 5. 因为monsterMapper 的运行类型是代理类型$Proxy
* 6. 所以当你调用monsterMapper.xxx(实参) 方法时,是一种动态代理执行,
*也就是会调用到NlcMapperProxy 这个代理类的 invoke(Object proxy, Method method, Object[] args)
* 7. 这时Method 就是xxx(实参), proxy 就是对象monsterMapper, args 就是你的实参
*/
public <T> T getMapper(Class<T> clazz) {
//返回动态代理对象
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
new NlcMapperProxy(nlcConfiguration,this,clazz));
}
3 、创建nlc-mybatis\src\main\java\com\nlc\nlcmybatis\sqlsession\NlcSessionFactory.java
//NlcSessionFactory 会话工厂-返回会话NlcSqlSession
public class NlcSessionFactory {
public static NlcSqlSession openSession() {
return new NlcSqlSession();
}
}
完成测试
1 、修改nlc-mybatis\src\test\java\com\nlc\test\NlcMybatisTest.java, 增加测试方法
@Test
public void openSession() {
NlcSqlSession nlcSqlSession = NlcSessionFactory.openSession();
MonsterMapper mapper = nlcSqlSession.getMapper(MonsterMapper.class);
Monster monster = mapper.getMonsterById(1);
System.out.println("monster===" + monster);
}
2、测试效果
😄总结
- 很多时候事务的本身并不是可怕的,而是我们自身对于未知事务的恐惧把它扩大了。
- MyBatis也是同样,它只是人类创建出来提高效率的工具,他的能力取决于使用的人。
- 去学习它、了解它,你会发现没有那么多的未知,得到的都是惊喜。
- MyBatis是一款优秀的持久层框架,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,节省了原生90%的代码。
😁热门专栏推荐
MyBatis手写机制篇–自己实现MyBatis 底层机制–抽丝剥茧(上)
文章到这里就结束了,如果有什么疑问的地方请指出,诸大佬们一起来评论区一起讨论😁
希望能和诸大佬们一起努力,今后我们一起观看感谢您的阅读🍻
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞