自己实现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%的代码。
文章到这里就结束了,如果有什么疑问的地方请指出,诸大佬们一起来评论区一起讨论😁
希望能和诸大佬们一起努力,今后我们一起观看感谢您的阅读🍻
如果帮助到您不妨3连支持一下,创造不易您们的支持是我的动力🤞