手写一个简单的mybatis框架

2022-07-04 14:37:30 浏览数 (1)

前言:

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

准备工作:

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

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

2、从源码的角度分析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数据读写阶段的流程

执行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

0 人点赞