表白网站制作平台,房山广州网站建设,泉州钟南山最新消息,wordpress内置编辑器文章目录 一、目标#xff1a;完善增删改查二、设计#xff1a;完善增删改查三、实现#xff1a;完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.4.2 执行器抽象基类3.4.3 简单执行器 3.5 语句处理器实现3.5.1 … 文章目录 一、目标完善增删改查二、设计完善增删改查三、实现完善增删改查3.1 工程结构3.2 完善增删改查类图3.3 扩展解析元素3.4 新增执行方法3.4.1 执行器接口添加update3.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 框架的开发中仅是处理了 select 的 SQL 信息现在需要把 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.xml3.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 namespaceorg.mybatis.example.BlogMapper* select idselectBlog parameterTypeint resultTypeBlog* 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(Mappers 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(ListElement... lists) {for (ListElement 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 新增执行方法
在 Mybatis 的 ORM 框架中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 ListE* throws SQLException SQL异常*/E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;...
}update 是 Executor 执行接口新增的方法。因为其他两个方法 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 ListE 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 ListE* throws SQLException SQL异常*/protected abstract E ListE 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 ListE 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 ListE 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 ListResultMap 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 ListE 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 ListE 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 ListE 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);/*** 以下是事务控制方法 commitrollback*/void commit();/*** 得到映射器使用泛型使得类型安全** param type 对象类型* param T 封装之后的对象类型* return 封装之后的对象*/T T getMapper(ClassT 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) {ListT 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 ListE 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(ClassT 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 SortedMapInteger, 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 MapString, Object param new ParamMap();int i 0;for (Map.EntryInteger, 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 SortedMapInteger, String getParams(Method method) {// 用一个TreeMap这样就保证还是按参数的先后顺序final SortedMapInteger, 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 ParamMapV extends HashMapString, 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 version1.0 encodingUTF-8?
!DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd
configurationenvironments defaultdevelopmentenvironment iddevelopmenttransactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.jdbc.Driver/property nameurl valuejdbc:mysql://127.0.0.1:3306/mybatis?useUnicodetrueamp;characterEncodingutf8/property nameusername valueroot/property namepassword value123456//dataSource/environment/environmentsmappersmapper resourcemapper/User_Mapper.xml//mappers
/configuration修改 url 地址信息添加 characterEncodingutf8 中文处理
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 ListUser 用户列表*/ListUser 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 version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.lino.mybatis.test.dao.IUserDaoselect idqueryUserInfoById parameterTypejava.lang.Long resultTypecom.lino.mybatis.test.po.UserSELECT id, userId, userName, userHeadFROM userWHERE id #{id}/selectselect idqueryUserInfo parameterTypecom.lino.mybatis.test.po.User resultTypecom.lino.mybatis.test.po.UserSELECT id, userId, userName, userHeadFROM userwhere id #{id}and userId #{userId}/selectselect idqueryUserInfoList resultTypecom.lino.mybatis.test.po.UserSELECT id, userId, userName, userHeadFROM user/selectupdate idupdateUserInfo parameterTypecom.lino.mybatis.test.po.UserUPDATE userSET userName #{userName}WHERE id #{id}/updateinsert idinsertUserInfo parameterTypecom.lino.mybatis.test.po.UserINSERT INTO user(userId, userName, userHead, createTime, updateTime)VALUES (#{userId}, #{userName}, #{userHead}, now(), now())/insertdelete iddeleteUserInfoByUserId parameterTypejava.lang.StringDELETEFROM userWHERE userId #{userId}/delete
/mapper4.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 - 构建参数映射 propertyuserId propertyTypeclass java.lang.String
16:32:32.510 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 propertyuserName propertyTypeclass java.lang.String
16:32:32.510 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 propertyuserHead propertyTypeclass 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设置对应的参数信息 value10002
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设置对应的参数信息 value1_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.测试验证: 对象参数ListUser users userDao.queryUserInfoList();logger.info(测试结果{}, JSON.toJSONString(users));
}测试结果 16:40:47.699 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statementcom.lino.mybatis.test.dao.IUserDao.queryUserInfoList parameternull
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设置对应的参数信息 value1
16:44:12.037 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果1这里测试验证把 ID1 的用户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设置对应的参数信息 value10002
16:47:54.643 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果true这里把数据表中 userId 10002 的用户删除掉通过测试日志和数据库截图测试通过。
五、总结完善增删改查
现在手写的 Mybatis 的全部主干流程串联实现完成了可以执行对数据库的增删改查操作。本章在原有的内容下进行扩展的时候是非常方便的甚至不需要多大的代码改动。这主要也得益于框架在设计实现过程中合理运用设计原则和设计模式的好处。