当前位置: 首页 > news >正文

手写Mybatis框架源码(简写)

pom文件:

springboot版本:2.6.5

jdk:8

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>mybatis-boot</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.5</version></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><!--web依赖--><dependency><!----><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!--测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><!--fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.83</version></dependency><!--MySQL驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><!--Dom4j依赖,比较流行的解析XML工具--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--XPath(XML Path Language)的解析和查询库--><!--使用XPath表达式来查询和操作XML文档中的节点,从而实现对XML数据的处理和分析--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.2.0</version></dependency><!--数据库连接池--><dependency><groupId>com.mchange</groupId><artifactId>c3p0</artifactId><version>0.9.5.5</version></dependency><!--在引入import注解的时候需要--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version> <!-- 确保使用兼容的版本 --></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.7</version> <!-- 确保使用兼容的版本 --></dependency><!--reflections--><dependency><groupId>org.reflections</groupId><artifactId>reflections</artifactId><version>0.9.12</version> <!-- 确保使用兼容的版本 --></dependency></dependencies></project>
  1. mybatis执行原理(自己理解的)通过mapper注解或者是mapperscan注解去读取相应的mapper接口,通过这些接口文件的类对象去实现动态代理,通过动态代理实现了一个动态对象,将这个动态对象加入到bean对象当中。在启动的时候顺便将mapper的xml的各种数据进行一个保存,通过这些数据可以拿到操作sql的sql语句和入参类型和返回类型。

代码的实现:

  1. 启动类:
    package org.example;import org.example.anno.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
    @MapperScan(value = "org.example.mapper") //自己实现的Mapper注解
    public class MybatisApplication {public static void main(String[] args) {SpringApplication.run(MybatisApplication.class,args);}
    }

2、MapperScan注解:

package org.example.anno;import org.example.handler.RegisterBeanMapperHandler;
import org.springframework.context.annotation.Import;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE) //注解的位置
@Retention(RetentionPolicy.RUNTIME) //运行时
@Import(RegisterBeanMapperHandler.class)   //注解的注册器
public @interface MapperScan {String value() default "";
}

3、MapperScan的注册器:

package org.example.handler;import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.DocumentException;
import org.example.anno.MapperScan;
import org.example.config.Configurations;
import org.example.dto.SqlData;
import org.example.executor.Executor;
import org.example.executor.impl.ExecutorImpl;
import org.example.session.Session;
import org.example.session.SessionInit;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;import java.beans.PropertyVetoException;
import java.lang.reflect.*;
import java.util.*;/*** 实现了bean的注册器,同时还实现了session接口,这个接口是自己实现的*/
@Slf4j
public class RegisterBeanMapperHandler implements ImportBeanDefinitionRegistrar, Session {private Configurations configurations; //xml的数据记录/*** * @param importingClassMetadata  添加了import注解的处理器对象* @param registry  注册器*/@SneakyThrows   //lombok的异常处理@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//判断是否含有MapperScan注解if (!importingClassMetadata.isAnnotated(MapperScan.class.getName())) {return;}//获取mapperscan注解的属性Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName());//获取这个mapper包的值String value = (String) annotationAttributes.get("value");//处理mapper.xml和数据库configurations = SessionInit.init();log.info("value: {}",value);//生成mapper接口的动态代理Map<String, Object> map = readMapperPackageData(value);if (registry instanceof DefaultListableBeanFactory){DefaultListableBeanFactory factory = (DefaultListableBeanFactory) registry;for (Map.Entry<String, Object> entry : map.entrySet()) {//将mapper生成的动态代理添加到bean容器当中,通过名字添加factory.registerSingleton(entry.getKey(),entry.getValue());}}}/*** * @param path 包名* @return* @throws ClassNotFoundException* @throws PropertyVetoException* @throws DocumentException*/public Map<String,Object> readMapperPackageData(String path) throws ClassNotFoundException, PropertyVetoException, DocumentException {Map<String,Object> map = new HashMap<>();//reflections的坑:是否excludeObjectClass排除这个包下的,默认为true。Reflections reflections = new Reflections(path,new SubTypesScanner(false));//如果默认为true,就会报SubType为null,就是说找不到Object相应的数据Set<Class<?>> allMapper = reflections.getSubTypesOf(Object.class);for (Class<?> aClass : allMapper) {//获取动态代理对象map.put(aClass.getSimpleName(),getMapper(aClass));}return map;}@Overridepublic <E> List<E> selectList(SqlData sqlData, Object... params) throws Exception {Executor executor = new ExecutorImpl();List<Object> query = executor.query(configurations, sqlData, params);return (List<E>) query;}@Overridepublic <E> E select(SqlData sqlData, Object... params) throws Exception {List<Object> objects = selectList(sqlData, params);if (objects == null || objects.size() ==0){return null;}if (objects.size()>1){throw new RuntimeException("查到不止一个");}return (E) objects.get(0);}@Overridepublic <E> E insert(SqlData sqlData, Object... params) throws Exception {Executor executor = new ExecutorImpl();List<Object> query = executor.query(configurations, sqlData, params);return (E) query.get(0);}@Overridepublic <E> E update(SqlData sqlData, Object... params) throws Exception {Executor executor = new ExecutorImpl();List<Object> query = executor.query(configurations, sqlData, params);return (E) query.get(0);}@Overridepublic <E> E delete(SqlData sqlData, Object... params) throws Exception {Executor executor = new ExecutorImpl();List<Object> query = executor.query(configurations, sqlData, params);return (E) query.get(0);}/*** * @param mapperType 要生成动态代理的类对象* @return* @param <T>* @throws PropertyVetoException* @throws DocumentException*/@Overridepublic <T> T getMapper(Class<?> mapperType) throws PropertyVetoException, DocumentException {//生成代理,通过代理处理你想要的结果Object instance =Proxy.newProxyInstance(mapperType.getClassLoader(), new Class[]{mapperType}, new InvocationHandler() {/*** method是动态代理对象的某个方法,args是这个方法的参数*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//获得method的名字和类,拼接之后得到sql的数据,拿到sql的数据去执行相应的逻辑String methodName = method.getName();String className = method.getDeclaringClass().getName();//返回类型Type genericReturnType = method.getGenericReturnType();//通过这个去configurations拿值String key = className+"."+methodName;SqlData sqlData = configurations.getMap().get(key);//相应方法的sql数据String sqlType = sqlData.getType();if ("insert".equals(sqlType)){return insert(sqlData,args);}else if ("update".equals(sqlType)){return update(sqlData,args);} else if ("delete".equals(sqlType)) {return delete(sqlData,args);}//是否是参数化类型if (genericReturnType instanceof ParameterizedType){return selectList(sqlData,args);}else{return select(sqlData,args);}}});//返回动态代理对象return (T) instance;}
}

4、configurations:

package org.example.config;import lombok.Data;
import org.example.dto.SqlData;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Component
@Data
public class Configurations {private DataSource dataSource;Map<String, SqlData> map = new ConcurrentHashMap<>();
}

5、session:

package org.example.session;import org.dom4j.DocumentException;
import org.example.dto.SqlData;import java.beans.PropertyVetoException;
import java.util.List;public interface Session {<E>List<E> selectList(SqlData sqlData,Object ...params) throws Exception;<E> E select(SqlData sqlData,Object ...params) throws Exception;<E> E insert(SqlData sqlData,Object ...params) throws Exception;<E> E update(SqlData sqlData,Object ...params) throws Exception;<E> E delete(SqlData sqlData,Object ...params) throws Exception;<T> T getMapper(Class<?> mapperType) throws PropertyVetoException, DocumentException;
}

6、SessionInit:

package org.example.session;import lombok.Data;
import org.dom4j.DocumentException;
import org.example.config.Configurations;
import org.example.service.DatasourceXml;
import org.example.service.XmlBuilderService;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import java.beans.PropertyVetoException;@Data
public class SessionInit {public static Configurations init() throws DocumentException, PropertyVetoException {Configurations configuration = new Configurations();//解析xml mapperXmlBuilderService xmlBuilderService = new XmlBuilderService(configuration);xmlBuilderService.parse();//数据库配置DatasourceXml datasourceXml = new DatasourceXml(configuration);datasourceXml.parse();//配置完之后,开始容器的创建,将所有的mapper进行封装,生成代理//通过返回bean对象将代理对象都交给bean容器管理return configuration;}
}

7、XmlBuilderMapper:

package org.example.service;import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.example.config.Configurations;
import org.example.dto.SqlData;import javax.annotation.PostConstruct;
import java.io.File;
import java.util.List;public class XmlBuilderService {private Configurations configuration;public XmlBuilderService(Configurations configuration) {this.configuration = configuration;}/*** 我在这里把地址写死了,当然可以在配置文件当中设置* @throws DocumentException*/public void parse() throws DocumentException {File folder = new File("src/main/resources/mapper");File[] files = folder.listFiles((dir,name)->{return name.endsWith(".xml");});for (File file : files) {SAXReader saxReader = new SAXReader();Document read = saxReader.read(file);Element readElements = read.getRootElement();//主标签上的属性String namespace = readElements.attributeValue("namespace");//主标签下的所有标签List<Element> elements = readElements.elements();//按照对应的namespace去找这个mapper对应的方法for (Element element : elements) {String name = element.getName();String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String textTrim = element.getTextTrim();//解析为相应的数据,保存到configurations当中SqlData sqlData = new SqlData();sqlData.setId(id);sqlData.setSql(textTrim);sqlData.setParameterType(parameterType);sqlData.setResultType(resultType);sqlData.setType(name);String key = namespace+"."+id;System.out.println(key);configuration.getMap().put(namespace+"."+id,sqlData);}}}
}
<!--resource mapper包下的一个xml-->
<?xml version="1.0" encoding="UTF-8" ?><mapper namespace="org.example.mapper.OrderMapper"><select id="getList" resultType="org.example.entity.Order">select*from orders</select><update id ="update" resultType="java.lang.Integer" parameterType="org.example.entity.Order">update ordersset orders = #{orders},date=#{date}where id = #{id}</update><delete id="delete" resultType="java.lang.Integer" parameterType="org.example.entity.Order">delete from orderswhere id = #{id}</delete><insert id="insert" resultType="java.lang.Integer" parameterType="org.example.entity.Order">insert into orders values (#{id},#{orders},#{date})</insert>
</mapper>

8、DatasourceXmlBuilder:我在这里写死了,当然可以通过解析或者配置yml文件去读取

package org.example.service;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.example.config.Configurations;import java.beans.PropertyVetoException;public class DatasourceXml {private Configurations configuration;public DatasourceXml(Configurations configuration) {this.configuration = configuration;}public void parse() throws PropertyVetoException {ComboPooledDataSource dataSource= new ComboPooledDataSource();dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ds0");dataSource.setUser("root");dataSource.setPassword("root");configuration.setDataSource(dataSource);}
}

SqlData:

package org.example.dto;import lombok.Data;@Data
public class SqlData {//方法名private String id;//返回类型private String resultType;//参数类型private String parameterType;//sql语句private String sql;//sql类型,select、update等等private String type;
}

BoundSql:

package org.example.dto;import lombok.Data;import java.util.ArrayList;
import java.util.List;@Data
public class BoundSql {//sql语句private String sql;//解析出来的sql当中的参数private List<String> paramsList =new ArrayList<>();
}

ExectuorImpl: 这段代码就不加注释了,自己理解了就好理解整个过程。

package org.example.executor.impl;import com.sun.deploy.ui.AboutDialog;
import org.example.config.Configurations;
import org.example.dto.BoundSql;
import org.example.dto.SqlData;
import org.example.executor.Executor;import javax.print.DocFlavor;
import javax.sql.DataSource;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;public class ExecutorImpl implements Executor {@Overridepublic <T> List<T> query(Configurations configurations, SqlData sqlData, Object... args) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException {DataSource dataSource = configurations.getDataSource();Connection connection = dataSource.getConnection();BoundSql boundSql = handleSql(sqlData.getSql());//String parameterType = sqlData.getParameterType();String sql = boundSql.getSql();List<String> paramsList = boundSql.getParamsList();PreparedStatement preparedStatement = connection.prepareStatement(sql);for (int i = 0; i < paramsList.size(); i++) {//参数替换,通过Method方法获取某个对象中某个对应的值String param = paramLists.get(i);Object arg = args[0];Class<?> type = getType(parameterType);//获取字段的对象,通过字段对象去匹配赋值Field declaredField = type.getDeclaredField(param);declaredField.setAccessible(true);Object filedValue = declaredField.get(arg);preparedStatement.setObject(i + 1, filedValue);}ResultSet resultSet;if ("select".equals(sqlData.getType())) {resultSet = preparedStatement.executeQuery();} else {int i = preparedStatement.executeUpdate();List<Integer> list = new ArrayList<>();list.add(i);return (List<T>) list;}if (resultSet == null){return null;}List<Object> resultList = new ArrayList<>(resultSet.getRow());String resultType = sqlData.getResultType();while (resultSet.next()){ResultSetMetaData metaData = resultSet.getMetaData();//获取列数int columnCount = metaData.getColumnCount();//创建一个返回类型的类对象Class<?> returnType = getType(resultType);//通过类对象创建一个返回对象Object instance = returnType.newInstance();for (int i = 1; i <= columnCount; i++) {//获取列的名字String columnName = metaData.getColumnName(i);System.out.println(columnName);//通过列的名字取值,并将列返回Object result = resultSet.getObject(columnName);System.out.println(result);//将列名与返回的类对象进行匹配PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,returnType);//得到一个写入方法Method writeMethod = propertyDescriptor.getWriteMethod();//将值写入到这个对象当中writeMethod.invoke(instance,result);}resultList.add(instance);}return (List<T>) resultList;}private Map<Integer,Integer> mapIndex = new LinkedHashMap<>();private List<String> paramLists = new ArrayList<>();private int fromIndex = 0;private BoundSql handleSql(String sql){submitSql(sql);for (Map.Entry<Integer, Integer> entry : mapIndex.entrySet()) {Integer begin = entry.getKey();Integer end = entry.getValue();String substring = sql.substring(begin + 2, end);paramLists.add(substring);}BoundSql boundSql = new BoundSql();for (String paramList : paramLists) {sql = sql.replace("#{" + paramList + "}", "?");boundSql.getParamsList().add(paramList);}boundSql.setSql(sql);//boundSql.setParamsList(paramLists);return boundSql;}private void submitSql(String sql) {int beginIndex = sql.indexOf("#{", fromIndex);if (beginIndex != -1){int endIndex = sql.indexOf("}", fromIndex + 1);if (endIndex != -1){fromIndex = endIndex+1;mapIndex.put(beginIndex,endIndex);submitSql(sql);}}}private Class<?> getType(String type) throws ClassNotFoundException {if (null == type || type.equals("")){return null;}return Class.forName(type);}}

总结:

总得来说,通过Proxy生成代理对象,通过代理对象调用相应的方法,相应的方法从configurations拿到相应的方法需要执行的sql语句。通过这个sql语句去执行JDBC基础的语句,也就是通过Connections去执行语句。

代码地址:lr-cc/fff

http://www.lryc.cn/news/500945.html

相关文章:

  • Flask返回中文Unicode编码(乱码)解决方案
  • 最大值和最小值的差
  • 如何在 IntelliJ IDEA 中为 Spring Boot 应用实现热部署
  • 探索 Java 中的 Bug 世界
  • SQL面试题——百度SQL面试题 连续签到领金币
  • easyExcel单一下拉框和级联下拉框
  • linux-安全-iptables防火墙基础笔记
  • 力扣刷题TOP101: 25.BM32合并二叉树
  • R的中文文本处理包--tmcn
  • 差异基因富集分析(R语言——GOKEGGGSEA)
  • scrapy对接rabbitmq的时候使用post请求
  • vue+elementUI+transition实现鼠标滑过div展开内容,鼠标划出收起内容,加防抖功能
  • 大模型语料库的构建过程 包括知识图谱构建 垂直知识图谱构建 输入到sql构建 输入到cypher构建 通过智能体管理数据生产组件
  • 阿里云ECS服务器域名解析
  • 牛客周赛71:A:JAVA
  • 查询产品所涉及的表有(product、product_admin_mapping)
  • 算法基础学习Day5(双指针、动态窗口)
  • docker 部署 mysql 9.0.1
  • 关于小标join大表,操作不当会导致笛卡尔积,数据倾斜
  • SpringMVC全局异常处理
  • 出海服务器可以用国内云防护吗
  • 从零开始的使用SpringBoot和WebSocket打造实时共享文档应用
  • Ant Design Pro实战--day01
  • pcl点云库离线版本构建
  • 字节高频算法面试题:小于 n 的最大数
  • ElasticSearch常见面试题汇总
  • Spring Boot如何实现防盗链
  • 工作中常用springboot启动后执行的方法
  • 力扣-图论-3【算法学习day.53】
  • Linux上的C语言编程实践