一、簡介
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