前言:
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