手写spring+springmvc+mybatis框架篇【Mybatis】

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
云数据库 RDS MySQL Serverless,价值2615元额度,1个月
简介: 手写spring+springmvc+mybatis框架篇【Mybatis】

整合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


手写板大致思路如下:


image.png


这里的Myconfiguration和我的JDBCUtils类似。


实现思路:


首先用XmlBuilderMapper类读取mapper.xml(我是在initBean中指定要读取的配置文件,并没有写成在xml中配置动态的读取xml。)文件,获取MapperInfo对象保存信息。


用户用MysqlSessiongetMapper方法,返回一个代理对象,用这个代理对象来执行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


推荐大而全的【后端技术精选】


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
8月前
|
缓存 Java 数据库连接
Mybatis 框架使用指南(进阶)3
Mybatis 框架使用指南(进阶)3
|
8月前
|
存储 SQL Java
Mybatis 框架使用指南(进阶)2
Mybatis 框架使用指南(进阶)2
|
29天前
|
Java 数据库连接 Spring
黄金搭档:Spring与MyBatis完美整合的终极指南
黄金搭档:Spring与MyBatis完美整合的终极指南
23 0
黄金搭档:Spring与MyBatis完美整合的终极指南
|
10月前
|
XML 缓存 安全
六.吃透Mybatis源码-面试官问我Spring是怎么整合Mybatis的
我们在项目中都是使用Spring整合Mybatis进行数据操作,而不会直接使用SqlSession去操作数据库,因为这样操作会显得特别的麻烦。Spring整合Mybatis之后,Spring对Mybatis的核心进行了封装和适配,让我们用起来更加简单。本篇文章将带你了解Spring整合Mybatis的核心原理,从此再也不用担心面试官问我“Spring是如何整合Mybatis的?”
|
10月前
|
XML Java 数据库连接
深入理解Mybatis-Spring工作原理
深入理解Mybatis-Spring工作原理
111 0
QGS
|
XML SQL Java
手牵手入门Spring6整合Mybatis3.5
Spring是Java EE编程领域的一个轻量级开源框架,该框架由一个叫Rod Johnson的程序员在 2002 年最早提出并随后创建,是为了解决企业级编程开发中的复杂性,实现敏捷开发的应用型框架 。
QGS
54 0
|
XML SQL 安全
|
存储 XML SQL
MyBatis 与 Spring 整合原理分析
前言 我们常常将 Spring 与 MyBatis 结合在一起使用,由于篇幅问题,上篇《MyBatis 快速整合 Spring》仅介绍了将 MyBatis 整合到 Spring 的方式,这篇在上篇的基础上总结出几个问题,并尝试通过分析其底层源码进行回答。
162 0
MyBatis 与 Spring 整合原理分析
|
设计模式 SQL 安全
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
MyBatis-整合Spring的原理分析
|
SQL Java 关系型数据库
深入浅出 Spring Boot - 数据访问之 MyBatis
深入浅出 Spring Boot - 数据访问之 MyBatis
102 0
深入浅出 Spring Boot - 数据访问之 MyBatis