手写简单的mybatis框架
创始人
2024-03-23 12:45:22
0

目录标题

    • 一、核心
    • 二、代码
    • (一)pom
    • (二)解析xml
    • (三)Configuration
    • (四)SqlSessionFactoryBuilder
    • (五)通过SqlSessionFactory获取SqlSession
    • (六)核心类SqlSession
    • (七)使用
    • 三、参考

一、核心

mybatis的核心是通过解析核心配置xml(获取数据源、存放mapper.xml文件的路径 ),根据mapper.xml路径解析出sql的类型(crud哪一种)、返回值类型(结果集是什么类型的)、参数类型(带不带参数就行sql操作)这些东西存放在一个Configuration 类里面(用一个map保存key为:接口全限定名.方法名。)。最后利用动态代理(jdk)去生成一个代理对象。代理对象根据Configuration类里面的存放的sql信息,利用jdbc操作数据库。

二、代码

(一)pom

junitjunit4.12org.projectlomboklombok1.18.12providedmysqlmysql-connector-java8.0.19c3p0c3p00.9.1.2dom4jdom4j1.6.1jaxenjaxen1.1.6

(二)解析xml

  • 使用dom4j工具解析SqlMapConfig.xml和UserMapper.xml

SqlMapConfig.xml


UserMapper.xml





  1. 需要找到文件位置(路径)。通过类加载器获取resources下的配置文件的输入流
package com.lihua.custommybatis;import java.io.InputStream;/*** @author lihua* @date 2022/12/6 17:28** 利用 ClassLoader加载配置*/
public class Resources {/*** 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。* @param path 文件路径* @return     字节流*/public static InputStream getResourceAsStream(String path) {InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;}}
  1. 解析xml
  • 解析核心配置文件
package com.lihua.custommybatis;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;/*** @author lihua* @date 2022/12/6 16:09* 用于读取mybatis核心xml文件,对mybatis核心配置文件进行读取。*/
public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}/*** 该方法就是使用dom4j对配置文件进行解析,封装成Configuration对象* @param in 字节输入流* @return   Configuration*/public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {Document document = new SAXReader().read(in);// Element rootElement = document.getRootElement();List list = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : list) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name, value);}ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));comboPooledDataSource.setUser(properties.getProperty("username"));comboPooledDataSource.setPassword(properties.getProperty("password"));configuration.setDataSource(comboPooledDataSource);// mapper.xml解析:拿到路径--字节输入流--dom4j进行解析List mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}
}
  • 解析mapper.xml配置文件
package com.lihua.custommybatis;import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;/*** @author lihua* @date 2022/12/6 16:11*/
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream in) throws DocumentException {Document document = new SAXReader().read(in);Element rootElement = document.getRootElement();String namespace = rootElement.attributeValue("namespace");List list = rootElement.selectNodes("//select");for (Element element : list) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sqlText = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultType);mappedStatement.setParameterType(parameterType);mappedStatement.setSql(sqlText);mappedStatement.setSqlType(element.getName());String key = namespace + "." + id;configuration.getMappedStatement().put(key, mappedStatement);}}}

(三)Configuration

解析xml后,将结果放到一个Configuration 类里存放。在前面的解析步骤中,需要解析两个配置文件。

  • 核心配置文件:存放了我们连接数据库的数据源信息。我们用一个javax.sql.DataSource;下的DataSource类存放。

  • mapper.xml:存放了我们编写的sql语句。我们创建一个MappedStatement类存放sql语句的信息,比如:sql类型(insert\select),参数类型,结果集类型,具体的sql。因为一个mapper中有很多sql语句,因此我们需要用一个map存放,key为:接口全限定名.方法名。value为:MappedStatement

MappedStatement

package com.lihua.custommybatis;import lombok.Data;/*** @author lihua* @date 2022/12/6 15:28* 用于存放解析后的sql语句**/
@Data
public class MappedStatement {// id标识private String id;//sql类型,crudprivate String sqlType;// 返回值类型private String resultType;// 参数值类型private String parameterType;// sql语句private String sql;
}

Configuration

package com.lihua.custommybatis;import lombok.Data;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author lihua* @date 2022/12/6 15:24* 自定义config,用于存放读取到的xml文件*/
@Data
public class Configuration {private DataSource dataSource;private Map mappedStatement = new HashMap<>();
}

(四)SqlSessionFactoryBuilder

使用建造者模式创建一个SqlSessionFactory 对象。一般以Builder 结尾的都是建造者模式。建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。(与工厂模式一样,是创建型模式,用于屏蔽复杂对象的构建过程)。

package com.lihua.custommybatis;import org.dom4j.DocumentException;import java.beans.PropertyVetoException;
import java.io.InputStream;/*** @author lihua* @date 2022/12/6 16:07* 建造者模式创建SqlSessionFactory 工厂对象*/
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream in) {// 1.使用dom4j解析配置文件,将解析出来的内容封装到ConfigurationXMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = null;try {configuration = xmlConfigBuilder.parseConfig(in);} catch (DocumentException e) {e.printStackTrace();} catch (PropertyVetoException e) {e.printStackTrace();}// 2.创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);return defaultSqlSessionFactory;}
}

(五)通过SqlSessionFactory获取SqlSession

SqlSessionFactory 是简单工厂模式(工厂模式),也是用于构建一个对象。这里用来构建SqlSession。

SqlSessionFactory

public interface SqlSessionFactory {/*** 通过工厂模式获取session** @return*/DefaultSqlSession openSession();
}

默认的实现类

package com.lihua.custommybatis;/*** @author lihua* @date 2022/12/6 15:48* 默认会话工厂实现*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;/*** 通过构造器注入* @param configuration*/public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic DefaultSqlSession openSession() {return new DefaultSqlSession(configuration);}
}

(六)核心类SqlSession

在SqlSession中就使用了动态代理(jdk动态代理)模式创建一个代理类去操作数据库。

  • 接口
package com.lihua.custommybatis;import java.util.List;/*** @author lihua* @date 2022/12/6 15:56*/
public interface SqlSession {/*** 查询所有* @param statementId sql唯一id* @param params      sql有可能十四模糊查询,传可变参数* @param          泛型* @return            List集合*/ List selectList(String statementId, Object... params) throws Exception;/*** 根据条件查询单个* @param statementId sql唯一id* @param params      sql有可能十四模糊查询,传可变参数* @param          泛型* @return            某一对象*/ T selectOne(String statementId, Object... params) throws Exception;/*** 为Dao层接口生成代理实现类(获取代理对象,执行crud)* @param mapperClass 字节码* @param          泛型* @return         某一对象*/ T getMapper(Class mapperClass) throws Exception;
}
  • 这里没有单独创建一个代理类,直接new了一个匿名类
package com.lihua.custommybatis;import java.lang.reflect.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** @author lihua* @date 2022/12/6 15:49* 会话*/
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Connection connection;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;try {//操作数据库的连接this.connection= configuration.getDataSource().getConnection();} catch (SQLException throwables) {throwables.printStackTrace();}}@Overridepublic  List selectList(String statementId, Object... params) throws Exception {return null;}@Overridepublic  T selectOne(String statementId, Object... params) throws Exception {return null;}@Overridepublic  T getMapper(Class mapperClass) throws Exception {// 使用JDK动态代理来为Dao层接口生成代理对象,并返回。Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** 底层都还是去执行JDBC代码* 根据不同情况来调用findAll或者findByCondition方法* 准备参数:* 1.statementId: sql语句的唯一标识 nnamespace.id = 接口全限定名.方法名*/// 方法名String methodNme = method.getName();String className = method.getDeclaringClass().getName();String   statementId  = className + "." + methodNme;System.out.println(methodNme);MappedStatement mappedStatement = configuration.getMappedStatement().get(statementId);//根据方法名得到它的环境信息PreparedStatement statement = connection.prepareStatement(mappedStatement.getSql());//根据sql得到预编译语句if ("insert".equals(mappedStatement.getSqlType())){//假设传入一个参数String paramType = mappedStatement.getParameterType();//得到参数类型Class clazz = args[0].getClass();Field[] fields = clazz.getDeclaredFields();for(int i=0;ifields[i].setAccessible(true);statement.setObject(i+1,fields[i].get(args[0]));}return statement.executeUpdate();}else if("delete".equals(mappedStatement.getSqlType())){for (int i=0;istatement.setObject(i+1,args[i]);}return statement.executeUpdate();}else if("select".equals(mappedStatement.getSqlType())){if (args!=null){for (int i=0;i//替换占位符的参数statement.setObject(i+1,args[i]);}}ResultSet resultSet = statement.executeQuery();//执行查询语句得到返回集合List list=new ArrayList();while (resultSet.next()){//遍历返回集合Class clazz = Class.forName(mappedStatement.getResultType());//通过反射机制,根据返回值类型创建实例Object object = clazz.newInstance();Field[] fields = clazz.getDeclaredFields();//通过反射机制,该类型的所有属性for (int i=0;ifields[i].setAccessible(true);//设置属性是可以访问的,避免privatefields[i].set(object,resultSet.getObject(fields[i].getName()));//给每个属性赋值}list.add(object);//把对象放在list集合中}return list;}return null;}});return (T) proxyInstance;}
}

MyBatis 框架中的代理类是这样的

/***    Copyright 2009-2017 the original author or authors.**    Licensed under the Apache License, Version 2.0 (the "License");*    you may not use this file except in compliance with the License.*    You may obtain a copy of the License at**       http://www.apache.org/licenses/LICENSE-2.0**    Unless required by applicable law or agreed to in writing, software*    distributed under the License is distributed on an "AS IS" BASIS,*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*    See the License for the specific language governing permissions and*    limitations under the License.*/
package org.apache.ibatis.binding;import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;/*** @author Clinton Begin* @author Eduardo Macarron*/
public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class mapperInterface;private final Map methodCache;public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod;}@UsesJava7private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class declaringClass = method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);}/*** Backport of java.lang.reflect.Method#isDefault()*/private boolean isDefaultMethod(Method method) {return ((method.getModifiers()& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)&& method.getDeclaringClass().isInterface();}
}

(七)使用

使用方式和Mybatis基本一样,只是功能简单。

  1. 创建一个实体类,一个操作数据库的接口(比如IUserMapper.class)
  2. 创建与接口(IUserMapper.class)对应的UserMapper.xml
  3. 在SqlMapConfig.xml 的配置中加上UserMapper.xml路径
  4. 通过SqlSession的getMapper()方法获取 ,通过动态代理生成的IUserMapper对象,然后调用IUserMapper里面的方法操作数据库

IUserMapper





SqlMapConfig.xml


IUserMapper

package com.lihua.mapper;import com.lihua.pojo.User;import java.util.List;/*** @author lihua* @date 2022/12/6 16:22*/
public interface IUserMapper {List queryUserAll();}

User

package com.lihua.pojo;import lombok.Data;
import lombok.ToString;/*** @author lihua* @date 2022/12/6 15:17* user*/
@Data
@ToString
public class User {private long id;private String account;private String password;private String email;
}

test

package com.lihua.test;import com.lihua.custommybatis.DefaultSqlSession;
import com.lihua.custommybatis.Resources;
import com.lihua.custommybatis.SqlSessionFactory;
import com.lihua.custommybatis.SqlSessionFactoryBuilder;
import com.lihua.mapper.UserMapper;
import com.lihua.pojo.User;import java.io.InputStream;/*** @author lihua* @date 2022/12/6 15:20* test*/
public class Test {@org.junit.Testpublic void  test(){InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);DefaultSqlSession sqlSession = sqlSessionFactory.openSession();try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);for (User user : mapper.queryUserAll()) {System.out.println(user.toString());}} catch (Exception e) {e.printStackTrace();}}
}

三、参考

手写mybatis 1

手写mybatis 2

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...