一、简介
Mybatis是一款优秀的持久层框架,在互联网公司的项目开发中使用非常广泛。通过对MyBatis源码的学习,可以更好的了解Mybatis的使用,同时也可以借鉴其中优秀的编程方式和设计模式。学习是一个抽象归纳然后再运用的过程,通过对Mybatis源码核心部分的抽象,手写一个Mybatis框架,来更好的学习Mybatis。
二、处理流程
Mybatis核心流程分为三步:
1.读取XML配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化工作;
2.封装iBatis的编程模式,使用mapper接口开发的初始化工作;
3.通过SqlSession完成SQL的解析,参数的映射、SQL的执行、结果的反射解析过程;
三、手写Mybatis具体实现
1. 实体相关类
Dept.java:
package com.taoxi.mybatis.mysimple.entity;
public class Dept {
private Integer deptNo;
private String dName;
private String loc;
public Integer getDeptNo() {
return deptNo;
}
public void setDeptNo(Integer deptNo) {
this.deptNo = deptNo;
}
public String getDname() {
return dName;
}
public void setDname(String dname) {
this.dName = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
}
DeptMapper.java:
package com.taoxi.mybatis.mysimple.mapper;
import com.taoxi.mybatis.mysimple.entity.Dept;
public interface DeptMapper {
Dept deptFindById(Integer deptId);
}
DeptMapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.taoxi.mybatis.mysimple.mapper.DeptMapper">
<select id="deptFindById" resultType="com.taoxi.mybatis.mysimple.entity.Dept">
select * from dept where deptno= ?
</select>
</mapper>
2.配置相关类
Configuration.java:
package com.taoxi.mybatis.mysimple.config;
import java.util.HashMap;
import java.util.Map;
public class Configuration {
private String jdbcDriver;
private String jdbcUrl;
private String jdbcUsername;
private String jdbcPassword;
private Map<String, MappedStatement> mappedStatments = new HashMap<String, MappedStatement>();
public String getJdbcDriver() {
return jdbcDriver;
}
public void setJdbcDriver(String jdbcDriver) {
this.jdbcDriver = jdbcDriver;
}
public String getJdbcUrl() {
return jdbcUrl;
}
public void setJdbcUrl(String jdbcUrl) {
this.jdbcUrl = jdbcUrl;
}
public String getJdbcUsername() {
return jdbcUsername;
}
public void setJdbcUsername(String jdbcUsername) {
this.jdbcUsername = jdbcUsername;
}
public String getJdbcPassword() {
return jdbcPassword;
}
public void setJdbcPassword(String jdbcPassword) {
this.jdbcPassword = jdbcPassword;
}
public Map<String, MappedStatement> getMappedStatments() {
return mappedStatments;
}
public void setMappedStatments(Map<String, MappedStatement> mappedStatments) {
this.mappedStatments = mappedStatments;
}
}
MappedStatemant.java:
package com.taoxi.mybatis.mysimple.config;
public class MappedStatement {
private String namespace;
private String sourceId;
private String resultType;
private String sql;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
3.SqlSessionFactory工厂类
SqlSessionFactory.java:
package com.taoxi.mybatis.mysimple.session;
import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;
public class SqlSessionFactory {
private final Configuration configuration = new Configuration();
public SqlSessionFactory() {
loadDbInfo();
loadMappersInfo();
}
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
//记录mapper xml文件存放的位置
public static final String MAPPER_CONFIG_LOCATION = "mappers";
//记录数据库连接信息文件存放位置
public static final String DB_CONFIG_FILE = "db.properties";
//加载数据库配置信息
private void loadDbInfo() {
//加载数据库信息配置文件
InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
Properties properties = new Properties();
try {
properties.load(dbIn);
} catch (IOException e) {
throw new RuntimeException("load db info error:", e);
}
//将数据库配置信息写入configuration对象
configuration.setJdbcDriver(properties.get("jdbc.driver").toString());
configuration.setJdbcPassword(properties.get("jdbc.password").toString());
configuration.setJdbcUrl(properties.get("jdbc.url").toString());
configuration.setJdbcUsername(properties.get("jdbc.username").toString());
}
//加载指定文件夹下的所有mapper.xml
private void loadMappersInfo() {
URL resources = null;
resources = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
File mappers = new File(resources.getFile());
if (mappers.isDirectory()){
File[] listFiles = mappers.listFiles();
//遍历文件夹下所有的mapper.xml,解析信息后,注册到configuration对象中
for (File file :
listFiles) {
loadMapperInfo(file);
}
}
}
//加载指定的mapper.xml文件
private void loadMapperInfo(File file) {
//创建saxReader对象
SAXReader reader = new SAXReader();
//通过read方法读取一个文件,转换成Document对象
Document document = null;
try {
document = reader.read(file);
} catch (DocumentException e) {
throw new RuntimeException("load mapper info error:", e);
}
//获取根节点元素对象<mapper>
Element root = document.getRootElement();
//获取命名空间
String namespace = root.attribute("namespace").getData().toString();
//获取select子节点列表
List<Element> selects = root.elements("select");
//遍历select节点,将信息记录到MappedStatement对象,并登记到configuration对象中
for (Element element :
selects) {
MappedStatement mappedStatement = new MappedStatement();//实例化mappedStatement
String id = element.attribute("id").getData().toString();//读取id属性
String resultType = element.attribute("resultType").getData().toString();//读取resultType属性
String sql = element.getData().toString();//读取SQL语句信息
String sourceId = namespace + "." + id;
//给mappedStatement属性赋值
mappedStatement.setSourceId(sourceId);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
mappedStatement.setNamespace(namespace);
//注册到configuration对象中
configuration.getMappedStatments().put(sourceId, mappedStatement);
}
}
}
4.SqlSession相关类
SqlSession.java接口:
package com.taoxi.mybatis.mysimple.session;
import java.util.List;
public interface SqlSession {
public <T> T selectOne(String statement, Object parameter);
public <E> List<E> selectList(String statement, Object parameter);
public <T> T getMapper(Class<T> type);
}
DefaultSqlSession.java默认实现类:
package com.taoxi.mybatis.mysimple.session;
import com.taoxi.mybatis.mysimple.binding.MapperProxy;
import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.executor.DefaultExecutor;
import com.taoxi.mybatis.mysimple.executor.Executor;
import java.lang.reflect.Proxy;
import java.util.List;
public class DefaultSqlSession implements SqlSession{
private final Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration) {
super();
this.configuration = configuration;
executor = new DefaultExecutor(configuration);
}
public <T> T selectOne(String statement, Object parameter) {
//执行Sql语句获取查询结果
List<Object> selectList = this.selectList(statement, parameter);
//查询结果为空返回null
if (selectList == null || selectList.size()==0) {
return null;
}
if (selectList.size()==1) {
return (T)selectList.get(0);
}else{
throw new RuntimeException("Too Many Results!");
}
}
public <E> List<E> selectList(String statement, Object parameter) {
try {
MappedStatement ms = configuration.getMappedStatments().get(statement);
return executor.query(ms, parameter);
} catch (Exception e) {
throw new RuntimeException("select list error:", e);
}
}
public <T> T getMapper(Class<T> type) {
MapperProxy mapperProxy = new MapperProxy(this);
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mapperProxy);
}
}
MapperProxy.java代理处理类:
package com.taoxi.mybatis.mysimple.binding;
import com.taoxi.mybatis.mysimple.session.SqlSession;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
public class MapperProxy implements InvocationHandler {
private SqlSession session;
public MapperProxy(SqlSession session) {
super();
this.session = session;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Collection.class.isAssignableFrom(method.getReturnType())){
return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),
args==null?null:args[0]);
}else {
return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),
args==null?null:args[0]);
}
}
}
5.Executor执行器相关类
Executor.java执行器接口:
package com.taoxi.mybatis.mysimple.executor;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import java.util.List;
public interface Executor {
public <E> List<E> query(MappedStatement ms, Object parameter) throws Exception;
}
DefaultExecutor.java执行器默认实现类:
package com.taoxi.mybatis.mysimple.executor;
import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.util.ReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class DefaultExecutor implements Executor{
private static Logger logger = LoggerFactory.getLogger(DefaultExecutor.class);
private final Configuration configuration;
public DefaultExecutor(Configuration configuration) {
super();
this.configuration = configuration;
}
public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException{
ArrayList<E> ret = new ArrayList<E>();//定义返回结果集
try {
Class.forName(configuration.getJdbcDriver());//加载驱动程序
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//获取连接,从Configuration中获取数据库信息
connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUsername(),
configuration.getJdbcPassword());
//创建prepareStatement,从MappedStatement中获取sql语句
preparedStatement = connection.prepareStatement(ms.getSql());
//处理sql语句中的占位符
parameterize(preparedStatement, parameter);
//执行查询操作获取resultSet
resultSet = preparedStatement.executeQuery();
//将结果集通过反射技术,填充到list中
handlerResultSet(resultSet, ret, ms.getResultType());
} catch (SQLException e) {
throw e;
}finally {
try {
resultSet.close();
preparedStatement.close();
connection.close();
} catch (SQLException e) {
throw e;
}
}
return ret;
}
//对prepareStatement中的占位符进行处理
private void parameterize(PreparedStatement preparedStatement, Object parameter) throws SQLException {
if (parameter instanceof Integer) {
preparedStatement.setInt(1,((Integer) parameter).intValue());
} else if (parameter instanceof Long) {
preparedStatement.setLong(1, ((Long) parameter).longValue());
} else if (parameter instanceof String) {
preparedStatement.setString(1, (String)parameter);
}
}
//读取resultset中的数据,并转换成目标对象
private <E> void handlerResultSet(ResultSet resultSet, List<E> ret, String className) {
Class<E> clazz = null;
try {
//通过反射获取类对象
clazz = (Class<E>)Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
while (resultSet.next()) {
Object entity = clazz.newInstance();
//使用反射工具将resultSet中的数据填充到entity中
ReflectionUtil.setPropToBeanFromResult(entity, resultSet);
//对象加入返回集合中
ret.add((E)entity);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ReflectionUti.java反射工具类:
package com.taoxi.mybatis.mysimple.util;
import java.lang.reflect.Field;
import java.sql.ResultSet;
public class ReflectionUtil {
public static void setPropToBean(Object bean, String propName, Object value) {
Field field;
try {
field = field = bean.getClass().getDeclaredField(propName);
field.setAccessible(true);
field.set(bean, value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
public static void setPropToBeanFromResult(Object entity, ResultSet resultSet) throws Exception {
Field[] declaredFields = entity.getClass().getDeclaredFields();
for (int i = 0;i<declaredFields.length;i++) {
if (declaredFields[i].getType().getSimpleName().equals("String")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName()));
} else if (declaredFields[i].getType().getSimpleName().equals("Integer")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName()));
} else if (declaredFields[i].getType().getSimpleName().equals("Long")){
setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName()));
}
}
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Class<?> clazz = Class.forName("com.taoxi.mybatis.mysimple.entity.Dept");
Object dept = clazz.newInstance();
ReflectionUtil.setPropToBean(dept, "dName", "zhongguo");
System.out.println(dept);
}
}
源码实现工程地址:https://github.com/taoxibj/study/tree/master/mysimple
DeptMapper