手写spring+springmvc+mybatis框架篇【Mybatis】
作者:小亮,原创投稿
个人主页:https://blog.csdn.net/qq_27631217
知音专栏
整合Mybatis是本项目中的一个难点。
实现功能:
1 动态绑定用户输入参数
2 Mybatis的resultType动态绑定返回实体类。
3 在spring中的接口注入
4 xml版本的mapper注入。
关于Mybatis的优秀文章给大家推荐两个:
1、手写简化版mybatis
https://my.oschina.net/liughDevelop/blog/1631006
2、Mybatis源码解读-设计模式总结
http://www.crazyant.net/2022.html
手写板大致思路如下:
https://my.oschina.net/liughDevelop/blog/1631006
这里的Myconfiguration和我的JDBCUtils类似。
实现思路:
首先用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
*/
4j
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
*/
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;
}
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
*/
4j
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
推荐大而全的【后端技术精选】