手写Mybatis:第12章-完善ORM框架,增删改查操作

文章目录

  • 一、目标:完善增删改查
  • 二、设计:完善增删改查
  • 三、实现:完善增删改查
    • 3.1 工程结构
    • 3.2 完善增删改查类图
    • 3.3 扩展解析元素
    • 3.4 新增执行方法
      • 3.4.1 执行器接口添加update
      • 3.4.2 执行器抽象基类
      • 3.4.3 简单执行器
    • 3.5 语句处理器实现
      • 3.5.1 语句处理器接口
      • 3.5.2 修改映射器语句类
      • 3.5.3 抽象语句处理器基类
      • 3.5.4 预处理语句处理器
      • 3.5.5 简单语句处理器
    • 3.6 SqlSession 定义和实现CRUD接口
      • 3.6.1 SqlSession接口
      • 3.6.2 默认SqlSession实现类
    • 3.7 映射器命令执行调度
  • 四、测试:完善增删改查
    • 4.1 测试环境配置
      • 4.1.1 配置数据源配置
      • 4.1.2 修改User实体类
      • 4.1.3 修改IUserDao用户持久层
      • 4.1.4 修改User_Mapper配置文件
    • 4.2 单元测试
      • 4.2.1 插入测试
      • 4.2.2 查询测试(多条数据)
      • 4.2.3 修改测试
      • 4.2.4 删除测试
  • 五、总结:完善增删改查

一、目标:完善增删改查

💡 目前框架中所提供的 SQL 处理仅有一个 select 查询操作.
还没有其他我们日常常用的 insert、update、delete,以及 select 查询返回的集合类型数据?

  • 这部分新增处理 SQL 的内容,也就是在 SqlSession 需要定义新的接口,通知让这些接口被映射器类方法 MappedMethod 进行调用处理。

在这里插入图片描述

  • 结合目前框架的开发结构,对于扩展 insert/update/delete 这部分功能来说,并不会太复杂。
    • 因为从 XML 对方法的解析、参数的处理、结果的封装,都是已经成型的结构。
    • 而我们只需要把这部分新增逻辑从前到后串联到 ORM 框架中就可是实现对数据库的新增、修改和删除。

二、设计:完善增删改查

💡 在现有的框架中完成对 insert/update/delete 方法的扩展?
思考:哪里是流程的开始?

  • 首先解决对 XML 的解析,之前的 ORM 框架的开发中,仅是处理了 selectSQL 信息,现在需要把 insert/update/delete 的语句也按照解析 select 的方式进行处理。

在这里插入图片描述

  • 在添加解析新类型 SQL 操作前提下,后续 DefaultSqlSession 中新增的执行 SQL 方法 insert/update/delete 就可以通过 Configuration 配置项拿到对应的映射器语句,并执行后续的处理流程。

在这里插入图片描述

  • 在执行 sqlSession.getMapper(IUserDao.class) 获取 Mapper 以后。
  • 后续的流程会依次串联到映射器工厂、映射器,以及获取对应的映射器方法开始,调用的就是 DefaultSqlSession
  • 注意:定义的 insert/update/delete,都是调用内部的 update 方法,这是 Mybatis ORM 框架对此类语句处理的一个包装。
    • 因为除了 select 方法,insert、update、delete,都是共性处理逻辑,所以可以被包装成一个逻辑来处理。

三、实现:完善增删改查

3.1 工程结构

mybatis-step-11
|-src|-main|	|-java|		|-com.lino.mybatis|			|-binding|			|	|-MapperMethod.java|			|	|-MapperProxy.java|			|	|-MapperProxyFactory.java|			|	|-MapperRegistry.java|			|-builder|			|	|-xml|			|	|	|-XMLConfigBuilder.java|			|	|	|-XMLMapperBuilder.java|			|	|	|-XMLStatementBuilder.java|			|	|-BaseBuilder.java|			|	|-MapperBuilderAssistant.java|			|	|-ParameterExpression.java|			|	|-SqlSourceBuilder.java|			|	|-StaticSqlSource.java|			|-datasource|			|	|-druid|			|	|	|-DruidDataSourceFacroty.java|			|	|-pooled|			|	|	|-PooledConnection.java|			|	|	|-PooledDataSource.java|			|	|	|-PooledDataSourceFacroty.java|			|	|	|-PoolState.java|			|	|-unpooled|			|	|	|-UnpooledDataSource.java|			|	|	|-UnpooledDataSourceFacroty.java|			|	|-DataSourceFactory.java|			|-executor|			|	|-parameter|			|	|	|-ParameterHandler.java|			|	|-result|			|	|	|-DefaultResultContext.java|			|	|	|-DefaultResultHandler.java|			|	|-resultset|			|	|	|-DefaultResultSetHandler.java|			|	|	|-ResultSetHandler.java|			|	|	|-ResultSetWrapper.java|			|	|-statement|			|	|	|-BaseStatementHandler.java|			|	|	|-PreparedStatementHandler.java|			|	|	|-SimpleStatementHandler.java|			|	|	|-StatementHandler.java|			|	|-BaseExecutor.java|			|	|-Executor.java|			|	|-SimpleExecutor.java|			|-io|			|	|-Resources.java|			|-mapping|			|	|-BoundSql.java|			|	|-Environment.java|			|	|-MappedStatement.java|			|	|-ParameterMapping.java|			|	|-ResultMap.java|			|	|-ResultMapping.java|			|	|-SqlCommandType.java|			|	|-SqlSource.java|			|-parsing|			|	|-GenericTokenParser.java|			|	|-TokenHandler.java|			|-reflection|			|	|-factory|			|	|	|-DefaultObjectFactory.java|			|	|	|-ObjectFactory.java|			|	|-invoker|			|	|	|-GetFieldInvoker.java|			|	|	|-Invoker.java|			|	|	|-MethodInvoker.java|			|	|	|-SetFieldInvoker.java|			|	|-property|			|	|	|-PropertyNamer.java|			|	|	|-PropertyTokenizer.java|			|	|-wrapper|			|	|	|-BaseWrapper.java|			|	|	|-BeanWrapper.java|			|	|	|-CollectionWrapper.java|			|	|	|-DefaultObjectWrapperFactory.java|			|	|	|-MapWrapper.java|			|	|	|-ObjectWrapper.java|			|	|	|-ObjectWrapperFactory.java|			|	|-MetaClass.java|			|	|-MetaObject.java|			|	|-Reflector.java|			|	|-SystemMetaObject.java|			|-scripting|			|	|-defaults|			|	|	|-DefaultParameterHandler.java|			|	|	|-RawSqlSource.java|			|	|-xmltags|			|	|	|-DynamicContext.java|			|	|	|-MixedSqlNode.java|			|	|	|-SqlNode.java|			|	|	|-StaticTextSqlNode.java|			|	|	|-XMLLanguageDriver.java|			|	|	|-XMLScriptBuilder.java|			|	|-LanguageDriver.java|			|	|-LanguageDriverRegistry.java|			|-session|			|	|-defaults|			|	|	|-DefaultSqlSession.java|			|	|	|-DefaultSqlSessionFactory.java|			|	|-Configuration.java|			|	|-ResultContext.java|			|	|-ResultHandler.java|			|	|-RowBounds.java|			|	|-SqlSession.java|			|	|-SqlSessionFactory.java|			|	|-SqlSessionFactoryBuilder.java|			|	|-TransactionIsolationLevel.java|			|-transaction|			|	|-jdbc|			|	|	|-JdbcTransaction.java|			|	|	|-JdbcTransactionFactory.java|			|	|-Transaction.java|			|	|-TransactionFactory.java|			|-type|			|	|-BaseTypeHandler.java|			|	|-IntegerTypeHandler.java|			|	|-JdbcType.java|			|	|-LongTypeHandler.java|			|	|-StringTypeHandler.java|			|	|-TypeAliasRegistry.java|			|	|-TypeHandler.java|			|	|-TypeHandlerRegistry.java|-test|-java|	|-com.lino.mybatis.test|	|-dao|	|	|-IUserDao.java|	|-po|	|	|-User.java|	|-ApiTest.java|-resources|-mapper|	|-User_Mapper.xml|-mybatis-config-datasource.xml

3.2 完善增删改查类图

在这里插入图片描述

  • 首先在 XML 映射器构建中,扩展 XMLMapperBuilder#configurationElement 方法,添加对 insert/update/delete 的解析操作。
    • 添加解析类型。
    • 同时解析信息都会存放到 Configuration 配置项的映射语句 Map 集合 mappedStatement 中,供后续 DefaultSqlSession 执行 SQL 获取配置信息时使用。
  • 接下来是对 MappedMethod 映射器方法的改造,在这里扩展 INSERT、UPDATE、DELETE,同时还需要对 SELECT 进行扩展查询出多个结果集的方法。
  • 所需要扩展的信息,都是由 DefaultSqlSession 调用执行器 Executor 进行处理的。
    • 这里你会看到 Executor 中只有 update 这个新增方法,并没有 insert、delete,因为这两个方法也是调用的 update 进行处理的。
  • 以上这些内容实现完成后,所有新增方法的调用,都会按照前面章节的实现:语句执行、结果封装等步骤,把流程执行完毕,并返回最终的结果。

3.3 扩展解析元素

  • 新增 SQL 类型的 XML 语句,把 insert、update、delete,几种类型的 SQL 解析完成后,存放到 Configuration 配置项的映射器语句中。

XMLMapperBuilder.java

package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.session.Configuration;
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: XML映射构建器*/
public class XMLMapperBuilder extends BaseBuilder {.../*** 配置mapper元素* <mapper namespace="org.mybatis.example.BlogMapper">* <select id="selectBlog" parameterType="int" resultType="Blog">* select * from Blog where id = #{id}* </select>* </mapper>** @param element 元素*/private void configurationElement(Element element) {// 1.配置namespaceString namespace = element.attributeValue("namespace");if ("".equals(namespace)) {throw new RuntimeException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2.配置select|insert|update|deletebuildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));}/*** 配置select|insert|update|delete** @param lists 元素列表*/@SafeVarargsprivate final void buildStatementFromContext(List<Element>... lists) {for (List<Element> list : lists) {for (Element element : list) {final XMLStatementBuilder statementBuilder = new XMLStatementBuilder(configuration, builderAssistant, element);statementBuilder.parseStatementNode();}}}
}
  • 这里改造 buildStatementFromContext 方法的入参类型为 list 的集合,也就是处理所传递到方法中的所有语句的集合。
  • 之后在 XMLMapperBuilder#configurationElement 调用层,传递
    • element.elements("select")
    • element.elements("insert")
    • element.elements("update")
    • element.elements("delete")
  • 四个类型的方法,就可以配置到 Mapper XML 中的不同 SQL 解析存放起来了。

3.4 新增执行方法

  • MybatisORM 框架中,DefaultSqlSession 中最终的 SQL 执行都会调用到 Executor 执行器的。

3.4.1 执行器接口添加update

Executor.java

package com.lino.mybatis.executor;import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;/*** @description: 执行器*/
public interface Executor {.../*** 更新** @param ms        映射器语句* @param parameter 参数* @return 返回的是受影响的行数* @throws SQLException SQL异常*/int update(MappedStatement ms, Object parameter) throws SQLException;/*** 查询** @param ms            映射器语句* @param parameter     参数* @param rowBounds     分页记录限制* @param resultHandler 结果处理器* @param boundSql      SQL对象* @param <E>           返回的类型* @return List<E>* @throws SQLException SQL异常*/<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;...
}
  • updateExecutor 执行接口新增的方法。因为其他两个方法 insert、delete 的调用,也都是调用 update 就够了。

3.4.2 执行器抽象基类

BaseExecutor.java

package com.lino.mybatis.executor;import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 执行器抽象基类*/
public abstract class BaseExecutor implements Executor {private org.slf4j.Logger logger = LoggerFactory.getLogger(BaseExecutor.class);protected Configuration configuration;protected Transaction transaction;protected Executor wrapper;private boolean closed;public BaseExecutor(Configuration configuration, Transaction transaction) {this.configuration = configuration;this.transaction = transaction;this.wrapper = this;}@Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {return doUpdate(ms, parameter);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {if (closed) {throw new RuntimeException("Executor was closed.");}return doQuery(ms, parameter, rowBounds, resultHandler, boundSql);}/*** 更新方法** @param ms        映射器语句* @param parameter 参数* @return 返回的是受影响的行数* @throws SQLException SQL异常*/protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;/*** 查询方法** @param ms            映射器语句* @param parameter     参数* @param rowBounds     分页记录限制* @param resultHandler 结果处理器* @param boundSql      SQL对象* @param <E>           返回的类型* @return List<E>* @throws SQLException SQL异常*/protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;@Overridepublic Transaction getTransaction() {if (closed) {throw new RuntimeException("Executor was closed.");}return transaction;}@Overridepublic void commit(boolean required) throws SQLException {if (closed) {throw new RuntimeException("Cannot commit, transaction is already closed.");}if (required) {transaction.commit();}}@Overridepublic void rollback(boolean required) throws SQLException {if (!closed) {if (required) {transaction.rollback();}}}@Overridepublic void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {transaction.close();}} catch (SQLException e) {logger.warn("Unexpected exception on closing transaction. Cause: " + e);} finally {transaction = null;closed = true;}}/*** 关闭语句** @param statement 语句*/protected void closeStatement(Statement statement) {if (statement != null) {try {statement.close();} catch (SQLException ignore) {}}}
}

3.4.3 简单执行器

SimpleExecutor.java

package com.lino.mybatis.executor;import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;import static ch.qos.logback.core.db.DBHelper.closeStatement;/*** @description: 简单执行器* @author: lingjian* @createDate: 2022/11/8 13:42*/
public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}@Overrideprotected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 新建一个 StatementHandlerStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 准备语句stmt = prepareStatement(handler);// StatementHandler.updatereturn handler.update(stmt);} finally {closeStatement(stmt);}}@Overrideprotected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();// 新建一个 StatementHandlerStatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);// 准备语句stmt = prepareStatement(handler);// 返回结果return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}}private Statement prepareStatement(StatementHandler handler) throws SQLException {Statement stmt;Connection connection = transaction.getConnection();// 准备语句stmt = handler.prepare(connection);handler.parameterize(stmt);return stmt;}
}
  • SimpleExecutor#doUpdate 方法是 BeanExecutor 抽象类实现 Executor#update 接口后,定义的抽象方法。
  • 这个抽象方法中,和 doQuery 方法几乎类似,都是创建一个新的 StatementHandler 语句处理器,之后准备语句,执行处理。
  • 注意doUpdate 创建 StatementHandler 语句处理器的时候,是没有 resultHandler、boundSql 两个参数的。
    • 所以在创建的过程中,是需要对有必要使用的 boundSql 进行判断处理的。
    • 这部分内容主要体现在 BaseStatementHandler 的构造函数中,关于 boundSql 的判断和实例化处理

3.5 语句处理器实现

3.5.1 语句处理器接口

StatementHandler.java

package com.lino.mybatis.executor.statement;import com.alibaba.druid.support.spring.stat.annotation.Stat;
import com.lino.mybatis.session.ResultHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 语句处理器*/
public interface StatementHandler {/*** 准备语句** @param connection 链接* @return Statement语句*/Statement prepare(Connection connection);/*** 参数化** @param statement 语句* @throws SQLException SQL异常*/void parameterize(Statement statement) throws SQLException;/*** 执行更新** @param statement 语句* @return 返回受影响的行数* @throws SQLException SQL异常*/int update(Statement statement) throws SQLException;/*** 执行查询** @param statement     语句* @param resultHandler 结果处理器* @param <E>           泛型类型* @return 泛型集合* @throws SQLException SQL异常*/<E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
}

3.5.2 修改映射器语句类

MappedStatement.java

package com.lino.mybatis.mapping;import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.List;/*** @description: 映射器语句类*/
public class MappedStatement {private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class<?> resultType;private LanguageDriver lang;private List<ResultMap> resultMaps;public MappedStatement() {}/*** 获取SQL对象** @param parameterObject 参数* @return SQL对象*/public BoundSql getBoundSql(Object parameterObject) {// 调用 SqlSource#getBoundSqlreturn sqlSource.getBoundSql(parameterObject);}...
}

3.5.3 抽象语句处理器基类

  • 主要变化在 BaseStatementHandler 的构造函数中添加了 boundSql 的初始化。

BaseStatementHandler.java

package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;/*** @description: 语句处理器抽象基类*/
public abstract class BaseStatementHandler implements StatementHandler {protected final Configuration configuration;protected final Executor executor;protected final MappedStatement mappedStatement;protected final Object parameterObject;protected final ResultSetHandler resultSetHandler;protected final ParameterHandler parameterHandler;protected final RowBounds rowBounds;protected BoundSql boundSql;public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds,ResultHandler resultHandler, BoundSql boundSql) {this.configuration = mappedStatement.getConfiguration();this.executor = executor;this.mappedStatement = mappedStatement;this.parameterObject = parameterObject;this.rowBounds = rowBounds;// 新增判断,因为update不会传入boundSql参数,这里做初始化处理if (boundSql == null) {boundSql = mappedStatement.getBoundSql(parameterObject);}this.boundSql = boundSql;this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, resultHandler, boundSql);}@Overridepublic Statement prepare(Connection connection) {Statement statement = null;try {// 实例化 Statementstatement = instantiateStatement(connection);// 参数设置,可以被抽取,提供配置statement.setQueryTimeout(350);statement.setFetchSize(10000);return statement;} catch (Exception e) {throw new RuntimeException("Error prepare statement. Cause: " + e, e);}}/*** 初始化语句** @param connection 连接* @return 语句* @throws SQLException SQL异常*/protected abstract Statement instantiateStatement(Connection connection) throws SQLException;}
  • 因为只有获取了 boundSql 的参数,才能方便的执行后续对 SQL 处理的操作。
  • 所以在执行 update 方法,没有传入 boundSql 的时候,则需要这里进行判断以及自己获取的处理操作。

3.5.4 预处理语句处理器

PreparedStatementHandler.java

package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 预处理语句处理器(PREPARED)*/
public class PreparedStatementHandler extends BaseStatementHandler {public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,RowBounds rowBounds, ResultHandler resultSetHandler, BoundSql boundSql) {super(executor, mappedStatement, parameterObject, rowBounds, resultSetHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {String sql = boundSql.getSql();return connection.prepareStatement(sql);}@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}@Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return ps.getUpdateCount();}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}
}
  • PreparedStatementHandler 预处理语句处理器中,实现类 update 方法。
  • 相对于 query 方法的实现,其实只是相当于 JDBC 操作数据库返回结果集的变化,update 处理要返回 SQL 的操作影响了多少条数据的数量。

3.5.5 简单语句处理器

SimpleStatementHandler.java

package com.lino.mybatis.executor.statement;import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 简单语句处理器(STATEMENT)*/
public class SimpleStatementHandler extends BaseStatementHandler {public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {super(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);}@Overrideprotected Statement instantiateStatement(Connection connection) throws SQLException {return connection.createStatement();}@Overridepublic void parameterize(Statement statement) {}@Overridepublic int update(Statement statement) throws SQLException {String sql = boundSql.getSql();statement.execute(sql);return statement.getUpdateCount();}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {String sql = boundSql.getSql();statement.execute(sql);return resultSetHandler.handleResultSets(statement);}
}

3.6 SqlSession 定义和实现CRUD接口

3.6.1 SqlSession接口

  • SqlSession 中需要新增出处理数据库的接口,包括 selectList、insert、update、delete

SqlSession.java

package com.lino.mybatis.session;import java.util.List;/*** @description: SqlSession 用来执行SQL,获取映射器,管理事务*/
public interface SqlSession {/*** 根据指定的sqlID获取一条记录的封装对象** @param statement sqlID* @param <T>       封装之后的对象类型* @return 封装之后的对象*/<T> T selectOne(String statement);/*** 根据指定的sqlID获取一条记录的封装对象,只不过这个方法容许我们给sql传递一些参数** @param statement sqlID* @param parameter 传递参数* @param <T>       封装之后的对象类型* @return 封装之后的对象*/<T> T selectOne(String statement, Object parameter);/*** 获取多条基类,这个方法容许我们可以传递一些参数** @param statement    sqlID* @param parameter    传递参数* @param <E>封装之后的对象列表* @return 封装之后的对象列表*/<E> List<E> selectList(String statement, Object parameter);/*** 插入记录,容许传递参数** @param statement sqlID* @param parameter 传递参数* @return 返回的是受影响的行数*/int insert(String statement, Object parameter);/*** 插入记录** @param statement sqlID* @param parameter 传递参数* @return 返回的是受影响的行数*/int update(String statement, Object parameter);/*** 删除记录** @param statement sqlID* @param parameter 传递参数* @return 返回的是受影响的行数*/Object delete(String statement, Object parameter);/*** 以下是事务控制方法 commit,rollback*/void commit();/*** 得到映射器,使用泛型,使得类型安全** @param type 对象类型* @param <T>  封装之后的对象类型* @return 封装之后的对象*/<T> T getMapper(Class<T> type);/*** 得到配置** @return Configuration*/Configuration getConfiguration();
}

3.6.2 默认SqlSession实现类

DefaultSqlSession.java

package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** @description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {private Logger logger = LoggerFactory.getLogger(DefaultSqlSession.class);private Configuration configuration;private Executor executor;public DefaultSqlSession(Configuration configuration, Executor executor) {this.configuration = configuration;this.executor = executor;}@Overridepublic <T> T selectOne(String statement) {return this.selectOne(statement, null);}@Overridepublic <T> T selectOne(String statement, Object parameter) {List<T> list = this.selectList(statement, parameter);if (list.size() == 1) {return list.get(0);} else if (list.size() > 1) {throw new RuntimeException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());} else {return null;}}@Overridepublic <E> List<E> selectList(String statement, Object parameter) {logger.info("执行查询 statement:{} parameter:{}", statement, JSON.toJSONString(parameter));MappedStatement ms = configuration.getMappedStatement(statement);try {return executor.query(ms, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, ms.getSqlSource().getBoundSql(parameter));} catch (SQLException e) {throw new RuntimeException("Error querying database. Cause: " + e);}}@Overridepublic int insert(String statement, Object parameter) {// 在 Mybatis 中 insert 调用的是 updatereturn update(statement, parameter);}@Overridepublic int update(String statement, Object parameter) {MappedStatement ms = configuration.getMappedStatement(statement);try {return executor.update(ms, parameter);} catch (SQLException e) {throw new RuntimeException("Errot updating database. Cause: " + e);}}@Overridepublic Object delete(String statement, Object parameter) {return update(statement, parameter);}@Overridepublic void commit() {try {executor.commit(true);} catch (SQLException e) {throw new RuntimeException("Error committing transaction. Cause: " + e);}}@Overridepublic <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);}@Overridepublic Configuration getConfiguration() {return configuration;}
}
  • DefaultSqlSession 的具体实现中可以看到,update 方法调用了具体的执行器封装成方法后,insert、delete 都是调用的这个 update 方法进行操作的。
    • 接口定义的是单一执行,接口实现是做了适配封装
  • 另外这里单独提供了 selectList 方法,所以把之前在 selectOne 关于 executor.query 的执行处理,都迁移到 selectList 方法中。
  • 之后在 selectOne 中调用 selectList 方法,并给出相应的判断处理。

3.7 映射器命令执行调度

  • 以上这些所实现的语句执行器、SqlSession 包装,最终都会交给 MapperMethod 映射器方法根据不同的 SQL 命令调用不同的 SqlSession 方法进行执行。

MapperMethod.java

package com.lino.mybatis.binding;import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.SqlSession;
import java.lang.reflect.Method;
import java.util.*;/*** @description: 映射器方法*/
public class MapperMethod {private final SqlCommand command;private final MethodSignature method;public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration) {this.command = new SqlCommand(configuration, mapperInterface, method);this.method = new MethodSignature(configuration, method);}public Object execute(SqlSession sqlSession, Object[] args) {Object result = null;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.insert(command.getName(), param);break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.delete(command.getName(), param);break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.update(command.getName(), param);break;}case SELECT: {Object param = method.convertArgsToSqlCommandParam(args);if (method.returnsMany) {result = sqlSession.selectList(command.getName(), param);} else {result = sqlSession.selectOne(command.getName(), param);}break;}default:throw new RuntimeException("Unknown execution method for: " + command.getName());}return result;}/*** SQL 指令*/public static class SqlCommand {private final String name;private final SqlCommandType type;public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {String statementName = mapperInterface.getName() + "." + method.getName();MappedStatement ms = configuration.getMappedStatement(statementName);this.name = ms.getId();this.type = ms.getSqlCommandType();}public String getName() {return name;}public SqlCommandType getType() {return type;}}/*** 方法签名*/public static class MethodSignature {private final boolean returnsMany;private final Class<?> returnType;private final SortedMap<Integer, String> params;public MethodSignature(Configuration configuration, Method method) {this.returnType = method.getReturnType();this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());this.params = Collections.unmodifiableSortedMap(getParams(method));}public Object convertArgsToSqlCommandParam(Object[] args) {final int paramCount = params.size();if (args == null || paramCount == 0) {// 如果没参数return null;} else if (paramCount == 1) {return args[params.keySet().iterator().next().intValue()];} else {// 否则,返回一个ParamMap, 修改参数名,参数名就是其位置final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : params.entrySet()) {// 1.先加一个#{0},#{1},#{2}...参数param.put(entry.getValue(), args[entry.getKey().intValue()]);// issue #71, add param names as param1, param2...but ensure backward compatibilityfinal String genericParamName = "param" + (i + 1);if (!param.containsKey(genericParamName)) {/*2.再加一个#{param1},#{param2}...参数你可以传递多个参数给一个映射器方法。默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等如果你想改变参数的名称(只在多参数情况下),那么你可以在参数上使用@Param("paramName")注解*/param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}private SortedMap<Integer, String> getParams(Method method) {// 用一个TreeMap,这样就保证还是按参数的先后顺序final SortedMap<Integer, String> params = new TreeMap<>();final Class<?>[] argTypes = method.getParameterTypes();for (int i = 0; i < argTypes.length; i++) {String paramName = String.valueOf(params.size());// 不做 Param 的实现,这部分不处理。params.put(i, paramName);}return params;}public boolean returnsMany() {return returnsMany;}}/*** 参数map,静态内部类,更严格的get方法,如果没有相应的key,报错*/public static class ParamMap<V> extends HashMap<String, V> {private static final long serialVersionUID = -2212268410512043556L;@Overridepublic V get(Object key) {if (!super.containsKey(key)) {throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());}return super.get(key);}}
}
  • 映射器方法 MapperMethod#execute 会根据不同的 SqlCommand 指令调用到不同的方法上去,INSERT、UPDATE、DELETE 分别按照对应的方法调用即可。
    • 这里 SELECT 进行了扩展,因为需要按照不同的方法出参类型,调用不同的方法,主要是 selectList、selectOne 的区别。
  • 另外这里 method.returnsMany 来自于 MapperMethod.MethodSignature 方法签名中进行通过返回类型进行获取。

在这里插入图片描述

四、测试:完善增删改查

4.1 测试环境配置

4.1.1 配置数据源配置

mybatis-config-datasource.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><mappers><mapper resource="mapper/User_Mapper.xml"/></mappers>
</configuration>
  • 修改 url 地址信息,添加 characterEncoding=utf8 中文处理

4.1.2 修改User实体类

User.java

package com.lino.mybatis.test.po;import java.util.Date;/*** @description: 用户实例类*/
public class User {private Long id;/*** 用户ID*/private String userId;/*** 头像*/private String userHead;/*** 用户名称*/private String userName;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;public User() {}public User(Long id, String userId) {this.id = id;this.userId = userId;}public User(Long id, String userId, String userName) {this.id = id;this.userId = userId;this.userName = userName;}...getter/setter
}

4.1.3 修改IUserDao用户持久层

IUserDao.java

package com.lino.mybatis.test.dao;import com.lino.mybatis.test.po.User;
import java.util.List;/*** @Description: 用户持久层*/
public interface IUserDao {/*** 根据ID查询用户信息** @param uId ID* @return User 用户*/User queryUserInfoById(Long uId);/*** 根据用户对象查询用户信息** @param user 用户* @return User 用户*/User queryUserInfo(User user);/*** 查询用户对象列表** @return List<User> 用户列表*/List<User> queryUserInfoList();/*** 更新用户信息** @param user 用户对象* @return 受影响的行数*/int updateUserInfo(User user);/*** 新增用户信息** @param user 用户对象* @return 受影响的行数*/int insertUserInfo(User user);/*** 根据ID删除用户信息** @param uId ID* @return 受影响的行数*/int deleteUserInfoByUserId(String uId);
}

4.1.4 修改User_Mapper配置文件

User_Mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IUserDao"><select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userWHERE id = #{id}</select><select id="queryUserInfo" parameterType="com.lino.mybatis.test.po.User" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM userwhere id = #{id}and userId = #{userId}</select><select id="queryUserInfoList" resultType="com.lino.mybatis.test.po.User">SELECT id, userId, userName, userHeadFROM user</select><update id="updateUserInfo" parameterType="com.lino.mybatis.test.po.User">UPDATE userSET userName = #{userName}WHERE id = #{id}</update><insert id="insertUserInfo" parameterType="com.lino.mybatis.test.po.User">INSERT INTO user(userId, userName, userHead, createTime, updateTime)VALUES (#{userId}, #{userName}, #{userHead}, now(), now())</insert><delete id="deleteUserInfoByUserId" parameterType="java.lang.String">DELETEFROM userWHERE userId = #{userId}</delete>
</mapper>

4.2 单元测试

4.2.1 插入测试

ApiTest.java

@Test
public void test_insertUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证User user = new User();user.setUserId("10002");user.setUserName("张三");user.setUserHead("1_05");userDao.insertUserInfo(user);logger.info("测试结果:{}", "Insert OK");// 3.提交事务sqlSession.commit();
}

测试结果

16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userId propertyType:class java.lang.String
16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userName propertyType:class java.lang.String
16:32:32.510 [main] INFO  c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:userHead propertyType:class java.lang.String
16:32:33.171 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002"
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"张三"
16:32:33.213 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"1_05"
16:32:33.213 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:Insert OK

在这里插入图片描述

  • 从测试日志信息和数据库的截图,可以看到数据已经插入到数据库,验证通过
  • 注意:执行完 SQL 以后,还执行一次 sqlSession.commit()
    • 这是因为在 DefaultSqlSessionFactory#openSession 开启 Session 创建事务工厂的时候,传入给事务工厂构造函数的事务是否自动提交为 false
    • 所以这里就需要我们去手动提交事务,否则是不会插入到数据库中的。

4.2.2 查询测试(多条数据)

ApiTest.java

@Test
public void test_queryUserInfoList() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证: 对象参数List<User> users = userDao.queryUserInfoList();logger.info("测试结果:{}", JSON.toJSONString(users));
}

测试结果

16:40:47.699 [main] INFO  c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IUserDao.queryUserInfoList parameter:null
16:40:48.361 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 1192171522.
16:40:48.395 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:[{"id":1,"userHead":"1_04","userId":"10001","userName":"小零哥"},{"id":4,"userHead":"1_05","userId":"10002","userName":"张三"}]
  • 现在我们再查询结果的时候,就可以查询到2条记录的集合了,这说明我们添加的 MapperMethod#execute 调用 sqlSession.selectList(command.getName(), param) 是测试通过的。

4.2.3 修改测试

ApiTest.java

@Test
public void test_updateUserInfo() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证int count = userDao.updateUserInfo(new User(1L, "10001", "小灵哥"));logger.info("测试结果:{}", count);// 3.提交事务sqlSession.commit();
}

测试结果

16:44:11.979 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:44:12.027 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"小灵哥"
16:44:12.028 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:1
16:44:12.037 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:1

在这里插入图片描述

  • 这里测试验证把 ID=1 的用户,userName 修改为 小灵哥,通过测试日志和数据库截图,测试通过。

4.2.4 删除测试

ApiTest.java

@Test
public void test_deleteUserInfoByUserId() {// 1.获取映射器对象IUserDao userDao = sqlSession.getMapper(IUserDao.class);// 2.测试验证int count = userDao.deleteUserInfoByUserId("10002");logger.info("测试结果:{}", count == 1);// 3.提交事务sqlSession.commit();
}

测试结果

16:47:54.591 [main] INFO  c.l.m.d.pooled.PooledDataSource - Created connention 597190999.
16:47:54.633 [main] INFO  c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:"10002"
16:47:54.643 [main] INFO  com.lino.mybatis.test.ApiTest - 测试结果:true

在这里插入图片描述

  • 这里把数据表中 userId = '10002' 的用户删除掉,通过测试日志和数据库截图,测试通过。

五、总结:完善增删改查

  • 现在手写的 Mybatis 的全部主干流程串联实现完成了,可以执行对数据库的增删改查操作。
  • 本章在原有的内容下进行扩展的时候是非常方便的,甚至不需要多大的代码改动。这主要也得益于框架在设计实现过程中,合理运用设计原则和设计模式的好处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/97799.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无人化在线静电监控系统的组成

无人化在线静电监控系统是一种用于检测和监控静电情况的系统&#xff0c;它可以自动地实时监测各个区域的静电水平&#xff0c;并在出现异常情况时发出报警信号。静电监控报警器则是该系统中的一个重要组成部分&#xff0c;用于接收和传达报警信号。 无人化在线静电监控系统通…

华为OD机试 - 找出经过特定点的路径长度 - 深度优先搜索(Java 2022 Q4 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

css画箭头图标放标题前面,旋转,border的单个边框设置

CSS边框属性_css border dotted_小张biubiu的博客-CSDN博客 你还不知道css的旋转效果怎么实现&#xff1f;来这里看看吧_css旋转效果_我糖呢的博客-CSDN博客 .sub-title{position: relative;margin-left: 59px;& span{color: #1CDBFE;};& span::before{content: "…

Unity 安卓(Android)端AVProVideo插件播放不了视频,屏幕一闪一闪的

编辑器运行没有问题&#xff0c;但是安卓就有问题&#xff0c;在平板上运行就会报错&#xff1a; vulkan graphics API is notsupported 说不支持Vulkan图形API,解决方法&#xff1a;把Vulkan删除掉

怎么把pdf转换成高清图片?

怎么把pdf转换成高清图片&#xff1f;最近&#xff0c;我的同事遇到了一个问题&#xff0c;现在她需要将一些pdf文件转换成高清的图片&#xff0c;这件事情让让她感到非常无助&#xff0c;因为她非常着急需要将这些文件转换为图片格式&#xff0c;以便更好的在今后的工作中进行…

长胜证券:三大拐点共振 看好智能驾驶新一轮行情

摘要 【长胜证券&#xff1a;三大拐点共振 看好智能驾驭新一轮行情】长胜证券研报指出&#xff0c;全球共振&#xff0c;国内智驾商场正迎来三大拐点&#xff1a;1&#xff09;技能上&#xff0c;“BEV Transformer数据闭环”新架构2023年开端上车&#xff0c;使得不依靠高精地…

Linux代码初试__进度条

前言 在我们的日常生活中&#xff0c;进度条是十分常见的&#xff0c;比如在软件下载中&#xff0c;应用加载中等等~~~那么进度条有什么特点&#xff1f;他又如何实现。 下面我们将结合下面的图展开讲解 一、前置理论知识 1.1回车和换行的区别 在我们的日常生活中&#x…

Servlet属性、监听者和会话

没有servlet能单独存在。在当前的现代Web应用中&#xff0c;许多组件都是在一起协作共同完成一个目标。怎么让这些组件共享信息&#xff1f;如何隐藏信息&#xff1f;怎样让信息做到线程安全&#xff1f; 1 属性和监听者 1.1 初始化 容器初始化一个servlet时&#xff0c;会为…

【HTML5高级第一篇】Web存储 - cookie、localStorage、sessionStorage

文章目录 一、数据存储1.1 cookie1.1.1 概念介绍1.1.2 存储与获取1.1.3 方法的封装1.1.4 总结 1.2 localstorage 与 sessionstorage1.2.1 概述1.2.2 操作数据的属性或方法1.2.3 案例-提交问卷1.2.4 Web Storage带来的好处 附录&#xff1a;1. HTML5提供的数据持久化技术&#x…

算法 数据结构 双向环形链表 手撸环形链表 环形链表实现容器 环形链表添加修改删除获取大小 环形链表实现自定义容器 手撸容器 双向环形哨兵链表 数据结构(六)

1. 环形链表&#xff1a; 2. 建议先不要看我写得自己先实现下&#xff0c;只将Node内部类复制自己命名得容器内&#xff0c; 实现方法&#xff1a; a. add方法&#xff08;添加到头部&#xff0c;尾部添加&#xff0c;指定位置添加&#xff09; b. get方法&#xff08;获取首部…

基于costas环的载波同步系统matlab性能仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................................ I_Dataroun…

Word转为PDF后图片模糊怎么办?Word转为PDF的技巧介绍

将Word文档转为PDF是我们日常办公和文档处理中常见的需求。PDF格式的优势在于跨平台兼容性、保留原始格式、文档保护以及方便共享和分发等方面。本文将探讨Word转为PDF后图片模糊怎么办?Word转为PDF的技巧有哪些?通过这些问题的答案&#xff0c;可以帮助您更好的利用文件转换…