手写mybatis - 2 源码解析
文章目录
0 缘起
本文接上文 手写mybatis - 1 概述&代码 , 目的是解释下代码的基本组成, 和设计思想,本文代码基于自己手写的 mybatis , 包结构和类名都和 mybatis 高度相似, 相当于一个mybatis 简化版吧。
1 mybatis 核心流程
mybatis 核心业务流程其实也蛮简单
1 解析 mybatis 系统配置文件,封装成 Configuration
2 解析 mapper.xml 封装成 MapperStatement 供后面使用
3 具体的业务代码,调用 dao 的代理类
4 参数的映射和处理
5 根据入参 和 MapperStatement 动态生成 执行的 sql 语句
6 通过 resulthandle 处理结果集合映射, 并且返回结果集
2 源码分析
//读取mybatis主配置文件
InputStream is = Resources.getResourceAsStream("MybatisConfig.xml");
//创建SqlSessionFactoryBuilder
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//通过构建者设计模式创建工厂
SqlSessionFactory factory = builder.build(is);
//通过工厂模式创建sqlSession
SqlSession sqlSession = factory.openSession();
//通过动态代理获取对应mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用方法
List<User> userList = mapper.getUserList();
//遍历打印结果集
for (User user : userList) {
System.out.println(user);
}
2.1 SqlSessionFactoryBuilder
负责加载并且解析配置文件 返回 SqlSessionFactory
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream);
return build(parser.parse());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
2.2 XMLConfigBuilder
具体的解析逻辑 将输入流解析成 Configuration 供后面使用
public class XMLConfigBuilder {
private InputStream inputStream;
public XMLConfigBuilder(InputStream inputStream) {
this.inputStream = inputStream;
}
public Configuration parse() {
return loadXmlConfiguration(inputStream);
}
/**
* 读取主配置文件
*
* @param inputStream
* @return
*/
public static Configuration loadXmlConfiguration(InputStream inputStream) {
//使用dom4j和xpath解析主配置文件
SAXReader reader = new SAXReader();
Configuration config = new Configuration();
try {
//将主配置文件二进制流传入,得到document对象
Document document = reader.read(inputStream);
//获取property节点集合(用于生成连接对象的四大参数)
List<Element> list = document.selectNodes("//property");
for (Element element : list) {
String name = element.attributeValue("name");
String value = element.attributeValue("value");
if (name.equals("username")) config.setUsername(value);
if (name.equals("driver")) config.setDriver(value);
if (name.equals("password")) config.setPassword(value);
if (name.equals("url")) config.setUrl(value);
}
//读取主配置文件下的所有mapper映射文件
List<Element> listMapper = document.selectNodes("//mapper");
for (Element element : listMapper) {
//遍历每个mapper标签,获取resource属性(mapper映射文件的全限类名)
String resource = element.attributeValue("resource");
//使用dom4j和xpath解析mapper映射文件
Map<String, MappedStatement> loadMapper = loadMapper(resource, config);
config.setMappedStatements(loadMapper);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
return config;
}
/**
* 使用dom4j和xpath解析mapper映射文件
*
* @param resource
* @return
*/
private static Map<String, MappedStatement> loadMapper(String resource, Configuration config) {
Map<String, MappedStatement> mappers = new HashMap<String, MappedStatement>();
//利用类加载器将配置文件转换为二进制流
InputStream inputStream = Resources.getResourceAsStream(resource);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(inputStream);
//获取根节点对象
Element rootElement = document.getRootElement();
//获取根节点的namespace属性值
String namespace = rootElement.attributeValue("namespace");
//获取根节点下所有select节点
List<Element> selectNodes = rootElement.selectNodes("//select");
//遍历,获取每个select节点的id、resultType以及sql语句等值
for (Element selectNode : selectNodes) {
String id = selectNode.attributeValue("id");
String resultType = selectNode.attributeValue("resultType");
SqlSource sqlSource = parseDynamicSqlNode(selectNode);
//用一个mapper对象存储以上的值
MappedStatement mapper = new MappedStatement();
mapper.setConfiguration(config);
mapper.setId(id);
mapper.setResultType(Class.forName(resultType));
mapper.setSqlSource(sqlSource);
//一个mapper映射文件可以有多个select(statment),不同映射文件里,select的id可以相同
//所以将namespace+"."+id作为唯一标志,用于区分,并存入map集合里
mappers.put(namespace + "." + id, mapper);
}
return mappers;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
}
}
private static SqlSource parseDynamicSqlNode(Element selectNode) {
String sql = selectNode.getTextTrim();
StaticSqlSource staticSqlSource = new StaticSqlSource(sql);
return staticSqlSource;
}
}
2.3 Configuration
具体的配置对象, 主要保存了连接信息 和 解析 mapper.xml 生成 的 mappedStatements 映射
private String username;
private String password;
private String url;
private String driver;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected Map<String, MappedStatement> mappedStatements = new HashMap();
public Executor newExecutor(ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor = null;
if (ExecutorType.BATCH == executorType) {
} else if (ExecutorType.REUSE == executorType) {
} else {
executor = new SimpleExecutor(this);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.4 SqlSessionFactory
完成配置解析后要根据 Configuration 对象生成会话工厂, 会话工厂有什么用呢, 说白了 就是不断的开启会话
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public Configuration getConfiguration() {
return configuration;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType());
}
private SqlSession openSessionFromDataSource(ExecutorType execType) {
try {
final Executor executor = configuration.newExecutor(execType);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
// 会话工厂的接口
public interface SqlSessionFactory {
SqlSession openSession();
}
2.5 SqlSession
通过 上面生成的会话工厂 可以开启一个会话
SqlSession sqlSession = factory.openSession();
DefaultSqlSessionFactory
...
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType());
}
private SqlSession openSessionFromDataSource(ExecutorType execType) {
try {
final Executor executor = configuration.newExecutor(execType);
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2.6 Executor & SimpleExecutor
执行器,根据上面的 configuration 对象可以生成对应的执行器, 执行器就是具体干活的,负责根据参数和上面解析 MappedStatement,还有ResultSetHandler,解析sql,处理结果并且返回
public interface Executor {
ResultSetHandler NO_RESULT_HANDLER = null;
<E> List<E> query(MappedStatement ms, Object parameter, ResultSetHandler resultSetHandler) throws SQLException;
...
}
SimpleExecutor 就是 Executor 的简单实现
public class SimpleExecutor implements Executor {
protected Configuration configuration;
public SimpleExecutor(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException {
Statement stmt = null;
Configuration configuration = mappedStatement.getConfiguration();
StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler);
return handler.query(stmt, resultSetHandler);
}
}
2.7 DefaultSqlSession
有了 执行器和默认的配置对象 我们可以生成默认的会话了, 会话 其实就是负责 增删该查的, 执行器 其实也是调用的 SqlSession 去执行对应的逻辑
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statement) throws RuntimeException {
return this.<T>selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) throws RuntimeException {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw new RuntimeException("Error querying database. Cause: " + e);
}
}
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public <T> T getMapper(Class<T> clazz){
try {
T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz));
return proxyObj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2.8 MapperProxy
MapperProxy 就是 mapper 对象的一个代理, 说白了 就是你定义 dao 的接口,用 jdk 动态代理生成的一个代理对象
> //通过动态代理获取对应mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
...
public <T> T getMapper(Class<T> clazz){
try {
T proxyObj = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperProxy(this, clazz));
return proxyObj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
MapperProxy 其实zu
public class MapperProxy implements InvocationHandler {
private SqlSession sqlSession;
private Class mapperInterface;
public MapperProxy(SqlSession sqlSession, Class mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果返回值是一个List集合才执行如下代码
Class<?> returnType = method.getReturnType();
if (returnType == List.class) {
//代理对象调用任何方法,都会在这执行
//真正执行Sql语句
//执行Sql语句的工作:sqlSession对象的selectList(),也就是说咱在这要调用那个方法
//key是"Mapper接口的全限定名"+"."+方法名
String methodName = method.getName();//方法名
Class<?> clazz = method.getDeclaringClass();
String clazzName = clazz.getName();//获取接口的全限定名
String key = clazzName + "." + methodName;
List<Object> list = sqlSession.selectList(key, args);
return list;
}else {
return null;
}
}
2.9 dao 的执行
dao方法调用的时候 其实执行的是代理对象的 invoke 方法
//调用方法
List<User> userList = mapper.getUserList();
--> MapperProxy --> invoke
--> sqlSession-->selectList
DefaultSqlSession
...
@Override
public <E> List<E> selectList(String statement, Object parameter) throws RuntimeException {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, parameter, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw new RuntimeException("Error querying database. Cause: " + e);
}
}
我们看看 executor 的 query
SimpleExecutor
...
@Override
public <E> List<E> query(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultSetHandler) throws SQLException {
Statement stmt = null;
Configuration configuration = mappedStatement.getConfiguration();
StatementHandler handler = new SimpleStatementHandler(mappedStatement, parameter, resultSetHandler);
return handler.query(stmt, resultSetHandler);
}
StatementHandler
看了这么久终于看到原生 jdbc 的东西了。
...
public class SimpleStatementHandler implements StatementHandler {
private MappedStatement mappedStatement;
private ResultSetHandler resultHandler;
private Object parameter;
public SimpleStatementHandler(MappedStatement mappedStatement, Object parameter, ResultSetHandler resultHandler) {
this.mappedStatement = mappedStatement;
this.resultHandler = resultHandler;
this.parameter = parameter;
}
@Override
public <E> List<E> query(Statement statement, ResultSetHandler resultHandler) throws SQLException {
Connection conn = null;
PreparedStatement preparedStatement = null;
try {
conn = getConnection();
preparedStatement = conn.prepareStatement(mappedStatement.getBoundSql(parameter).getSql());
ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
parameterHandler.setParameters(preparedStatement);
preparedStatement.execute();
resultHandler = new DefaultResultSetHandler(mappedStatement.getResultType(), preparedStatement.getResultSet());
return resultHandler.handleResultSets();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (preparedStatement != null) {
preparedStatement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
private Connection getConnection() throws SQLException, ClassNotFoundException {
Configuration configuration = mappedStatement.getConfiguration();
Class.forName(configuration.getDriver());
return DriverManager.getConnection(configuration.getUrl(), configuration.getUsername(), configuration.getPassword());
}
}
2.10 SimpleStatementHandler
就是和 jdbc 打交道的,通过 ParameterHandler 处理参数,ResultSetHandler 处理返回结果并且返回
DefaultParameterHandler
public class DefaultParameterHandler implements ParameterHandler {
private Object parameter;
public DefaultParameterHandler(Object parameter) {
this.parameter = parameter;
}
@Override
public void setParameters(PreparedStatement psmt) throws SQLException {
try {
Object[] parameters = (Object[]) parameter;
if (parameters != null && parameters.length > 0) {
for (int i = 0; i < parameters.length; i++) {
if (parameters[i] instanceof Integer) {
psmt.setInt(i + 1, (Integer) parameters[i]);
} else if (parameters[i] instanceof Long) {
psmt.setLong(i + 1, (Long) parameters[i]);
} else if (parameters[i] instanceof String) {
psmt.setString(i + 1, String.valueOf(parameters[i]));
} else if (parameters[i] instanceof Boolean) {
psmt.setBoolean(i + 1, (Boolean) parameters[i]);
} else {
psmt.setString(i + 1, String.valueOf(parameters[i]));
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
DefaultResultSetHandler
...
public class DefaultResultSetHandler<E> implements ResultSetHandler {
private Class type;
private ResultSet resultSet;
public DefaultResultSetHandler(Class type, ResultSet resultSet) {
this.type = type;
this.resultSet = resultSet;
}
@Override
public <E> List<E> handleResultSets() {
try {
ArrayList<E> resultList = new ArrayList<>();
Object resultObject = null;
while (resultSet.next()) {
resultObject = new DefaultObjectFactory().create(type);
for (Field field : resultObject.getClass().getDeclaredFields()) {
setValue(resultObject, field, resultSet);
}
resultList.add( (E) resultObject);
}
return resultList;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
public void setValue(Object resultObject, Field field, ResultSet resultSet) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, SQLException {
Method method = type.getMethod("set" + upperCapital(field.getName()), field.getType());
method.invoke(resultObject, getResult(field, resultSet));
}
public Object getResult(Field field, ResultSet rs) throws SQLException {
Class<?> type = field.getType();
if (Integer.class == type) {
return rs.getInt(field.getName());
} else if (String.class == type) {
return rs.getString(field.getName());
} else if (Long.class == type) {
return rs.getLong(field.getName());
} else {
return rs.getString(field.getName());
}
}
private String upperCapital(String name) {
String first = name.substring(0, 1);
String tail = name.substring(1);
return first.toUpperCase() + tail;
}
}
3 拓展
mybatis 的核心逻辑到这里就告一段落了,但是mybatis 远不止这些东西,比如 sql 动态生成, 插件机制,缓存机制,事务机制, 这些东西博主以后会专门写文章介绍的。尽情期待。