Mybatis执行流程学习之手写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来完成对数据库的操作,这样的编码方式会存在很多问题,比如:
- 每次执行一个方法都会区获取一个Connection对象,也就是创建一个数据库连接
- sql语句和业务代码融合在一起,增加代码耦合度,也不便于维护
- 封装返回结果麻烦,不够智能
所以针对第一个问题我们引入了连接池来管理数据库连接,每次都是从池子里面去获取,减少了资源消耗,提高了效率,针对后面两个问题,首先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的包装。
Mybatis执行流程学习之手写mybatis雏形的更多相关文章
- 手写MyBatis流程
MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...
- Mybatis执行流程浅析(附深度文章推荐&面试题集锦)
首先推荐一个简单的Mybatis原理视频教程,可以作为入门教程进行学习:点我 (该教程讲解的是如何手写简易版Mybatis) 执行流程的理解 理解Mybatis的简单流程后自己手写一个,可以解决百分之 ...
- 手写mybatis框架笔记
MyBatis 手写MyBatis流程 架构流程图 封装数据 封装到Configuration中 1.封装全局配置文件,包含数据库连接信息和mappers信息 2.封装*mapper.xml映射文件 ...
- 浅析MyBatis(二):手写一个自己的MyBatis简单框架
在上一篇文章中,我们由一个快速案例剖析了 MyBatis 的整体架构与整体运行流程,在本篇文章中笔者会根据 MyBatis 的运行流程手写一个自定义 MyBatis 简单框架,在实践中加深对 MyBa ...
- 要想精通Mybatis?从手写Mybatis框架开始吧!
1.Mybatis组成 动态SQL Config配置 Mapper配置 2.核心源码分析 Configuration源码解析 SqlSessionFactory源码解析 SqlSession源码解析 ...
- 手写MyBatis ORM框架实践
一.实现手写Mybatis三个难点 1.接口既然不能被实例化?那么我们是怎么实现能够调用的? 2.参数如何和sql绑定 3.返回结果 下面是Mybatis接口 二.Demo实现 1.创建Maven工程 ...
- mybatis 执行流程以及初用错误总结
mappper 配置文件 头文件: 1. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" &q ...
- Spring学习之——手写Spring源码V2.0(实现IOC、D、MVC、AOP)
前言 在上一篇<Spring学习之——手写Spring源码(V1.0)>中,我实现了一个Mini版本的Spring框架,在这几天,博主又看了不少关于Spring源码解析的视频,受益匪浅,也 ...
- Mybatis(一):手写一套持久层框架
作者 : 潘潘 未来半年,有幸与导师们一起学习交流,趁这个机会,把所学所感记录下来. 「封面图」 自毕业以后,自己先创业后上班,浮沉了近8年,内心着实焦躁,虽一直是走科班路线,但在技术道路上却始终没静 ...
随机推荐
- 多个HDFS集群的fs.defaultFS配置一样,造成应用一直连接同一个集群的问题分析
背景 应用需要对两个集群中的同一目录下的HDFS文件个数和文件总大小进行比对,在测试环境中发现,即使两边HDFS目录下的数据不一样,应用日志显示两边始终比对一致,分下下来发现,应用连的一直是同一个集群 ...
- Java后端使用socketio,实现小程序答题pk功能
在使用socket.io跟前端通信过程中,出现了一系列问题,现做下记录. 一.功能需求是,在小程序端,用户可相互邀请,进入房间后进行答题PK.实现方法是,用户点击邀请好友,建立连接,查询当前是否有房间 ...
- ECharts的下载和安装(图文详解)
首先搜索找到ECharts官网,点击进入. 找到下载 进入就看到第三步,就点击在线制作 点击进入之后就自己可以选择里面的形状图,就在线制作.最后生成echarts.min.js 点击下载后就会生成js ...
- numpy的好处
python是很慢的,因为python在执行代码的时候会执行很多复杂的check功能,比如 b=1; a=b/0.5 这个运算看起来很简单,但是在计算机的内部.b要先从一个整数integer转化成一个 ...
- EF Core 三 、 骚操作 (导航属性,内存查询...)
EF Core 高阶操作 本文之前,大家已经阅读了前面的系列文档,对其有了大概的了解 我们来看下EF Core中的一些常见高阶操作,来丰富我们业务实现,从而拥有更多的实现选择 1.EF 内存查找 wh ...
- java 数字和日期处理
static int abs(int a) 返回 a 的绝对值 static long abs(long a) 返回 a 的绝对值 static float abs(float a) 返回 a 的绝对 ...
- 通过naa在esxi主机上找到物理磁盘的位置
因为有一块磁盘告警,需要找到这个块磁盘.通过网络搜索就找到了这个shell脚本. 感谢 Jorluis Perales, VxRail TSE 2 shell脚本: # Script to obtai ...
- 简单谈谈网络抓包,特别是thrift 接口
按照惯例先谈谈最近情况,最近不是刚好跨年吗?看到很多人都在写年度总结,所以我也在写年度总结文章(其实之前我基本没有写过的,今年有点感触,也想记录一下),结果发现写起来有点多,之前还想着元旦前发出来,结 ...
- 安卓手机使用Termux及搭建FTP服务器
Termux安装配置设置参见: 国光:Termux高级终端使用配置教程 搭建FTP服务器参见: Termux安装使用FTP服务器
- Facetoobject_encapsulation
面向对象程序设计思想 一.思想 处处皆对象. 当提到某一功能时,首先应该想有没有实现该功能的对象,有则调用,没有则创建类.当提到数据时,应该想到属于哪个对象. 1.求1~n的累加和 public cl ...