Mybatis执行流程学习之手写mybatis雏形

theMine 2021-01-22 15:57:27
学习 mybatis 流程 执行 之手


Mybatis是目前开发中最常用的一款基于ORM思想的半自动持久层框架,平时我们都仅仅停留在使用阶段,对mybatis是怎样运行的并不清楚,今天抽空找到一些资料自学了一波,自己写了一个mybatis的雏形,在此对学习过程做一个记录
首先,我们新建一个提供mybatis框架功能的工程IMybatis,这个工程中主要完成mybatis整个初始化和执行过程的功能开发。

 

该工程中用到的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.my</groupId>
<artifactId>IMybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compile.encoding>UTF-8</maven.compile.encoding>
<java.version>1.8</java.version>
<maven.compile.source>1.8</maven.compile.source>
<maven.compile.target>1.8</maven.compile.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.26</version>
</dependency>
</dependencies>
</project>

 

我们在完成上面第一步中框架的编写后会进行打包发布到本地仓库,再新建一个测试工程IMybatis-test,这个工程的pom文件中会引入IMybatis工程的依赖,完成测试

 

 

 该工程的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.my</groupId>
<artifactId>IMybatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.my</groupId>
<artifactId>IMybatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

 

mybatis要完成对数据库的连接,增删改查功能,需要有两个配置文件(这里先不管以注解的形式在mapper接口中编写的sql),一个是配置的数据库的连接信息,我这里是datasourceConfig.xml,

<configuration>
<!-- 数据库配置信息 -->
<dataSource>
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///test?serverTimezone=Asia/Shanghai"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</dataSource>
<mapper resource="UserMapper.xml"></mapper>
</configuration>

 

另一个是提供sql的mapper文件,这里是UserMapper.xml,这两个文件都在IMybatis-test工程中提供

<mapper namespace="com.my.dao.UserMapper">
<!-- sql的唯一表示由 namespace.id 来组成statementId -->
<select id="findAll" resultType="com.my.pojo.User">
select * from user
</select>
<select id="findOne" parameterType="com.my.pojo.User" resultType="com.my.pojo.User">
select * from user where id = #{id}
</select>
<select id="findById" parameterType="java.lang.Long" resultType="com.my.pojo.User">
select * from user where id = #{id}
</select>
<delete id="delete" parameterType="com.my.pojo.User">
delete from user where id = #{id}
</delete>
<delete id="deleteById" parameterType="java.lang.Long">
delete from user where id = #{id}
</delete>
<update id="update" parameterType="com.my.pojo.User">
update user set name = #{name} where id = #{id}
</update>
<insert id="insert" parameterType="com.my.pojo.User">
insert into user(id, name) VALUES(#{id}, #{name})
</insert>
</mapper>

 

下面就要完成IMybatis的功能开发。

一、新建Resource类完成对datasourceConfig.xml文件的加载,将其以流的形式加载到内存中

package com.my.io;
import java.io.InputStream;
/**
* @Description: 配置文件读取
* @Author lzh
* @Date 2020/12/6 16:01
*/
public class Resource {
/**
* 根据传递的路径path去读取到该路径下的配置文件datasourceConfig.xml,并将其读成字节流返回
* @param path
* @return InputStream
*/
public static InputStream getResourceAsStream(String path){
InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
}
}

二、新建SqlSessionFactoryBuilder类,编写build()方法,一步一步构建SqlSessionFactory对象

package com.my.sqlSession;
import com.my.config.XMLConfigBuilder;
import com.my.pojo.Configuration;
import java.io.InputStream;
/**
* @Description: 解析配置文件
* @Author lzh
* @Date 2020/12/6 16:23
*/
public class SqlSessionFactoryBuilder {
/**
* 根据字节流解析出配置文件中各个标签的值,并封装到Configuration中,创建DefaultSqlSessionFactory对象
* @param in
* @return
* @throws Exception
*/
public SqlSessionFactory build(InputStream in) throws Exception {
//创建一个XMLConfigBuilder对象
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
//对配置文件进行解析
Configuration configuration = xmlConfigBuilder.parseConfig(in);
//创建DefaultSqlSessionFactory对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}

三、在build方法中可以看到首先要创建一个XMLConfigBuilder 对象,在该对象中编写了一个parseConfig()方法完成对配置文件的解析,并完成对Configuration 对象的封装,Configuration 是我们这个工程中的一个非常核心的对象,里面存储了对配置文件解析后的结果,同样在真正的Mybatis框架中也有该对象,当然功能比我这里的更强大。

package com.my.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.my.io.Resource;
import com.my.pojo.Configuration;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/**
* @Description:
* @Author lzh
* @Date 2020/12/6 16:26
*/
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
/**
* 解析dataSourceConfig.xml
* @param in
* @return
* @throws Exception
*/
public Configuration parseConfig(InputStream in) throws Exception {
//利用dom4j技术对配置文件进行解析
Document document = new SAXReader().read(in);
Element rootElement = document.getRootElement();
//查找dataSourceConfig.xml中的property标签
List<Element> list = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element element : list) {
//取出每个property标签中的值存到Properties对象中
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.setProperty(name, value);
}
//从Properties中取出各个属性构建一个连接池,来提供对数据库连接的管理,避免资源浪费,提高性能
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(properties.getProperty("driverClass"));
druidDataSource.setUrl(properties.getProperty("url"));
druidDataSource.setUsername(properties.getProperty("username"));
druidDataSource.setPassword(properties.getProperty("password"));
//将连接池对象放入Configuration对象中
 configuration.setDataSource(druidDataSource);
//解析dataSourceConfig.xml中的mapper标签,mapper标签中的resource属性值存放的就是UserMapper.xml的文件位置
List<Element> mapperList = rootElement.selectNodes("//mapper");
for (Element element : mapperList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
//解析UserMapper.xml文件,进一步封装Configuration对象
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parse(resourceAsStream);
}
return configuration;
}
}

上图红色的地方创建了一个XMLMapperBuilder对象,该对象提供了一个parse()方法,就是完成对UserMapper.xml文件的解析,并完成对Configuration封装

package com.my.config;
import com.my.config.eunm.SqlCommandType;
import com.my.pojo.Configuration;
import com.my.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @Description:
* @Author lzh
* @Date 2020/12/6 17:03
*/
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
/**
* 解析UserMapper.xml配置文件中得内容,将每一个标签构建成一个MappedStatement,并赋值到Configuration中
* @param in
* @throws DocumentException
*/
public void parse(InputStream in) throws DocumentException {
Document document = new SAXReader().read(in);
Element rootElement = document.getRootElement();
String namespace = rootElement.attributeValue("namespace");
//解析select标签
List<Element> selectList = rootElement.selectNodes("//select");
this.parseElement(selectList, namespace, SqlCommandType.SELECT);
//解析insert标签
List<Element> insertList = rootElement.selectNodes("//insert");
this.parseElement(insertList, namespace, SqlCommandType.INSERT);
//解析update标签
List<Element> updateList = rootElement.selectNodes("//update");
this.parseElement(updateList, namespace, SqlCommandType.UPDATE);
//解析delete标签
List<Element> deleteList = rootElement.selectNodes("//delete");
this.parseElement(deleteList, namespace, SqlCommandType.DELETE);
}
/**
* 解析mapper.xml文件中增删改查标签
* @param elementList
* @param namespace
* @param sqlCommandType
*/
private void parseElement(List<Element> elementList, String namespace, SqlCommandType sqlCommandType) {
for (Element element : elementList) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setSqlCommandType(sqlCommandType);
mappedStatement.setId(id);
mappedStatement.setParameterType(parameterType);
mappedStatement.setResultType(resultType);
mappedStatement.setSql(sql);
configuration.getMappedStatementMap().put(namespace + "." + id, mappedStatement);
}
}
}

该类中用到的SqlCommandType是一个枚举类,就是列举的UserMapper.xml中的几个主要的sql标签类型增删改查,也是借鉴的原Mybatis框架中的写法

package com.my.config.eunm;
public enum SqlCommandType {
INSERT,
UPDATE,
DELETE,
SELECT;
private SqlCommandType(){
}
}

还有一个MappedStatement对象,这个对象中就是封装的每一个insert、update、delete、select标签中的信息(包括每个标签中的id、parameterType、resutType、sql语句等等),每个标签就是一个MappedStatement对象

package com.my.pojo;
import com.my.config.eunm.SqlCommandType;
/**
* @Description:
* @Author lzh
* @Date 2020/12/6 16:17
*/
public class MappedStatement {
private SqlCommandType sqlCommandType;
private String id;
private String resultType;
private String parameterType;
private String sql;
public SqlCommandType getSqlCommandType() {
return sqlCommandType;
}
public void setSqlCommandType(SqlCommandType sqlCommandType) {
this.sqlCommandType = sqlCommandType;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}

封装好MappedStatement对象后,再将其放入Configuration对象的mappedStatementMap属性中,该属性就是一个Map集合,key就是UserMapper.xml文件中的namespace的值+ "." +每一个标签的id值(例如我们这里的com.my.dao.UserMapper.findAll),因为一个Mapper接口对应一个Mapper.xml文件,而每个Mapper.xml文件中的namespace的值就是Mapper接口的全限定类名,每个标签的id值就是Mapper接口中对应的方法名,所以通过这个组合key就能和Mapper接口产生关联,当我们在调用Mapper接口中的方法时,就可以通过Mapper接口的全限定类名和调用的方法名在Configuration中的Map集合中找到对应的MappedStatement对象,也就是能拿到需要执行的sql、参数类型、返回值类型等等。

package com.my.pojo;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/** 核心对象
* @Description:
* @Author lzh
* @Date 2020/12/6 16:18
*/
public class Configuration {
/**
* 数据源
*/
private DataSource dataSource;
/**
* key:statementId vlaue:封装好的MappedStatement
*/
private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, MappedStatement> getMappedStatementMap() {
return mappedStatementMap;
}
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
}

到这里我们的Configuration对象就封装完毕。

四、然后我们可以在第二步中的SqlSessionFactoryBuilder类的build()方法中看到,根据Configuration对象构造出了DefaultSqlSessionFactory工厂对象,整个构建DefaultSqlSessionFactory的过程就是一个构建者模式的体现(通过多个小的对象构建出一个大的对象)

package com.my.sqlSession;
public interface SqlSessionFactory {
SqlSession createSqlSession();
}

 

package com.my.sqlSession;
import com.my.pojo.Configuration;
/**
* @Description: SqlSession的工厂对象,用于生产SqlSession
* @Author lzh
* @Date 2020/12/6 17:17
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
/**
* 创建SqlSession会话
* @return
*/
public SqlSession createSqlSession() {
return new DefaultSqlSession(configuration);
}
}

五、利用DefaultSqlSessionFactory工厂对象 的createSqlSession()方法来获取一个SqlSession对象,就是一个我们所说的一个会话对象,该对象也是一个非常重要的对象

package com.my.sqlSession;
import java.util.List;
/**
* @Description: SqlSession
* @Author lzh
* @Date 2020/12/6 17:18
*/
public interface SqlSession {
<E> List<E> selectList(String statementId, Class<?> methodParameterType, Object... param) throws Exception;
<T> T selectOne(String statementId, Class<?> methodParameterType, Object... param) throws Exception;
<T> T getMapper(Class<?> mapperClass);
}
package com.my.sqlSession;
import com.my.config.eunm.SqlCommandType;
import com.my.pojo.Configuration;
import com.my.pojo.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
/**
* @Description: SqlSession会话的实现
* @Author lzh
* @Date 2020/12/6 17:21
*/
public class DefaultSqlSession implements SqlSession, InvocationHandler {
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
/**
* 多条查询
* @param statementId
* @param param
* @param <E>
* @return
* @throws Exception
*/
public <E> List<E> selectList(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
List<Object> query = simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), methodParameterType, param);
return (List<E>) query;
}
/**
* 单条查询
* @param statementId
* @param param
* @param <T>
* @return
* @throws Exception
*/
public <T> T selectOne(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
List<Object> objects = selectList(statementId, methodParameterType, param);
if (objects.size() == 1){
return (T) objects.get(0);
}else if (objects.size() <= 0){
return null;
}else {
throw new RuntimeException("Result more than one");
}
}
/**
* 新增
* @param statementId
* @param param
* @return
*/
public int insert(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
return update(statementId, methodParameterType, param);
}
/**
* 修改
* @param statementId
* @param param
* @return
*/
public int update(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
SimpleExecutor simpleExecutor = new SimpleExecutor();
return simpleExecutor.update(configuration, configuration.getMappedStatementMap().get(statementId), methodParameterType, param);
}
/**
* 删除
* @param statementId
* @param param
* @return
*/
public int delete(String statementId, Class<?> methodParameterType, Object... param) throws Exception {
return update(statementId, methodParameterType, param);
}
/**
* 创建代理对象
* @param mapperClass
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<?> mapperClass) {
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, this);
return (T) proxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?> methodParameterType = null;
if (null != method.getParameterTypes() && 0 < method.getParameterTypes().length){
methodParameterType = method.getParameterTypes()[0];
}
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if (SqlCommandType.SELECT == sqlCommandType){
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
return selectList(statementId, methodParameterType, args);
}
return selectOne(statementId, methodParameterType, args);
}else if (SqlCommandType.INSERT == sqlCommandType){
return insert(statementId, methodParameterType, args);
}else if (SqlCommandType.UPDATE == sqlCommandType){
return update(statementId, methodParameterType, args);
}else if (SqlCommandType.DELETE == sqlCommandType){
return delete(statementId, methodParameterType, args);
}else {
throw new RuntimeException("Unknown SqlCommandType For: " + sqlCommandType);
}
}
}

六、在SqlSession中,就提供了增删改查方法,用于操作数据库,我们另外还可以看到一个getMapper()方法,该方法需要传入一个Class参数,那么这个方法是干什么的呢?我们有过开发经验的朋友都知道很早以前在用spring+Mybatis框架开发的时候,对每一个Dao(也就是这里我们说的Mapper层)层的接口都会去写一个实现类DaoImpl,在实现类中通过JDBC来完成对数据库的操作,这样的编码方式会存在很多问题,比如:

  1. 每次执行一个方法都会区获取一个Connection对象,也就是创建一个数据库连接
  2. sql语句和业务代码融合在一起,增加代码耦合度,也不便于维护
  3. 封装返回结果麻烦,不够智能

所以针对第一个问题我们引入了连接池来管理数据库连接,每次都是从池子里面去获取,减少了资源消耗,提高了效率,针对后面两个问题,首先Mybatis去调了DaoImpl实现类,其次,通过Java反射技术完成对参数的赋值和对返回结果的动态封装(这一步后面代码中会有体现)。那么去掉了DaoImpl实现类,Dao接口中需要做的事总是需要有人来做的,否则无法完成对数据库的操作,因此Mybatis中会为每个Dao接口(也就是这里我们说的Mapper接口)生成一个代理对象,去完成之前DaoImpl做的事。这里的getMapper()方法就是去获取传入参数对象的代理对象,我们这里就是获取UserMapper接口的代理对象,创建代理对象时我们可以看到在getMapper()方法中的Proxy.newProxyInstance(),需要传递三个参数,第一个参数就是一个类加载器,第二参数就是我们需要为哪个对象产生代理对象,也就是getMapper()方法的参数,重点是第三个参数,需要传入一个InvocationHandler对象,而InvocationHandler是一个接口,我们这里的DefaultSqlSession实现了这个接口,所以第三个参数传的就是this,该类本身。实现了InvocationHandler接口就需要重写invoke()方法,而我们知道调用代理对象的方法,都会走到该invoke()方法中,所以我们这里调用UserMapper接口中的方法时,同样会执行这里的invoke方法,这样在invoke()方法中就可以完成我们以前在DaoImpl中需要完成的事。

七、下面我们具体来看下invoke()中做了什么,首先看下三个参数,第一个就是一个代理对象,第二个就是我们调用的方法Method,第三个就是调用方法时传入的参数args,那么我们根据Method对象就可以获取到该方法的全限定类名和该方法的名称,从而组合一个statemenId,而我们在上面第五步中通过createSqlSession()方法创建SqlSession对象时,是将我们封装的Configuration对象传入了,所有这里我们可以通过statementId在Configuration对象的mappedStatementMap这个Map集合中找到我们封装的MappedStatement对象,通过MappedStatement对象中的SqlCommandType的值我们可以判断出我们需要执行增删改查中的哪个方法,从而去调用该类具体的增删改查方法,在执行具体方法时,我们这里并没有在SqlSession对象中直接去操作数据库,而是将这些crud操作交给了一个SimpleExecutor执行器去完成真正对数据库的操作。

package com.my.sqlSession;
import com.my.pojo.Configuration;
import com.my.pojo.MappedStatement;
import java.util.List;
public interface Executor {
<E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object... param) throws Exception;
int update(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception;
}
package com.my.sqlSession;
import com.my.config.BoundSql;
import com.my.pojo.Configuration;
import com.my.pojo.MappedStatement;
import com.my.utils.GenericTokenParser;
import com.my.utils.ParameterMapping;
import com.my.utils.ParameterMappingTokenHandler;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @Description: Executor执行器
* @Author lzh
* @Date 2020/12/6 17:32
*/
public class SimpleExecutor implements Executor {
/**
* 真正的查询方法,负责完成JDBC的操作
* @param configuration
* @param mappedStatement
* @param param
* @param <E>
* @return
* @throws Exception
*/
public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement,Class<?> methodParameterType, Object... param) throws Exception {
PreparedStatement preparedStatement = this.createPreparedStatement(configuration, mappedStatement, methodParameterType, param);
//执行sql,返回结果集ResultSet
ResultSet resultSet = preparedStatement.executeQuery();
//对结果封装,映射出对应得返回类型
String resultType = mappedStatement.getResultType();
Class<?> resultClass = getClassType(resultType);
List<Object> result = new ArrayList<Object>();
while (resultSet.next()){
Object o = resultClass.newInstance();
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnName(i);
Object object = resultSet.getObject(columnName);
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o, object);
}
result.add(o);
}
return (List<E>) result;
}
@Override
public int update(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception {
PreparedStatement preparedStatement = this.createPreparedStatement(configuration, mappedStatement, methodParameterType, param);
preparedStatement.execute();
int row = preparedStatement.getUpdateCount();
return row;
}
/**
* 获取PreparedStatement对象
* @param configuration
* @param mappedStatement
* @param param
* @return
* @throws Exception
*/
private PreparedStatement createPreparedStatement(Configuration configuration, MappedStatement mappedStatement, Class<?> methodParameterType, Object[] param) throws Exception {
//获取数据库连接
Connection connection = configuration.getDataSource().getConnection();
//从MappedStatement中取出sql,现在的sql就是userMapper.xml中我们编写的带有#{}的sql语句
String sql = mappedStatement.getSql();
//处理sql语句,解析出sql语句中#{}中的属性值,并将#{}替换为?,封装到BoundSql对象中
BoundSql boundSql = getBoundSql(sql);
//获取PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
//如果有参数,给参数赋值
String parameterType = mappedStatement.getParameterType();
if (null != parameterType){
Class<?> parameterClass = getClassType(parameterType);
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
if (isObject(methodParameterType)){
preparedStatement.setObject(i + 1, param[0]);
}else {
ParameterMapping parameterMapping = parameterMappingList.get(i);
//该content就是我们sql中#{id}中的id
String content = parameterMapping.getContent();
//利用反射在parameterClass中取出content这个属性的值,并完成sql的赋值
Field declaredField = parameterClass.getDeclaredField(content);
declaredField.setAccessible(true);
Object o = declaredField.get(param[0]);
preparedStatement.setObject(i + 1, o);
}
}
}
return preparedStatement;
}
/**
* 根据参数类型或者返回值类型获取该对象的Class
* @param parameterType
* @return
* @throws ClassNotFoundException
*/
private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
Class<?> aClass = Class.forName(parameterType);
return aClass;
}
/**
* 解析sql,封装成BoundSql
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
//解析出来的sql
String parseSql = genericTokenParser.parse(sql);
//解析出来的id和name
List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
private Boolean isObject(Class<?> methodParameterType){
if (null == methodParameterType){
return false;
}
if (Integer.class.getName().equals(methodParameterType.getName())
|| Long.class.getName().equals(methodParameterType.getName())
|| String.class.getName().equals(methodParameterType.getName())
|| Double.class.getName().equals(methodParameterType.getName())
|| Float.class.getName().equals(methodParameterType.getName())
|| Byte.class.getName().equals(methodParameterType.getName())
|| Short.class.getName().equals(methodParameterType.getName())
|| Character.class.getName().equals(methodParameterType.getName())
|| Boolean.class.getName().equals(methodParameterType.getName())
|| Date.class.getName().equals(methodParameterType.getName())){
return true;
}
return false;
}
}

八、在这个执行器中就是真正完成对数据库的操作,从连接池中获取一个Connection连接,从MappedStatement中获取到要执行的sql,这里注意这时候的sql还是从UserMapper.xml中解析出来的sql(select * from user where id = #{id}),需要对其进行处理用?替换掉#{},并记录大括号中的参数,因为JDBC中参数的占位符是?,所以这里的getBoundSql()方法就是在做这些事情,最终封装成一个BoundSql对象。

package com.my.config;
import com.my.utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: sql
* @Author lzh
* @Date 2020/12/6 17:42
*/
public class BoundSql {
private String sqlText;
private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>();
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText() {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}

 

这其中用到的几个工具类我也贴在这里,这也是从Mybatis源码中拿到的,就是对sql解析处理,这里不用过大关注。

package com.my.utils;
/**
* @author lzh
*/
public interface TokenHandler {
String handleToken(String content);
}

 

package com.my.utils;
import java.util.ArrayList;
import java.util.List;
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是参数名称 #{id} #{username}
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
package com.my.utils;
/**
* @author Clinton Begin
*/
public class GenericTokenParser {
private final String openToken; //开始标记
private final String closeToken; //结束标记
private final TokenHandler handler; //标记处理器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* 解析${}和#{}
* @param text
* @return
* 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
* 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
*/
public String parse(String text) {
// 验证参数问题,如果是null,就返回空字符串。
if (text == null || text.isEmpty()) {
return "";
}
// 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
// text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
// 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression变量,避免空指针或者老数据干扰。
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {////存在结束标记时
if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {//不存在转义字符,即需要作为参数进行处理
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//首先根据参数的key(即expression)进行参数处理,返回?作为占位符
 builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
package com.my.utils;
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

解析完sql后就是创建PreparedStatement对象,并通过MappedStatement对象中记录的参数类型,利用java反射技术进行赋值,然后执行sql,最后再通过MappedStatement对象中记录的返回值类型对结果进行封装,同样是用java反射,这样就实现了参数的动态赋值和结果的动态封装。这就是整个Mybatis的执行流程,到这里也就完成了IMybatis框架的编写,下面我们进行测试。

九、将IMybatis打包到本地仓库,在IMybatis-test中引入依赖,编写一个用户Pojo类、UserMapper接口和一个测试类,UserMapper.xml在上面已经提供

package com.my.pojo;
/**
* @Description:
* @Author lzh
* @Date 2020/12/6 15:57
*/
public class User {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package com.my.dao;
import com.my.pojo.User;
import java.util.List;
public interface UserMapper {
/**
* 查询所有
* @return
*/
List<User> findAll() ;
/**
* 查询单条
* @param user
* @return
*/
User findOne(User user);
/**
* 根据id查询单条
* @param id
* @return
*/
User findById(Long id);
/**
* 根据id删除用户
* @param id
* @return
*/
int deleteById(Long id);
/**
* 删除用户
* @param user
* @return
*/
int delete(User user);
/**
* 新增用户
* @param user
* @return
*/
int insert(User user);
/**
* 修改用户
* @param user
* @return
*/
int update(User user);
}
package com.my.test;
import com.my.dao.UserMapper;
import com.my.io.Resource;
import com.my.pojo.User;
import com.my.sqlSession.SqlSession;
import com.my.sqlSession.SqlSessionFactory;
import com.my.sqlSession.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @Description:
* @Author lzh
* @Date 2020/12/6 16:05
*/
public class IMybatisTest {
private SqlSession sqlSession;
@Before
public void before() throws Exception {
InputStream resourceAsStream = Resource.getResourceAsStream("dataSourceConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
sqlSession = sqlSessionFactory.createSqlSession();
}
@Test
public void test1() {
User user = new User();
user.setId(2L);
user.setName("王五");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.findOne(user);
System.out.println(user1);
}
@Test
public void test2() {
User user = new User();
user.setId(1L);
user.setName("王五");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> all = mapper.findAll();
for (User user1 : all) {
System.out.println(user1);
}
}
@Test
public void test3() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1= mapper.findById(2L);
System.out.println(user1);
}
@Test
public void test4() {
User user = new User();
user.setId(3L);
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.delete(user);
System.out.println(row);
}
@Test
public void test5() {
User user = new User();
user.setId(3L);
user.setName("王五");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.update(user);
System.out.println(row);
}
@Test
public void test6() {
User user = new User();
user.setId(3L);
user.setName("张三");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int row = mapper.insert(user);
System.out.println(row);
}
}

这里就不把全部的测试结果贴出来了,贴一个看下效果就行,可以看到控制台正常输出,说明我们自己写的IMybatis没问题,可以成功执行。

 

总结:我们可以看到最后仍然是通过JDBC完成的数据库操作。所以到这里我们可以知道Mybatis最终仍然是调用的JDBC去操作数据库,它只不过在执行JDBC之前还多去做了这一系列解析配置文件,封装各个对象等等这些操作,Mybatis就是对JDBC的包装。

 

版权声明
本文为[theMine]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/theMine/p/14313786.html

  1. 【计算机网络 12(1),尚学堂马士兵Java视频教程
  2. 【程序猿历程,史上最全的Java面试题集锦在这里
  3. 【程序猿历程(1),Javaweb视频教程百度云
  4. Notes on MySQL 45 lectures (1-7)
  5. [computer network 12 (1), Shang Xuetang Ma soldier java video tutorial
  6. The most complete collection of Java interview questions in history is here
  7. [process of program ape (1), JavaWeb video tutorial, baidu cloud
  8. Notes on MySQL 45 lectures (1-7)
  9. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  10. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  11. 精进 Spring Boot 03:Spring Boot 的配置文件和配置管理,以及用三种方式读取配置文件
  12. Refined spring boot 03: spring boot configuration files and configuration management, and reading configuration files in three ways
  13. 【递归,Java传智播客笔记
  14. [recursion, Java intelligence podcast notes
  15. [adhere to painting for 386 days] the beginning of spring of 24 solar terms
  16. K8S系列第八篇(Service、EndPoints以及高可用kubeadm部署)
  17. K8s Series Part 8 (service, endpoints and high availability kubeadm deployment)
  18. 【重识 HTML (3),350道Java面试真题分享
  19. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  20. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  21. [re recognize HTML (3) and share 350 real Java interview questions
  22. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  23. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  24. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  25. RPC 1: how to develop RPC framework from scratch
  26. 造轮子系列之RPC 1:如何从零开始开发RPC框架
  27. RPC 1: how to develop RPC framework from scratch
  28. 一次性捋清楚吧,对乱糟糟的,Spring事务扩展机制
  29. 一文彻底弄懂如何选择抽象类还是接口,连续四年百度Java岗必问面试题
  30. Redis常用命令
  31. 一双拖鞋引发的血案,狂神说Java系列笔记
  32. 一、mysql基础安装
  33. 一位程序员的独白:尽管我一生坎坷,Java框架面试基础
  34. Clear it all at once. For the messy, spring transaction extension mechanism
  35. A thorough understanding of how to choose abstract classes or interfaces, baidu Java post must ask interview questions for four consecutive years
  36. Redis common commands
  37. A pair of slippers triggered the murder, crazy God said java series notes
  38. 1、 MySQL basic installation
  39. Monologue of a programmer: despite my ups and downs in my life, Java framework is the foundation of interview
  40. 【大厂面试】三面三问Spring循环依赖,请一定要把这篇看完(建议收藏)
  41. 一线互联网企业中,springboot入门项目
  42. 一篇文带你入门SSM框架Spring开发,帮你快速拿Offer
  43. 【面试资料】Java全集、微服务、大数据、数据结构与算法、机器学习知识最全总结,283页pdf
  44. 【leetcode刷题】24.数组中重复的数字——Java版
  45. 【leetcode刷题】23.对称二叉树——Java版
  46. 【leetcode刷题】22.二叉树的中序遍历——Java版
  47. 【leetcode刷题】21.三数之和——Java版
  48. 【leetcode刷题】20.最长回文子串——Java版
  49. 【leetcode刷题】19.回文链表——Java版
  50. 【leetcode刷题】18.反转链表——Java版
  51. 【leetcode刷题】17.相交链表——Java&python版
  52. 【leetcode刷题】16.环形链表——Java版
  53. 【leetcode刷题】15.汉明距离——Java版
  54. 【leetcode刷题】14.找到所有数组中消失的数字——Java版
  55. 【leetcode刷题】13.比特位计数——Java版
  56. oracle控制用户权限命令
  57. 三年Java开发,继阿里,鲁班二期Java架构师
  58. Oracle必须要启动的服务
  59. 万字长文!深入剖析HashMap,Java基础笔试题大全带答案
  60. 一问Kafka就心慌?我却凭着这份,图灵学院vip课程百度云