手写mybatis-记第一次捋顺了mybatis流程 深碍√TFBOYSˉ_ 2022-12-20 02:50 60阅读 0赞 # 前言: # 其实也没啥可说的,就是我自己学习和在工作中使用一段时间后,想了解一下mybatis的源码,自己看mybatis的设计图和查看源码时,一直抓不住整体的思路。就想着能有人能给个引导,就好。其实学习别人的思路也就可以学习相应解题的方法。 第一次接触拉钩教育是因为我看到了一个操作系统的课程,觉得将的很好。老师的思路也清晰并且在讲课之前,给出老师自己学习知识的思路。这个对我帮助很大。然后看到的Java的课程,里面正好有mybatis的源码分析和手写mybatis的课程。但是也并没有立即报名。真正决定学习,是从启源老师讲的【高并发MySql优化实战】,详解讲解了mysql分库和分表的思路、记录和代码实战。正好我工作中遇到了同样的问题,根据课程的知识正好解决了我的实际问题。其实我还有很多想深入了解的地方,比如spring 的源码、spring boot 的源码、分布式和微服务的原理、分布式和微服务的实战、发布等等。看了一下,课程目录还都有。下面开始学习啦!!! # 1、原始JDBC存在的问题 # 使用子默老师的讲课原图啦: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h6ajgwOTI3_size_16_color_FFFFFF_t_70][] jdbc的问题是所有学习java开发都了解的。 问题解决的思路 1. 使用数据库连接池初始化连接资料 2. 将sql语句抽取到xml配置文件中 3. 将数据库配置信息抽取到xml配置文件中 4. 使用反射、内省等底层技术,自动将实体属性与表字段进行自动映射 # 2、手写Mybatis的思路 # ## 使用端 ## 引入自定义持久层框架的jar包 提供两部分配置信息:数据库连接信息、sql配置信息:sql语句、输入参数类型、输出参数类型 使用配置文件来提供两部分配置信息: 1) SqlMapConfig.xml : 存放数据库配置信息,存放mapper.xml全路径 2) Mapper.xml : 存放sql配置信息 ## 自定义框架本身 ## 本质就是对JDBC代码进行了封装 1. 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中,创建Resources类,提供方法:InputStream getResourceStream(String path) 2. 创建两个JaveBean:容器对象,存储的就是配置文件解析出来的内容 , 第一个:Configuration: 核心配置类存放SqlMapConfig.xml解析出来的内容 第二个:MappedStatement : 映射配置类,存放mapper.xml解析出来的内容 3. 创建SqlSessionFactoryBuider类,方法builder(),第一步使用dom4j解析xml文件 第二步:创建sqlSessionFactory,生产sqlSession ,使用工厂模式生成会话工厂 4. 创建SqlSessionFactory接口和DefaultSqlSessionFactory实现类,提供方法:openSession(); 生产sqlSession 5. 创建SqlSession接口和DefaultSqlSession实现类:定义数据库的CRUD方法,selectList()\\selectOne()\\update()\\insert()\\delete() 6. 创建Executor接口和SimplerExecutor实现类,提供方法:query(Configuration configuration,MappedStatement mappedStatement,Object... params) : 执行的就是JDBC代码 # 3、代码实现 # **3.1 创建maven工程,导入需要的依赖坐** <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.45</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> </dependencies> **3.2 创建Resources类** public class Resources { public static InputStream getResourceAsStream(String path){ InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path); return resourceAsStream; } } **3.3 创建两个容器类** 3.3.1 Configuration public class Configuration { //数据源 private DataSource dataSource; //map集合: key:statementId value:MapperStatement private Map<String,MapperStatement> mappedStatementMap = new HashMap<String,MapperStatement>( ); --省略get/set } 3.3.4 MappedStatement public class MapperStatement { private String Id; private String sql; //输入参数 private Class<?> parameterType; //输出参数 private Class<?> resultType; --省略 get/set方法 } **3.4 解析配置文件** **3.4.1 创建SqlSessionFactoryBuider类** public class SqlSessionFactoryBuilder { /** * 创建sqlSessionFactory * @return */ public SqlSessionFactory builder(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException { //1. 解析配置文件,封装Configuration XMLConfigBuilder xmlConfiguratorBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfiguratorBuilder.parseConfiguration( inputStream ); //2. 创建 sqlSessionFactory SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory( configuration ); return sqlSessionFactory; } } 3.4.2 XmlConfigBuilder 解析核心配置文件sqlMapConfig public class XmlConfigBuilder { private Configuration configuration=null; public XmlConfigBuilder(){ configuration = new Configuration(); } /** * dom4j解析核心配置文件 * @param inputStream * @return */ public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException { //读取数据库的配置信息 Document document = new SAXReader().read( inputStream ); Element rootElement = document.getRootElement(); //读取属性文件 List<Element> propertyElements = rootElement.selectNodes( "//property" ); Properties properties = new Properties( ); for (Element propertyElement : propertyElements) { String name = propertyElement.attributeValue( "name" ); String value = propertyElement.attributeValue( "value" ); properties.put( name,value ); } //创建连接池 //druid连接池 DruidDataSource druidDataSource = new DruidDataSource( ); druidDataSource.setDriverClassName( properties.getProperty( "driverClass" ) ); druidDataSource.setUrl( properties.getProperty( "url" ) ); druidDataSource.setUsername( properties.getProperty( "username" ) ); druidDataSource.setPassword( properties.getProperty( "password" ) ); //填充 configuration configuration.setDataSource( druidDataSource ); //读取mapper List<Element> mapperElements = rootElement.selectNodes( "//mapper" ); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder( configuration ); for (Element mapperElement : mapperElements) { String path = mapperElement.attributeValue( "resource" ); InputStream resourcesAsStream = Resources.getResourcesAsStream( path ); xmlMapperBuilder.parseMapperXml( resourcesAsStream ); } return configuration; } } 3.4.3 XMLMapperBuilder,解析mapper配置文件 public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration){ this.configuration = configuration; } //解析mapper.xml public void parseMapperXml(InputStream inputStream) throws DocumentException { Document document = new SAXReader().read( inputStream ); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue( "namespace" ); //读取select节点 this.readNodes( rootElement, namespace,"select" ); //读取insert 节点 this.readNodes( rootElement, namespace,"insert" ); //读取update 节点 this.readNodes( rootElement, namespace,"update" ); //读取delete节点 this.readNodes( rootElement, namespace,"delete" ); } /** * 读取指定节点 * @param rootElement * @param namespace * @param nodeName */ private void readNodes(Element rootElement, String namespace,String nodeName) { List<Element> select = rootElement.selectNodes( nodeName ); for (Element element : select) { String id = element.attributeValue("id");//获取id String parameterType = element.attributeValue("parameterType");//输入参数 String resultType = element.attributeValue( "resultType" );//输出参数 //statementId String key = namespace+"."+id; //sql语句 String sql = element.getTextTrim(); //封装 mappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId( id ); mappedStatement.setParameterType( parameterType ); mappedStatement.setResultType( resultType ); mappedStatement.setSql(sql ); configuration.getMappedStatementMap().put( key,mappedStatement ); } } } **3.5 创建SqlSessionFactory接口和DefaultSqlSessionFactory实现类** 3.5.1 SqlSessionFactory public interface SqlSessionFactory { public SqlSession openSession(); } 3.5.2 DefaultSqlSessionFactory public class DefaultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); } } **3.6 创建SqlSession接口和DefaultSqlSession实现类** 3.6.1 SqlSession public interface SqlSession { public <E> List<E> selectList(String statementId, Object... param) throws Exception; public <T> T selectOne(String statementId,Object... params) throws Exception; public void close() throws SQLException; //代理方式 public <T> T getMapper(Class<?> mapperClass); } 3.6.2 DefaultSqlSession package frame.core; import frame.pojo.Configuration; import frame.pojo.MapperStatement; import java.lang.reflect.*; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * FileName: DefaultSqlSession * Author: * Date: 2020/11/6 12:01 * Description: */ public class DefaultSqlSession implements SqlSession { private Configuration configuration; //处理器对象 private Executor simpleExecutor = new SimpleExecutor(); public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; //处理器对象 } @Override public <E> List<E> selectList(String statementId, Object... param) throws Exception { MapperStatement mapperStatement = configuration.getMappedStatementMap().get( statementId ); List<E> query = simpleExecutor.query( configuration, mapperStatement, param ); return query; } //selectOne 中调用 selectList @Override public <T> T selectOne(String statementId, Object... params) throws Exception { List<Object> objects = this.selectList( statementId, params ); if (objects.size() == 1) { return (T) objects.get( 0 ); } else { throw new RuntimeException( "返回结果过多" ); } } @Override public void close() throws SQLException { simpleExecutor.close(); } @Override public <T> T getMapper(Class<?> mapperClass) { T o = (T) Proxy.newProxyInstance( mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // selectOne String methodName = method.getName(); // className:namespace String className = method.getDeclaringClass().getName(); //statementId String key = className + "." + methodName; MapperStatement mappedStatement = configuration.getMappedStatementMap().get( key ); Type genericReturnType = method.getGenericReturnType(); //判断是否实现泛型类型参数化 if (genericReturnType instanceof ParameterizedType) { return selectList( key, args ); } return selectOne( key, args ); } } ); return o; } } **3.7 创建Executor接口和SimplerExecutor实现类** 3.7.1 Executor public interface Executor { <E> List<E> query(Configuration configuration, MapperStatement mappedStatement, Object[] param) throws Exception; void close() throws SQLException; } 3.7.2 SimpleExecutor public class SimpleExecutor implements Executor { private Connection connection = null; @Override public <E> List<E> query(Configuration configuration, MapperStatement mappedStatement, Object[] param) throws Exception { //1、注册驱动,获取连接 connection = configuration.getDataSource().getConnection(); //2、获取sql语句:select * from user where id = #{id} and username = #{username} //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储 String sql = mappedStatement.getSql(); //转换字符把 #{} 和 ${} 转换为 ? ,并且获取 #{} 和 ${}中间的字符串 BoundSql boundsql = getBoundSql(sql); // 3.获取预处理对象:preparedStatement PreparedStatement preparedStatement = connection.prepareStatement( boundsql.getSqlText() ); Class<?> parameterTypeClass = mappedStatement.getParameterType(); // 4. 设置参数 List<ParameterMapping> parameterMappingList = boundsql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get( i ); String content = parameterMapping.getContent();//#{} 中的参数 //反射 Field declaredField = parameterTypeClass.getDeclaredField( content ); //暴力访问 declaredField.setAccessible( true ); //获取指定字段在实体类的值 Object o = declaredField.get( param[0] ); preparedStatement.setObject( i+1,o ); } //5、执行sql,返回查询结果 ResultSet resultSet = preparedStatement.executeQuery(); //获取返回值的类 Class<?> resultTypeClass = mappedStatement.getResultType(); ArrayList<Object> objects = new ArrayList<>(); // 6. 封装返回结果集 while(resultSet.next()){ //根据类的class创建具体类 Object o = resultTypeClass.newInstance(); //元数据 ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 1; i <= metaData.getColumnCount(); i++) { // 字段名 String columnName = metaData.getColumnName( i ); // 字段的值 Object value = resultSet.getObject( columnName ); //使用反射或者内省,根据数据库表和实体的对应关系,完成封装,PropertyDescriptor 是内省库中的类 PropertyDescriptor propertyDescriptor = new PropertyDescriptor( columnName, resultTypeClass ); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke( o,value ); } objects.add( o ); } return (List<E>) objects; } /** * 完成对#{} 的解析工作,1 将#{} 使用? 代替, 2。解析出#{} 里面的值进行存储 * @param sql * @return */ private BoundSql getBoundSql(String sql){ //标记处理类:配置标记解析器来完成对占位符的解析处理工作 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser( "#{", "}", parameterMappingTokenHandler ); //解析出来的sql String parse = genericTokenParser.parse( sql ); //#{}里面解析出来的参数名称 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); return new BoundSql(parse,parameterMappings); } /** * 连接归还连接池 * @throws SQLException */ @Override public void close() throws SQLException { connection.close(); } } **备注:SimpleExecutor中用到的工具类** 1、TokenHandler public interface TokenHandler { String handleToken(String content); } 2、ParameterMappingTokenHandler public class ParameterMappingTokenHandler implements TokenHandler { private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>(); // context是参数名称 #{id} #{username} public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { ParameterMapping parameterMapping = new ParameterMapping(content); return parameterMapping; } public List<ParameterMapping> getParameterMappings() { return parameterMappings; } public void setParameterMappings(List<ParameterMapping> parameterMappings) { this.parameterMappings = parameterMappings; } } 3、ParameterMapping public class ParameterMapping { private String content; public ParameterMapping(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } 4、GenericTokenParser ** * @author Clinton Begin */ public class GenericTokenParser { private final String openToken; //开始标记 private final String closeToken; //结束标记 private final TokenHandler handler; //标记处理器 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } /** * 解析${}和#{} * @param text * @return * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。 * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现 */ public String parse(String text) { // 验证参数问题,如果是null,就返回空字符串。 if (text == null || text.isEmpty()) { return ""; } // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。 int start = text.indexOf(openToken, 0); if (start == -1) { return text; } // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder, // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码 char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; while (start > -1) { // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理 if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //重置expression变量,避免空指针或者老数据干扰。 if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + openToken.length(); int end = text.indexOf(closeToken, offset); while (end > -1) {存在结束标记时 if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时 // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else {//不存在转义字符,即需要作为参数进行处理 expression.append(src, offset, end - offset); offset = end + closeToken.length(); break; } } if (end == -1) { // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { //首先根据参数的key(即expression)进行参数处理,返回?作为占位符 builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } } 5、BoundSql public class BoundSql { //解析过后的sql语句 private String sqlText; //解析出来的参数 private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>(); public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) { this.sqlText = sqlText; this.parameterMappingList = parameterMappingList; } public String getSqlText() { return sqlText; } public void setSqlText(String sqlText) { this.sqlText = sqlText; } public List<ParameterMapping> getParameterMappingList() { return parameterMappingList; } public void setParameterMappingList(List<ParameterMapping> parameterMappingList) { this.parameterMappingList = parameterMappingList; } } # 4、测试 # **4.1 在maven的resources目录下创建SqlMapConfig.xml 和 UserMapper.xml** 4.1.1 SqlMapConfig.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <!-- 数据库连接信息--> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <!-- 引入sql配置信息--> <mapper resource="UserMapper.xml"/> </mappers> </configuration> 4.1.2 UserMapper.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="frame.dao.UserDao"> <!--sql的唯一标识:namespace.id来组成 : statementId--> <select id="findAll" resultType="frame.pojo.User" > select * from user </select> <!-- User user = new User() user.setId(1); user.setUsername("zhangsan") --> <select id="findByCondition" resultType="frame.pojo.User" parameterType="frame.pojo.User"> select * from user where id = #{id} and name = #{name} </select> </mapper> **4.2 创建User.java** public class User { private Integer id; private String name; private String email; -- 省略get/set方法 } **4.3 创建测试类:testMybatis** public class TestMybatis { //xml方式测试 @Test public void test1() throws Exception { InputStream resourceAsSteam = Resources.getResourceAsStream( "SqlMapConfig.xml" ); SqlSessionFactory builder = new SqlSessionFactoryBuilder().builder( resourceAsSteam ); SqlSession sqlSession = builder.openSession(); List<Object> objects = sqlSession.selectList( "frame.dao.UserDao.findAll" ); System.out.println( objects ); User user = new User(); user.setId( 1 ); user.setName( "jack" ); User user1 = sqlSession.selectOne( "frame.dao.UserDao.findByCondition", user ); System.out.println(user1); } } 结果如下: ![watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h6ajgwOTI3_size_16_color_FFFFFF_t_70 1][] [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h6ajgwOTI3_size_16_color_FFFFFF_t_70]: /images/20221120/9128054d120f403dbdd007e26b794fa5.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h6ajgwOTI3_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/2020111010441423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3h6ajgwOTI3,size_16,color_FFFFFF,t_70
还没有评论,来说两句吧...