如何深度理解mybatis?

深度自定义mybatis

回顾mybatis的操作的核心步骤

编写核心类SqlSessionFacotryBuild进行解析配置文件

深度分析解析SqlSessionFacotryBuild干的核心工作

编写核心类SqlSessionFacotry

深度分析解析SqlSessionFacotry干的核心工作

编写核心类SqlSession

深度分析解析SqlSession干的核心工作

总结自定义mybatis用的技术点

一. 回顾mybatis的操作的核心步骤

声明一点我们本篇主要探讨的是mybatis的注解方式的操作, 完全从头开始都是小编从头开搞的, 如果与其他大神的代码思维有出入请多指教

我们首先需要准备mybatis的核心配置文件(当然导入相关的坐标这里不在啰嗦)

<?xml version=”1.0″ encoding=”UTF-8″ ?> <!DOCTYPE configuration PUBLIC “-//mybatis.org//DTD Config 3.0//EN” “http://mybatis.org/dtd/mybatis-3-config.dtd”> <configuration> <environments default=“development”> <environment id=“development”> <transactionManager type=“JDBC”/> <dataSource type=“POOLED”> <!–数据库连接信息–> <property name=“driver” value=“com.mysql.jdbc.Driver”/> <property name=“url” value=“jdbc:mysql:///db6?useSSL=false”/> <property name=“username” value=“root”/> <property name=“password” value=“root”/> </dataSource> </environment> </environments> <mappers> <!– 配置sql语句编写的位置 –> <package name=“cn.itcast.mapper”/> </mappers> </configuration>

准备好结果的实体类以及在mapper接口上编写需要执行的sql语句

public class User { private Integer uid; private String username; private String password; private String nickname; }package cn.itcast.mapper; import cn.itcast.pojo.User; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper { @Select(“select * from users”) List<User> findAll(); }

使用mybatis的api来帮助我们完成sql语句的执行以及结果集的封装

//1.关联主配置文件 InputStream in = Resources.getResourceAsStream(“mybatis-config.xml”); //2.解析配置文件 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = builder.build(in); //3.创建会话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.可以采用接口代理的方式 UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findAll(); System.out.println(all); //5.释放资源 sqlSession.close();

思考: mybatis大致是如何帮我们完成相关操作的 ?

我们通过Resources的getResourceAsStream告诉了mybatis我们编写的核心配置文件的位置, mybatis就可以找到我们数据库的连接信息, 也同时找到我们编写的sql语句的地方, 然后可以将其解析按照某种规则存放起来, 我们通过调用接口代理的方式执行方法时, 可以找到对应方法上的sql语句然后执行将结果封装返回给我们

二. 编写核心类SqlSessionFacotryBuild进行解析配置文件

那么我们废话不多说开始我们自定义mybatis的旅程,

1.首先我们需要用户编写配置文件, 然后通过我们自己的Resources来告诉我们配置文件所在位置package com.itheima.ibatis.configuration; import java.io.InputStream; public class Resources { public static InputStream getResourceAsStream(String path) { return ClassLoader.getSystemClassLoader().getResourceAsStream(path); } } 2. 然后需要定义SqlSessionFacotryBuild来对配置文件进行解析分发package com.itheima.ibatis.configuration; import com.itheima.ibatis.core.session.SqlSessionFactory; import com.itheima.ibatis.core.session.impl.DefaultSqlSessionFactory; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import javax.sql.DataSource; import java.io.File; import java.io.InputStream; import java.lang.reflect.Method; import java.util.List; import java.util.Properties; public class SqlSessionFactoryBuilder { private Configuration configuration = new Configuration(); public SqlSessionFactory build(InputStream in) { SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader.read(in); } catch (DocumentException e) { e.printStackTrace(); } Element rootElement = document.getRootElement(); parseEnvironment(rootElement.element(“environments”)); parseMapper(rootElement.element(“mappers”)); return new DefaultSqlSessionFactory(configuration); } private void parseMapper(Element mapper) { String pack = mapper.element(“package”).attributeValue(“name”); String directory = pack.replace(“.”, “/”); String path = ClassLoader.getSystemClassLoader().getResource(“”).getPath(); File mapperDir = new File(path, directory); if (!mapperDir.exists()) { throw new RuntimeException(“找不到mapper映射”); } findMapper(mapperDir, pack); // System.out.println(configuration.getSql()); } private void findMapper(File mapperDir, String base) { File[] files = mapperDir.listFiles(); if (files != null) { for (File file : files) { if (file.isFile()) { if (file.getName().endsWith(“.class”)) { String name = file.getName(); name = name.substring(0, name.lastIndexOf(“.”)); String className = base + “.” + name; initMapper(className); } } else { findMapper(file, base + “.” + file.getName()); } } } } private void initMapper(String className) { try { Class<?> clazz = Class.forName(className); Method[] methods = clazz.getMethods(); for (Method method : methods) { if(method.getAnnotations().length>0){ Mapper mapper = ParseMapper.parse(method); this.configuration.getMappers().put(className + “.” + method.getName(), mapper); } } } catch (Exception e) { e.printStackTrace(); } } private void parseEnvironment(Element environments) { String defEnv = environments.attributeValue(“default”); Node node = environments.selectSingleNode(“//environment[@id=” + defEnv + “]”); List<Element> list = node.selectNodes(“//property”); Properties properties = new Properties(); for (Element element : list) { String name = element.attributeValue(“name”); String value = element.attributeValue(“value”); properties.put(name, value); } DataSource dataSource = new DefaultDataSource().getDataSource(properties); configuration.setDataSource(dataSource); } } 三. 深度分析解析SqlSessionFacotryBuild干的核心工作1. build(InputStream in) 方法做的工作

①借助Dom4j的来解析了xml文件, 将environments解析工作分发给了parseEnvironment(Element environments)

②将mappers的解析工作分发给了parseMapper(Element mapper)

2. parseEnvironment(Element environments)方法做的工作

①主要解析了连接数据库的参数们, 并且创建了数据库连接池

自定义连接池非本章节的重点,所以这里内部本质采用的Druid连接池来做了简化

package com.itheima.ibatis.configuration; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.util.Properties; public class DefaultDataSource { public DataSource getDataSource(Properties properties) { try { return DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } return null; } }

②将解析好的连接池放入configuration对象中,mappers成员变量先别纠结下一章节会讲解

package com.itheima.ibatis.configuration; import lombok.Data; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Data public class Configuration { private Map<String, Mapper> mappers = new HashMap<>(); private DataSource dataSource; }

详细图解如下图

如何深度理解mybatis?
3.parseMapper(Element mapper) 方法做的工作

①解析出用户配置的package找到sql语句所在接口的文件夹, 交给initMapper来处理

如何深度理解mybatis?

②递归找到这个包下所有的.class文件,并且获取到接口的全类名, 然后交给initMapper来处理

如何深度理解mybatis?

③initMapper通过反射获取类中的每一个方法,将方法交给一个专门解析方法上的注解的工具类ParseMapper的parse方法处理,处理完后将其放到configuration中的mappers的集合中

如何深度理解mybatis?

④ParseMapper的parse方法做的工作, 这是解析配置的核心地方

package com.itheima.ibatis.configuration; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class ParseMapper { public static Mapper parse(Method method) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Annotation[] annotations = method.getAnnotations(); Object value = annotations[0].getClass().getMethod(“value”).invoke(annotations[0]); Mapper mapper = new Mapper(); Class<?> resultType = method.getReturnType(); String val = (String) value; Pattern pattern = Pattern.compile(“\#\{\s*\w+\s*\}”); Matcher matcher = pattern.matcher(val); List<String> paramNames = new ArrayList<>(); while (matcher.find()) { String group = matcher.group(); String fieldName = group.substring(2, group.length() – 1).trim(); paramNames.add(fieldName); } String sql = val.replaceAll(“\#\{\s*\w+\s*\}”, “?”); mapper.setSql(sql); mapper.setParameterNames(paramNames); mapper.setSql(sql); if (resultType == List.class) { mapper.setSelectList(true); Type genericReturnType = method.getGenericReturnType(); ParameterizedType parameterizedType = (ParameterizedType) genericReturnType; Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0]; mapper.setResultType(actualTypeArgument.getTypeName()); mapper.setType(“SELECT”); } else if (resultType == Integer.class || resultType == int.class) { mapper.setType(“UPDATE”); } else { mapper.setType(“SELECT”); mapper.setResultType(resultType.getName()); } return mapper; } }

首先拿到方法上的注解,得到用户填入的sql语句

如何深度理解mybatis?

然后处理sql语句#{参数}的这些数据, 然后将参数的顺序保存起来, 用来后期设置参数的数据做准备, 一个

方法对应一个Mapper对象

如何深度理解mybatis?

然后再根据结果类型, 判断是什么类型相关的操作,方便后期执行对应的sql语句

如何深度理解mybatis?
四. 编写核心类SqlSessionFacotry1.回顾那个地方创建的SqlSessionFacotry对象

经过SqlSessionFacotryBuilder的努力, 我们成功的将配置文件中核心的信息解析出来并放入了configuration对象中了, 然后我们此时将解析好的configuration传入到SqlSessionFacotry中

如何深度理解mybatis?

SqlSessionFactory的实现类如下:

public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; private TransactionManagement defaultTransactionManagement; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration =configuration; defaultTransactionManagement = new DefaultTransactionManagement(configuration.getDataSource()); } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration,defaultTransactionManagement,false); } }2.添加事务管理器

事务管理是一个小的功能, 里面希望使用ThreadLocal集合来保证一个用户拿到的链接是同一个

如何深度理解mybatis?

事务管理的代码如下:

public class DefaultTransactionManagement implements TransactionManagement { private ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); private DataSource dataSource; public DefaultTransactionManagement(DataSource dataSource) { this.dataSource = dataSource; } public Connection getConnection() { Connection connection = threadLocal.get(); if (connection == null) { try { connection = dataSource.getConnection(); } catch (SQLException e) { e.printStackTrace(); } threadLocal.set(connection); } return connection; } @Override public void commit() { Connection connection = threadLocal.get(); if (connection != null ) { try { connection.commit(); } catch (Exception e) { e.printStackTrace(); } } } @Override public void rollback() { Connection connection = threadLocal.get(); if (connection != null) { try { connection.rollback(); } catch (SQLException e) { e.printStackTrace(); } } } public void close() { Connection connection = threadLocal.get(); if (connection != null) { try { connection.close(); threadLocal.remove(); } catch (SQLException e) { e.printStackTrace(); } } } @Override public void begin() { Connection connection = threadLocal.get(); if (connection != null) { try { connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } } } }五. 深度分析解析SqlSessionFacotry干的核心工作1. SqlSession openSession() 方法做的工作

可以看的出来我们在这个方法创建了DefaultSqlSession对象,并传入封装好的configuration,默认的事务管理器

默认通过openSession事务是开启的等等相关的参数

如何深度理解mybatis?
六.编写核心类SqlSession

其实有SqlSession的接口,我们使用的实现类是DefaultSession, 这里记录了解析的配置对象configuration

默认事务管理器对象transactionManagement, 默认事务开启的状态tx标记

package com.itheima.ibatis.core.session.impl; import com.itheima.ibatis.configuration.Configuration; import com.itheima.ibatis.configuration.Mapper; import com.itheima.ibatis.core.BaseExecutor; import com.itheima.ibatis.core.annotation.Param; import com.itheima.ibatis.core.session.SqlSession; import com.itheima.ibatis.core.transaction.TransactionManagement; import java.lang.reflect.*; import java.util.HashMap; import java.util.List; import java.util.Map; public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final boolean tx; private TransactionManagement transactionManagement; public DefaultSqlSession(Configuration configuration, TransactionManagement transactionManagement, boolean tx) { this.configuration = configuration; this.transactionManagement = transactionManagement; this.tx = tx; } public void close() { transactionManagement.close(); } @Override public void commit() { transactionManagement.commit(); } @Override public void rollback() { transactionManagement.rollback(); } @Override public <T> List<T> selectList(String sqlId) { return selectList(sqlId, null); } @Override public <T> List<T> selectList(String sqlId, Object param) { List<Object> list = new BaseExecutor(transactionManagement, tx).queryList(getMapper(sqlId), param); return (List<T>) list; } @Override public <T> T selectOne(String sqlId) { return selectOne(sqlId, null); } @Override public <T> T selectOne(String sqlId, Object param) { return new BaseExecutor(transactionManagement, tx).query(getMapper(sqlId), param); } @Override public int delete(String sqlId) { return update0(sqlId, null); } @Override public int delete(String sqlId, Object param) { return update0(sqlId, param); } @Override public int update(String sqlId) { return update0(sqlId, null); } @Override public int update(String sqlId, Object param) { return update0(sqlId, param); } @Override public int insert(String sqlId) { return update0(sqlId, null); } @Override public int insert(String sqlId, Object param) { return update0(sqlId, param); } @Override public <T> T getMapper(Class<T> clazz) { Object o = Proxy.newProxyInstance( clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String sqlId = clazz.getName() + “.” + method.getName(); Mapper mapper = configuration.getMappers().get(sqlId); String type = mapper.getType(); Object findParam = null; if (args != null) { if (args.length == 1) { Object param = args[0]; boolean isArray = param.getClass().isArray(); if (!isArray) { findParam = param; } } else { Map<String, Object> map = new HashMap<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { Param param = parameters[i].getAnnotation(Param.class); String key = “arg”+i; if(param !=null){ key = param.value(); } map.put(key, args[i]); } findParam = map; } } if (type.equals(“SELECT”)) { boolean selectList = mapper.isSelectList(); if (selectList) return selectList(sqlId, findParam); else return selectOne(sqlId, findParam); } else { return update0(sqlId, findParam); } } }); return (T) o; } private int update0(String sqlId, Object param) { return new BaseExecutor(transactionManagement, tx).update(getMapper(sqlId), param); } public Mapper getMapper(String sqlId) { Mapper mapper = configuration.getMappers().get(sqlId); if (mapper == null) { throw new RuntimeException(“没有找到sql映射,请检查”); } return mapper; } }七.深度分析解析SqlSession干的核心工作1.selectOne & selectList做的工作

主要是分发了下功能, 执行sql语句避免不了有参数和无参数的, 都让调用有参数的方便管理

如何深度理解mybatis?

在执行前, 考虑还有一种情况, 用户不是通过接口代理的方式来执行以上方法, 这样手动输入sqlId容易造成错误

这里做一个健壮性判断

如何深度理解mybatis?

BaseExecutor中的query以及queryList做的核心工作

首先这两个方法的特点都是查询, 其步骤基本类似, 所以这里可以合并一起转调query0功能

如何深度理解mybatis?

这里需要对参数进行设定, 还根据最后isOne的参数决定返回值是否是单个

如何深度理解mybatis?

参数设置这里比较复杂我们通过图解的方式来解释, (注: 参数是List集合类型的和数组类型的没有做!!!)

如何深度理解mybatis?

对结果的封装主要用到内省技术和数据库元数据等等知识点

如何深度理解mybatis?
2.update&delete&insert做的工作
如何深度理解mybatis?

BaseExecutor中的update做的核心工作

还是和query&queryList一样需要设置参数, 不管是增删改其本质其结果都是一致

如何深度理解mybatis?
3.getMapper代理模式开发的原理

主要使用的动态代理的技术创建接口的实现类, 内部主要整合了sqlId和参数, 省去用户自己拼sqlId拼错的风险

也同时解决用户手动合参数的麻烦, 但是最终工作的还是selectOne,selectList以及update0这些方法

如何深度理解mybatis?
如何深度理解mybatis?
如何深度理解mybatis?
总结自定义mybatis用的技术点

一款框架的诞生肯定不是一蹴而就的, 随着时间慢慢推进逐步更新出来, 所以一款好的框架肯定要经过

很多考验才能够稳定靠谱, 但是纵观整篇用的技术点, 不难发现框架也是由基础代码编写而来,解决大量重复

的工作, 提供扩展性等等机制,比如本篇用核心的技术点有

① 反射

② 内省

③ 解析xml

④ 动态代理

⑤ 工厂设计模式

等等, 感谢大家耐心阅览, 附件有本篇的原码, 如果有更好的建议和想法欢迎和小编一起探讨交流

免责声明:文章内容来自互联网,本站仅作为分享,不对其真实性负责,如有侵权等情况,请与本站联系删除。
转载请注明出处:如何深度理解mybatis? https://www.dachanpin.com/a/cyfx/11573.html

(0)
上一篇 2023-05-12 03:25:00
下一篇 2023-05-12 03:26:11

相关推荐

  • 成功创业经验分享,硅藻泥创业必看

    二、选择顺应趋势,有发展潜力的品牌 天然居作为一个有持续发展能力的品牌绝不会跟在别人背后亦步亦趋。天然居走差异化道路与别的品牌形成市场区隔,让竞争对手无从跟随,最终拥有更强的生命力,更高的市场价值! 硅藻泥行业虽然发展了将近十年,但行业为消费者熟知的知名品牌才能称之为渠道品牌。天然居硅藻泥被评为硅藻泥行业十大品牌,品牌实力强势。为加盟商的支持力度会更大,会更…

    创业分享 2023-06-16
    7700
  • 我的折腾史 从机关单位到互联网团队创业经历

      开抢了!双11创业者优选服务!   这就是我从机关单位到团队创业的经历,为了美好的明天,时刻努力着。即使隔行如隔山又怎样,还不是一样要脚踏实地的踏过去。创业不可怕,可怕的是还没有踏上创业之路就退缩了。因为我们还年轻,可以为自己的未来奋力拼搏。   从规划院离职后,没有再去找工作。这时我另外一个长辈,知道我想找一个施工方面的工作,于是就给联系了他的一个朋友…

    创业分享 2023-05-21
    4600
  • 创业社区如何助力创业者?

    创业故事: 家住市区胜利街的王黎和在社区里是有名的发明家,2012年6月从浙江省机电职业技术学院毕业。11月20日,他发明的“迷宫捕蝇笼”正式获得国家实用新型专利,至此,连同“悬挂门捕蟑盒”“单向旋转式连续捕鼠器”,他一共获得3项国家实用新型专利,而更高级别的发明专利已同步通过国家知识产权局的初审和公布。 据说目前,“公有制咖啡”还没有赢利,每个月仅够保本。…

    创业分享 2023-05-25
    7600
  • 自主创业还是搞加盟?选择清鼻堂很重要!

    自主创业还是搞加盟?选择清鼻堂很重要! 分享到 关注中金在线: 扫描二维码 关注√ 中金在线微信 在线咨询: 扫描或点击关注中金在线客服   有一个问题萦绕在众多有志之士心头,到底做什么最赚钱?尤其是现在众多品牌纷纭而立,让各种创业者分辨不清。自主创业苦于没有好项目,尤其对于经验要求极高,容错率较低。加盟店虽说有完善的运营体系,但是有很多品牌的产品却经不住考…

    2023-05-13
    8700
  • 设基金推动青年创新创业

      陈丹丹说,每代人有每代人的发展机遇,每代人有每代人需要承担的歷史责任。“包括我的父辈在内的第一代港商,用大半生的心力在取得自身事业成功的同时,也为后辈奠定非常优越的发展基础。作为他们的继承者,第二代的港商大部分都有海外求学的经歷,视野也更为开阔。其中,有不少人把海外所学,运用到父辈的製造业之上,还有一部分根据自己的兴趣和当前市场的需要,调整了努力的方向。…

    创业分享 2023-05-25
    5300

发表回复

登录后才能评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信