自己實現Mybatis底層機制-02 7.任務階段4&5 階段4任務:開發Mapper介面和Mapper.xml 階段5任務:開發和Mapper介面相映射的MapperBean (1)Mapper介面 package com.li.mapper; import com.li.entity.Monst ...
自己實現Mybatis底層機制-02
7.任務階段4&5
階段4任務:開發Mapper介面和Mapper.xml
階段5任務:開發和Mapper介面相映射的MapperBean
(1)Mapper介面
package com.li.mapper;
import com.li.entity.Monster;
/**
* @author 李
* @version 1.0
* MonsterMapper:聲明對資料庫的crud方法
*/
public interface MonsterMapper {
//查詢方法
public Monster getMonsterById(Integer id);
}
(2)Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.li.mapper.MonsterMapper">
<!--實現配置介面方法getMonsterById-->
<select id="getMonsterById" resultType="com.li.entity.Monster">
select * from monster where id = ?
</select>
</mapper>
(3)Function.java,用於記錄Mapper.xml文件實現的方法信息
package com.li.limybatis.config;
import lombok.Getter;
import lombok.Setter;
/**
* @author 李
* @version 1.0
* Function:記錄對應 Mapper.xml的方法信息
*/
@Getter
@Setter
@ToString
public class Function {
private String sqlType;//sql類型,如select,update,insert,delete
private String funcName;//方法名
private String sql;//執行的sql語句
private Object resultType;//返回類型
private String parameterType;//參數類型
}
(4)MapperBean.java,作用是讀取Mapper介面對應的Mapper.xml,將該xml文件方法信息封裝到MapperBean中。
package com.li.limybatis.config;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
/**
* @author 李
* @version 1.0
* MapperBean:將我們的Mapper信息,進行封裝
*/
@Setter
@Getter
@ToString
public class MapperBean {
private String interfaceName;//介面名
//介面下的所有方法
public List<Function> functions;
}
8.任務階段6
階段6任務:在MyConfiguration中讀取xxMapper.xml,能夠創建MapperBean對象
(1)修改 MyConfiguration.java,添加 readMapper() 方法
/**
* 讀取xxMapper.xml,創建MapperBean對象
* @param path xml的路徑+文件名,從類的載入路徑開始計算,若xml文件放在resource目錄下,直接傳入文件名即可
* @return 返回MapperBean對象
*/
public MapperBean readMapper(String path) {
MapperBean mapperBean = new MapperBean();
try {
//獲取到mapper.xml文件對應的InputStream
InputStream stream = loader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
//獲取到xml文件對應的document
Document document = reader.read(stream);
//得到xml的根節點
Element root = document.getRootElement();
//獲取到 namespace
String namespace = root.attributeValue("namespace").trim();
//設置mapperBean的屬性interfaceName
mapperBean.setInterfaceName(namespace);
//遍歷獲取root的子節點-生成 Function
Iterator rootIterator = root.elementIterator();
//保存介面下的所有方法信息
List<Function> list = new ArrayList<>();
while (rootIterator.hasNext()) {
//取出一個子元素
/**
* <select id="getMonsterById" resultType="com.li.entity.Monster">
* select * from monster where id = ?
* </select>
*/
Element e = (Element) rootIterator.next();
Function function = new Function();
String sqlType = e.getName().trim();
String funcName = e.attributeValue("id").trim();
//這裡的resultType是返回類型的全路徑-全類名
String resultType = e.attributeValue("resultType").trim();
String sql = e.getText().trim();
//將信息封裝到 function對象中
function.setSql(sql);
function.setFuncName(funcName);
function.setSqlType(sqlType);
//這裡的function.resultType應該為Object類型
//因此使用反射生成對象,再放入function中
Object instance = Class.forName(resultType).newInstance();
function.setResultType(instance);
//將封裝好的function對象放到list中
list.add(function);
}
mapperBean.setFunctions(list);
} catch (Exception e) {
e.printStackTrace();
}
return mapperBean;
}
(2)測試
@Test
public void readMapper() {
MyConfiguration myConfiguration = new MyConfiguration();
MapperBean mapperBean = myConfiguration.readMapper("MonsterMapper.xml");
System.out.println("mapperBean=" + mapperBean);
}
測試結果:
mapperBean=MapperBean(interfaceName=com.li.mapper.MonsterMapper, functions=[Function(sqlType=select, funcName=getMonsterById, sql=select * from monster where id = ?, resultType=Monster(id=null, age=null, name=null, email=null, birthday=null, salary=0.0, gender=null), parameterType=null)])
9.任務階段7
階段7任務:實現動態代理Mapper的方法-動態代理生成Mapper對象,調用MyExecutor方法
(1)MyMapperProxy.java
package com.li.limybatis.sqlsession;
import com.li.limybatis.config.Function;
import com.li.limybatis.config.MapperBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
/**
* @author 李
* @version 1.0
* MyMapperProxy:動態代理生成 Mapper對象,調用 MyExecutor方法
*/
public class MyMapperProxy implements InvocationHandler {
private MySqlSession mySqlSession;
private String mapperFile;
private MyConfiguration myConfiguration;
//構造器
public MyMapperProxy(MySqlSession mySqlSession, MyConfiguration myConfiguration, Class clazz) {
this.mySqlSession = mySqlSession;
this.myConfiguration = myConfiguration;
this.mapperFile = clazz.getSimpleName() + ".xml";
}
//當執行Mapper介面的代理對象方法時,會執行到invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = myConfiguration.readMapper(this.mapperFile);
//判斷是否是xml文件對應的介面
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName()))
{
//通過method拿到執行的方法所在的介面的名稱,與MapperBean存放的介面名比較
return null;
}
//取出MapperBean的functions
List<Function> functions = mapperBean.getFunctions();
//判斷當前mapperBean解析對應的XML文件後,有方法
if (null != functions && 0 != functions.size()) {
for (Function function : functions) {
//如果當前要執行的方法和function.getFuncName()一樣
//說明我們可以從當前遍歷的function對象中,取出相應的信息sql,並執行方法
if (method.getName().equals(function.getFuncName())) {
//如果當前function要執行的SqlType是select,就去執行selectOne
/*
* 說明:
* 1.如果要執行的方法是select,就對應執行selectOne
* 因為我們在MySqlSession只寫了一個方法(selectOne)
* 2.實際上原生的MySqlSession中應該有很多的方法,只是這裡簡化了,
* 實際上應該根據不同的匹配情況調用不同的方法,並且還需要進行參數解析處理,
* 還有比較複雜的字元串處理,拼接sql,處理返回類型等工作
* 3.因為這裡主要想實現mybatis生成mapper動態代理對象,調用方法的機制,所以簡化
*/
if ("select".equalsIgnoreCase(function.getSqlType())) {
return mySqlSession
.selectOne(function.getSql(), String.valueOf(args[0]));
}
}
}
}
return null;
}
}
(2)修改MySqlSession.java,添加方法,返回動態代理對象
/**
* 1.回 mapper的動態代理對象
* 2.這裡的 clazz到時傳入的類似 MonsterMapper.class
* 3.返回的就是 MonsterMapper 介面的代理對象
* 4.當執行介面方法時(通過代理對象調用),
* 根據動態代理機制會執行到MyMapperProxy的invoke()方法
* @param clazz
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> clazz) {
//返回動態代理對象
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz},
new MyMapperProxy(this, myConfiguration, clazz));
}
(3)創建 MySessionFactory.java
package com.li.limybatis.sqlsession;
/**
* @author 李
* @version 1.0
* MySessionFactory-會話工廠-返回會話SqlSession
*/
public class MySessionFactory {
public static MySqlSession openSession() {
return new MySqlSession();
}
}
(4)測試
@Test
public void openSession() {
MySqlSession mySqlSession = MySessionFactory.openSession();
MonsterMapper mapper = mySqlSession.getMapper(MonsterMapper.class);
System.out.println("mapper的運行類型=" + mapper.getClass());
Monster monster = mapper.getMonsterById(1);
System.out.println("monster--" + monster);
}