前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写一个简单的mybatis框架

手写一个简单的mybatis框架

作者头像
全栈程序员站长
发布2022-07-04 14:37:30
3770
发布2022-07-04 14:37:30
举报
文章被收录于专栏:全栈程序员必看

前言:

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

准备工作:

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

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

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

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

手写一个简单的mybatis框架
手写一个简单的mybatis框架

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

1、初始化阶段;

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

一、初始化阶段

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

该逻辑是放在SqlsessionFactory.java中

SqlsessionFactory.java

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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框架
手写一个简单的mybatis框架

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

SimpleExecutor .java

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021年8月3,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档