手寫spring+springmvc+mybatis框架篇——Mybatis
整合Mybatis是本專案中的一個難點。
實現功能:
1 動態繫結使用者輸入引數
2 Mybatis的resultType動態繫結返回實體類。
3 在spring中的介面注入
4 xml版本的mapper注入。
關於Mybatis的優秀文章給大家推薦兩個
1 手寫簡化版mybatis https://my.oschina.net/liughDevelop/blog/1631006
http://www.crazyant.net/2022.html
手寫板大致思路如下,圖片引用自https://my.oschina.net/liughDevelop/blog/1631006
這裡的Myconfiguration
實現思路: 首先用XmlBuilderMapper類讀取mapper.xml(我是在initBean中指定要讀取的配置檔案,並沒有寫成在xml中配置動態的讀取xml。)檔案,獲取MapperInfo物件儲存資訊。使用者用MysqlSession的getMapper方法,返回一個代理物件,用這個代理物件來執行MySqlSession定義的selectOne方法來查詢,Mybaits中的SqlSession中定義了大量的方法,我這裡簡化只有一個selectOne方法。然後這個方法呼叫executor執行器來解析sql,傳入引數,執行資料庫操作。最後返回結果。將返回結果封裝成物件。
動態代理一般有兩種,一種是jdk一種是cglib動態代理,本專案採用是jdk動態代理實現。
XmlBuilderMapper.class
package spring.mybatis; import lombok.extern.slf4j.Slf4j; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import spring.constants.Constants; import java.io.File; import java.util.ArrayList; import java.util.List; /** * @ClassName XmlBuilder * @Description * @Data 2018/7/8 * @Author xiao liang */ @Slf4j public class XmlBuilderMapper { public List<MapperInfo> buildMapper(String xmlMapperPath){ //例項化mapperInfo的連結串列,每一條sql對應一個MapperInfo物件 List<MapperInfo> mapperInfoList = new ArrayList<>(); MapperInfo mapperInfo = new MapperInfo(); // 建立saxReader物件 SAXReader reader = new SAXReader(); // 通過read方法讀取一個檔案 轉換成Document物件 Document document = null; String pathName = Constants.PATH + xmlMapperPath; try { document = reader.read(new File(pathName)); } catch (DocumentException e) { log.error("檔案沒有找到,{}", pathName); } //獲取根節點元素 Element node = document.getRootElement(); mapperInfo.setInterfaceName(node.attributeValue("namespace")); //獲取所有的bean List<Element> elementsList = node.elements(); for (Element element : elementsList) { if ("select".equals(element.getName())){ mapperInfo.setMethodName(element.attributeValue("id")); mapperInfo.setResultClassName(element.attributeValue("resultType")); mapperInfo.setSqlContent(element.getText()); } mapperInfoList.add(mapperInfo); } return mapperInfoList; } }
然後介紹一下MapperInfo物件
package spring.mybatis;
import lombok.Data;
/**
* @ClassName MapperInfo
* @Description 用來封裝讀取mapper.xml檔案後的資訊
* @Data 2018/7/8
* @Author xiao liang
*/
@Data
public class MapperInfo {
//namespace名稱空間
private String interfaceName;
//sql內容
private String sqlContent;
//對應的方法名
private String methodName;
//返回值的class名
private String resultClassName;
}
JDBCUtils工具類我在開篇就介紹了,也沒什麼新內容,不貼在這裡了
其實關鍵點就是動態代理和執行器
MySqlSession(動態代理部分)
package spring.mybatis;
import java.lang.reflect.Proxy;
/**
* @ClassName MySqlSession
* @Description
* @Data 2018/7/8
* @Author xiao liang
*/
public class MySqlSession {
public <T> T selectOne(MapperInfo mapperInfo ,Object[] paremeters){
MyExecutor myexecutor = new MyExecutor();
return myexecutor.query(mapperInfo,paremeters);
}
public <T> T getMapper(Class<?> aClass,String mybatisXmlName){
return (T) Proxy.newProxyInstance(aClass.getClassLoader(),new Class[]{aClass},new MyMapperProxy(this,mybatisXmlName));
}
}
MyMapperProxy(jdk動態代理的實現) 代理之後執行的還是sqlSession中的方法package spring.mybatis;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
/**
* @ClassName MyMapperProxy
* @Description
* @Data 2018/7/8
* @Author xiao liang
*/
public class MyMapperProxy implements InvocationHandler {
private MySqlSession mySqlSession;
private String mybatisXmlName;
//mybatisXmlName傳入的要讀取的xml檔名
public MyMapperProxy(MySqlSession mySqlSession , String mybatisXmlName){
this.mySqlSession = mySqlSession;
this.mybatisXmlName = mybatisXmlName;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
XmlBuilderMapper xmlBuilderMapper = new XmlBuilderMapper();
List<MapperInfo> mapperInfoList = xmlBuilderMapper.buildMapper(mybatisXmlName);
//如果存在sql,開始執行方法
if (mapperInfoList != null && mapperInfoList.size() != 0){
for (MapperInfo mapperInfo :
mapperInfoList) {
if (!method.getDeclaringClass().getName().equals(mapperInfo.getInterfaceName())){
return null;
}
if (method.getName().equals(mapperInfo.getMethodName())){
//其實最後執行的mySqlSession中的方法,args是使用者傳遞的引數陣列
return mySqlSession.selectOne(mapperInfo,args);
}
}
}
return null;
}
}
返回到MySqlSession後,就輪到執行器MyExcutor出場了。
MyExcutor:也是一個難點,先說一下程式的邏輯。讀取到mapperinfo物件,獲得定義的sql,返回類的名稱等資訊。用正則解析sql,根據#{},判斷sql中有幾處?,然後將使用者傳遞的引數按照順序依次寫入到sql的?中。最後讀取結果集,用set方法注入到返回的物件中,這樣就實現了返回時候的實體類綁定了。
package spring.mybatis;
import lombok.extern.slf4j.Slf4j;
import spring.Utils.GetMethodName;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @ClassName MyExecutor
* @Description 執行器
* @Data 2018/7/8
* @Author xiao liang
*/
@Slf4j
public class MyExecutor {
public <T> T query(MapperInfo mapperInfo, Object[] paremeters) {
//獲取mapper.xml檔案中的sql語句
String preSql = mapperInfo.getSqlContent();
//正則匹配規則,匹配有幾個#{}
String rgex = "#\\{.*?}";
String sql = null;
String resultClassName = mapperInfo.getResultClassName();
Class<?> aClass = null;
Field[] fields = null;
Method[] methods = null;
Object object = null;
Pattern pattern = Pattern.compile(rgex);
Matcher m = pattern.matcher(preSql);
Connection connection = null;
ResultSet rs = null;
PreparedStatement preparedStatement = null;
//Preparement注入引數的個數
int orderPre = 0;
//每匹配一次,加一
while (m.find()) {
orderPre++;
}
//匹配完成之後,將#{}用?代替,preparement執行sql
sql = m.replaceAll("?");
try {
aClass = Class.forName(resultClassName);
fields = aClass.getDeclaredFields();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
JDBCUtils jdbcUtils = new JDBCUtils();
connection = jdbcUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
//將使用者傳遞過來的引數按照順序依次賦值給prepareStatement的sql
for (int i = 1; i <= orderPre; i++) {
preparedStatement.setObject(i, paremeters[i - 1]);
}
rs = preparedStatement.executeQuery();
object = aClass.newInstance();
while (rs.next()) {
int i = 1;
for (Field field :
fields) {
//遍歷每個屬性,然後將結果集中的資料用set方法注入到返回的物件中
String setMethodNameByField = GetMethodName.getSetMethodNameByField(field.getName());
Method method2 = aClass.getMethod(setMethodNameByField, field.getType());
if (field.getType().getSimpleName().equals("String")) {
method2.invoke(object, rs.getString(i));
} else if (field.getType().getSimpleName().equals("Integer")) {
method2.invoke(object, rs.getInt(i));
}
i++;
}
}
return (T) object;
} catch (SQLException e) {
log.error("sql語句異常,請檢查sql{}", sql);
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
finally {
JDBCUtils.colseResource(connection,preparedStatement,rs);
}
return null;
}
}
我將此專案上傳到了github,需要的童鞋可以自行下載。
https://github.com/836219171/MySSM