手写一个简单的mybatis框架

手写一个简单的mybatis框架

前言:

mysql作为优秀的开源框架之一,作为一个高级java程序员不仅仅学会使用它,更应该学习它的源码、设计、思想。经过前面对mybatis的流程的学习,今天分享一下如何自己实现一个简单的mybatis框架。当然由于技术和时间的限制,本文在这里实现的一个简化版本的mybatis,相对来说只是mybatis本身框架的冰山一角,但是整体的流程以及设计的思想都是和mybatis一样的,个人觉得对我们理解和学习mybatis还是非常有用

准备工作:

学习本文首先最好是对mybatis的基本操作会使用、其次是对mybatis的大概的流程有一些了解;建议可以先参考我的其他几篇对mybatis分析的文章

1、从源码的角度分析mybatis的核心流程(上)

2、从源码的角度分析mybatis的核心流程(中)

本文的目录结构基本上和mybatis的源码的结构保持一致

手写一个简单的mybatis框架

好了,废话不多说了,开始学习,为了更好的帮助理解,我这里将源码分为两个部分:

1、初始化阶段;

2、代理、数据读写及结果解析

一、初始化阶段

初始化阶段主要是将配置文件加载到内存,保存到configuration对象中,本文大量简化了操作,主要是将数据库的连接信息、xxxmapper.xml加载到configuration中

该逻辑是放在SqlsessionFactory.java中

SqlsessionFactory.java

package com.taolong.mybatis_myself.session;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import com.taolong.mybatis_myself.config.Configuration;
import com.taolong.mybatis_myself.config.MappedStatement;

public class SqlSessionFactory {

	private Configuration configuration = new Configuration();
	
	public SqlSessionFactory() {
		//加载数据库信息
		loadDbInfo();
		//解析mapper.xml内容,保存到configuration中
		loadMappersInfo();
	}
	
	/**
	 * 加载数据库的连接信息,设置到configuration中
	 */
	private void loadDbInfo() {
		InputStream dbInfo = SqlSessionFactory.class.getClassLoader()
			.getResourceAsStream(configuration.DB_FILE);
		Properties properties = new Properties();
		try {
			properties.load(dbInfo);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		configuration.setDbDriver(properties.getProperty("jdbc.driver"));
		configuration.setDbUrl(properties.getProperty("jdbc.url"));
		configuration.setDbUserName(properties.getProperty("jdbc.username"));
		configuration.setDbPassWord(properties.getProperty("jdbc.password"));
	}
	
	private void loadMappersInfo() {
		URL resources = null;
		//获取存放mapper文件的路径
		resources = SqlSessionFactory.class.getClassLoader()
				.getResource(configuration.MAPPER_LOCATION);
		File mappers = new File(resources.getFile());
		if (mappers.isDirectory()) {
			File[] listFiles = mappers.listFiles();
			if (listFiles == null || listFiles.length == 0)return;
			for (File mapper : listFiles) {
				loadMapperInfo(mapper);
			}
		}
	}
	
	private void loadMapperInfo(File mapper) {
		SAXReader reader = new SAXReader();
		Document document = null;
		try {
			document = reader.read(mapper);
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		Element node = document.getRootElement();
		String namespace = node.attribute("namespace").getData().toString();
		List<Element> selects = node.elements("select");
		if (selects == null || selects.isEmpty()) return;
		for (Element element : selects) {
			MappedStatement mappedStatement = new MappedStatement();
			String id = element.attribute("id").getData().toString();
			String resultType = element.attribute("resultType").getData().toString();
			String sql = element.getData().toString();
			String sourceId = namespace+"."+id;
			
			mappedStatement.setSourceId(sourceId);
			mappedStatement.setNamespace(namespace);
			mappedStatement.setResultType(resultType);
			mappedStatement.setSql(sql);
			configuration.getMappedStatements().put(sourceId, mappedStatement);
		}
	}
	
	
	/**
	 * 单元测试
	 */
	@Test
	public void sqlSessionFactoryTest() {
		SqlSessionFactory sessionFactory = new SqlSessionFactory();
		Configuration configuration = sessionFactory.configuration;
		System.out.println(configuration);
	}
	
	public SqlSession openSession() {
		return new DefaultSqlSession(configuration);
	}
}

 初始化简化了很多,mybatis中的初始化时解析mybatis-config.xml和xxxmapper.xml然后将加载的内容放到configuration中,其中做了很多解析mybatis-config中的属性、以及xxxmapper中resultmap、sql、select、update、delete…等,而本文只是做了解析db.properties和xxxmapper.xml(还是简化内容的xml),到这里初始化就已经结束了,数据已经保存保存到了configuration中

二、代理、数据读写及结果解析

1、代理:代理阶段就是生成动态代理的mapper接口,使得面向接口编程得到更好的展现,代替了原来的ibatis编程模式。使用动态代理模式生成动态代理接口

(1)使用了一个简单工厂

MapperProxyFactory .java

package com.taolong.mybatis_myself.binding;

import java.lang.reflect.Proxy;

import com.taolong.mybatis_myself.session.SqlSession;

/**
 * @author hongtaolong
 * 动态代理类的生产工厂
 */
public class MapperProxyFactory {

	public static <T> T getMapperProxy(SqlSession sqlSession,Class<T> mapperInterface) {
		//创建一个invocationhandler的动态代理对象
		MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface);
		return (T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),
					new Class[] {mapperInterface}, mapperProxy);
	}
}

 (2)MapperProxy是代理对象实现了InvocationHandler接口,最终是调用它的invoke方法,来看看它的实现

MapperProxy.java

package com.taolong.mybatis_myself.binding;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;

import com.taolong.mybatis_myself.session.SqlSession;

/**
 * @author hongtaolong
 * mapper接口的动态代理类
 */
public class MapperProxy<T> implements InvocationHandler{

	private SqlSession sqlSession;
	
	private final Class<T> mapperInterface;
	
	
	
	public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
		super();
		this.sqlSession = sqlSession;
		this.mapperInterface = mapperInterface;//被代理的对象
	}

	private <T> boolean isCollection(Class<T> type) {
		return Collection.class.isAssignableFrom(type);
	}
	
	//动态代理类最终调用的方法
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Object result = null;
		//object本身的方法,不进行增强
		if (Object.class.equals(method.getDeclaringClass())) {
			return method.invoke(this, args);
		}
		//获取接口的返回值,然后选择是调用selectone还是selectList
		Class<?> returnType = method.getReturnType();
		if (isCollection(returnType)) {
			result = sqlSession.selectList(mapperInterface.getName()+"."+method.getName(), args);
		}else {
			result = sqlSession.selectOne(mapperInterface.getName()+"."+method.getName(), args);
		}
		return result;
	}
	
}

 这里的invoke方法也比较简单,只是做了查询的处理

2、数据读写阶段

先看一个图来梳理mybatis数据读写阶段的流程

手写一个简单的mybatis框架

 执行sql从MapperProxy.invoke()所以执行到了sqlsession中,源码中sqlsession其实基本不做执行sql的操作,它是使用executor来执行,源码中excutor中也是比较复杂的,有很多的设计模式(代理、模板…),还有很多缓存的逻辑,这里做了简化直接就是获取连接和查询的逻辑,来看看代码

SimpleExecutor .java

package com.taolong.mybatis_myself.executor;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import com.taolong.mybatis_myself.config.Configuration;
import com.taolong.mybatis_myself.config.MappedStatement;
import com.taolong.mybatis_myself.executor.parameter.DefaultParameterHandler;
import com.taolong.mybatis_myself.executor.parameter.ParameterHandler;
import com.taolong.mybatis_myself.executor.resultset.DefaultResultSetHandler;
import com.taolong.mybatis_myself.executor.resultset.ResultSetHandler;
import com.taolong.mybatis_myself.executor.statement.DefaultStatementHandler;
import com.taolong.mybatis_myself.executor.statement.StatementHandler;

public class SimpleExecutor implements Executor {

	private Configuration configuration;
	
	
	public SimpleExecutor(Configuration configuration) {
		super();
		this.configuration = configuration;
	}


	@Override
	public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException {
		//1.获取连接
		Connection connection = getConnection();
		//2.实例化statementhandler
		StatementHandler statementHandler = new DefaultStatementHandler(ms);
		//3.获取prepareStatement
		PreparedStatement prepare = statementHandler.prepare(connection);
		//4.实例化prepareHandler对象,sql语句占位符
		ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
		parameterHandler.setParameters(prepare);
		//5.查询
		ResultSet resutSet = statementHandler.query(prepare);
		//对resultSet进行处理
		ResultSetHandler resultSetHandler = new DefaultResultSetHandler(ms);
		return resultSetHandler.handleResultSets(resutSet);
	}
	
	
	/**
	 * 获取数据库的连接,和jdbc一样的方式
	 * @return
	 */
	private Connection getConnection() {
		Connection connection = null;
		try {
			Class.forName(configuration.getDbDriver());
			connection = DriverManager.getConnection(configuration.getDbUrl(),
							configuration.getDbUserName(), configuration.getDbPassWord());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return connection;
	}


	public Configuration getConfiguration() {
		return configuration;
	}


	public void setConfiguration(Configuration configuration) {
		this.configuration = configuration;
	}
	
}

其实代码执行到这里已经完成了整个流程,但是这里executor是将查询的操作、结果的处理、参数处理放到了StatementHandler、ParameterHandler、ResultSetHandler中,其实从上面的图中也能看出来。来看看本文实现这三个类的代码,当然也是简化了很多逻辑

DefaultStatementHandler .java

package com.taolong.mybatis_myself.executor.statement;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.taolong.mybatis_myself.config.MappedStatement;

public class DefaultStatementHandler implements StatementHandler {

	private MappedStatement mappedStatement;
	
	
	public DefaultStatementHandler(MappedStatement mappedStatement) {
		super();
		this.mappedStatement = mappedStatement;
	}

	@Override
	public PreparedStatement prepare(Connection connection) {
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement =  connection.prepareStatement(mappedStatement.getSql());
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return preparedStatement;
	}

	@Override
	public ResultSet query(PreparedStatement statement) {
		ResultSet resultSet = null;
		try {
			resultSet =  statement.executeQuery();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return resultSet;
	}

}

 StatementHandler的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用源码中的statementHandler也比这里复杂很多,有处理批量操作、修改、还有调用存储过程的statementHandler,这里实现的也很简单。

DefaultParameterHandler .java

package com.taolong.mybatis_myself.executor.parameter;

import java.sql.PreparedStatement;
import java.sql.SQLException;

public class DefaultParameterHandler implements ParameterHandler {

	private Object parameter;
	
	
	
	public DefaultParameterHandler(Object parameter) {
		super();
		this.parameter = parameter;
	}

	@Override
	public void setParameters(PreparedStatement ps) throws SQLException {
		if (parameter == null) return;
		if (parameter.getClass().isArray()) {
			Object[] paramArray = (Object[]) parameter;
			int parameterIndex = 1;
			for (Object param : paramArray) {
				if (param instanceof Integer) {
					ps.setInt(parameterIndex, (int)param);
				}else if(param instanceof String) {
					ps.setString(parameterIndex, (String)param);
				}//more type...
				parameterIndex ++;
			}
		}
	}

	public Object getParameter() {
		return parameter;
	}

	public void setParameter(Object parameter) {
		this.parameter = parameter;
	}
	
}

 ParameterHandler是对预编译的SQL语句进行参数设置,源码中的parameterhandler中远比这复杂,它不仅处理基本常用类型的参数,还解析mapper.xml中的parameterMap和parameterType等参数,而这里就简单做了Integer和String。

DefaultResultSetHandler .java

 

 

package com.taolong.mybatis_myself.executor.resultset;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.taolong.mybatis_myself.config.MappedStatement;
import com.taolong.mybatis_myself.reflection.ReflectionUtils;

public class DefaultResultSetHandler implements ResultSetHandler {

	private MappedStatement mappedStatement;
	
	
	
	public DefaultResultSetHandler(MappedStatement mappedStatement) {
		super();
		this.mappedStatement = mappedStatement;
	}



	@Override
	public <E> List<E> handleResultSets(ResultSet resultSet) throws SQLException {
		if (resultSet == null) return null;
		List<E> ret = new ArrayList<E>();
		String className = mappedStatement.getResultType();
		Class<?> returnClass = null;
		//使用反射处理
		while(resultSet.next()) {
			try {
				returnClass = Class.forName(className);
				E entry = (E)returnClass.newInstance();
				Field[] declaredFields = returnClass.getDeclaredFields();
				for (Field field : declaredFields) {
					String fieldName = field.getName();
					if (field.getType().getSimpleName().equals("String")) {
						ReflectionUtils.setBeanProp(entry, fieldName, resultSet.getString(fieldName));
					}else if(field.getType().getSimpleName().equals("Integer")) {
						ReflectionUtils.setBeanProp(entry, fieldName, resultSet.getInt(fieldName));
					}//more type
					ret.add(entry);
				}
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return ret;
	}

	public MappedStatement getMappedStatement() {
		return mappedStatement;
	}

	public void setMappedStatement(MappedStatement mappedStatement) {
		this.mappedStatement = mappedStatement;
	}
	
}

 ResultSetHandler是对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型,源码中也同样是远比这复杂,包括解析resultMap,分页、甚至存储过程返回的多结果集处理等等,本文就是对默认返回的是resultType,然后通过反射的技术对该class的类型对象进行赋值(源码也是用反射)

好了,就分析到这里把,如有错误,欢迎指正!谢谢,本文的源码经过测试是能运行成功,需要自己简单创建一个数据库表,另外配置文件可能要稍微修改一点点。

本文源码的地址:https://github.com/xiaohongstudent/mybatis_myself

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111232.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • exosip

    exosip

    2021年11月30日
  • 时序数据库 mysql_时序数据库 应用场景

    时序数据库 mysql_时序数据库 应用场景influxDB介绍时间序列数据是以时间字段为每行数据的标示,比如股票市场的价格,环境中的温度,主机的CPU使用率等。但是又有什么数据是不包含timestamp的呢?几乎所有的数据都可以打上一个timestamp字段。时间序列数据更重要的一个属性是如何去查询它。在查询的时候,对于时间序列我们总是会带上一个时间范围去过滤数据。同时查询的结果里也总是会包含timestamp字段。InfluxDB是一…

  • 深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解

    深入浅出Yolo系列之Yolov3&Yolov4&Yolov5&Yolox核心基础知识完整讲解因为工作原因,项目中经常遇到目标检测的任务,因此对目标检测算法会经常使用和关注,比如Yolov3、Yolov4算法。当然,实际项目中很多的第一步,也都是先进行目标检测任务,比如人脸识别、多目标追踪、REID、客流统计等项目。因此目标检测是计算机视觉项目中非常重要的一部分。从2018年Yolov3年提出的两年后,在原作者声名放弃更新Yolo算法后,俄罗斯的Alexey大神扛起了Yolov4的大旗。在此,大白将项目中,需要了解的Yolov3、Yolov4系列相关知识点以及相关代码进行完整的汇总,希望和大家

  • <C++学习笔记>iterator C++

    <C++学习笔记>iterator C++

  • async/await Task Timeout

    async/await Task Timeout

  • matlab 汽车振动,基于MatLab的车辆振动响应幅频特性分析

    matlab 汽车振动,基于MatLab的车辆振动响应幅频特性分析【实例简介】利用MatLab-Simulink仿真了不同减振器阻尼系数和不同悬架刚度下车身加速度、悬架动挠度、车轮动载分别对于路面速度激励振动响应的幅频特性,从而为半主动悬架和主动悬架的优化提供必要的理论支持.关于汽车振动与MATLAB的案例,大家都可以下载看看,3Matlab472基于Simulink车辆振动响应幅频特性分析SimulinkAdd2ToWorkspaceSS1/m,…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号