• 手敲Mybatis-SQL执行器


    上一章节的DefaultSqlSession类获取连接以及执行sql语句再转换结果等的写法不符合单一职责原则,这样日后维护这个方法会越来越长,甚至更改还会出现其他业务错误的可能,所以这一章节围绕的就是按各个职能去拆分不同的类去实现,也方便日后扩展,如执行SQL语句则使用执行器去执行,在执行的过程中需要得到SQL语句,所以需要设计语句处理器需要准备语句,参数化(预处理则将参数进行?转为数据),最后才执行查询等操作,这些类相辅相成,最终的设计则体现在可独自使用,也可按流程合在一起执行。

    1.UML类图

    2.代码实现 

    Executor类:定义执行器接口,这里定义了查询方法、获取事务、以及对事务提交关闭和回滚等方法,等待相关子类实现。

    package df.middleware.mybatis.executor
    1. public interface Executor {
    2. ResultHandler NO_RESULT_HANDLER = null;
    3. // 定义执行Sql查询操作
    4. List query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);
    5. Transaction getTransaction();
    6. // 以下事务处理-提交、回滚、关闭
    7. void commit(boolean required) throws SQLException;
    8. void rollback(boolean required) throws SQLException;
    9. void close(boolean forceRollback);
    10. }

    BaseExecutor类:实现Executor接口,此类的query方法则使用了模板模式,先判断事务是否关闭,如没有执行doQuery()查询方法,这个方法就可以由子类去实现,其他的事务提交回滚以及关闭都是一样的操作,所以都写到基础BaseExecutor类。

    package df.middleware.mybatis.executor;
    1. // 基础执行器,处理事务和执行查询的定义,此类处理共性的方法
    2. public abstract class BaseExecutor implements Executor {
    3. private org.slf4j.Logger logger = LoggerFactory.getLogger(BaseExecutor.class);
    4. protected Configuration configuration;
    5. protected Transaction transaction;
    6. protected Executor wrapper;
    7. private boolean closed;
    8. protected BaseExecutor(Configuration configuration, Transaction transaction) {
    9. this.configuration = configuration;
    10. this.transaction = transaction;
    11. this.wrapper = this;
    12. }
    13. @Override
    14. public List query(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
    15. if (closed) {
    16. throw new RuntimeException("Executor was closed.");
    17. }
    18. return doQuery(ms, parameter, resultHandler, boundSql);
    19. }
    20. protected abstract List doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql);
    21. @Override
    22. public Transaction getTransaction() {
    23. if (closed) {
    24. throw new RuntimeException("Executor was closed.");
    25. }
    26. return transaction;
    27. }
    28. @Override
    29. public void commit(boolean required) throws SQLException {
    30. if (closed) {
    31. throw new RuntimeException("Cannot commit, transaction is already closed");
    32. }
    33. if (required) {
    34. transaction.commit();
    35. }
    36. }
    37. @Override
    38. public void rollback(boolean required) throws SQLException {
    39. if (!closed) {
    40. if (required) {
    41. transaction.rollback();
    42. }
    43. }
    44. }
    45. @Override
    46. public void close(boolean forceRollback) {
    47. try {
    48. try {
    49. rollback(forceRollback);
    50. } finally {
    51. transaction.close();
    52. }
    53. } catch (SQLException e) {
    54. logger.warn("Unexpected exception on closing transaction. Cause: " + e);
    55. } finally {
    56. transaction = null;
    57. closed = true;
    58. }
    59. }
    60. }

    SimpleExecutor类:简单执行器,BaseExecutor的子类,继承自BaseExecutor,实现doQuery()方法,将流程放入了这个方法中,如创建的是语句处理器,则后续用语句处理器进行初始化参数,并用语句处理器进行设置参数以及调用语句处理器的查询方法,可能语句处理器你比较模糊,sql查询有的需要参数,则参数的设置过程叫预处理,没有参数的基本查询则叫简单语句处理器,不用处理参数,这个下面会介绍这两个类。

    总之此类的作用就是(流程再此组合)-(不同的职责不同的类)-(最后还是回归此并返回结果)

    package df.middleware.mybatis.executor;
    1. public class SimpleExecutor extends BaseExecutor {
    2. public SimpleExecutor(Configuration configuration, Transaction transaction) {
    3. super(configuration, transaction);
    4. }
    5. @Override
    6. protected List doQuery(MappedStatement ms, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
    7. try {
    8. try {
    9. Configuration configuration = ms.getConfiguration();
    10. // 调用创建语句处理器-PreparedStatementHandler
    11. StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, resultHandler, boundSql);
    12. Connection connection = transaction.getConnection();
    13. // 调用语句处理器-准备操作,如初始化参数
    14. Statement stmt = handler.prepare(connection);
    15. // 设置参数
    16. handler.parameterize(stmt);
    17. // 调用语句处理器的查询方法
    18. return handler.query(stmt, resultHandler);
    19. } catch (SQLException e) {
    20. e.printStackTrace();
    21. return null;
    22. }
    23. } catch (SQLException e) {
    24. e.printStackTrace();
    25. return null;
    26. }
    27. }
    28. }

    StatementHandler类:语句处理器接口,定义准备语句,参数化,执行查询

    package df.middleware.mybatis.executor.statement;
    1. public interface StatementHandler {
    2. /** 准备语句 */
    3. Statement prepare(Connection connection) throws SQLException;
    4. /** 参数化 */
    5. void parameterize(Statement statement) throws SQLException;
    6. /** 执行查询 */
    7. List query(Statement statement, ResultHandler resultHandler) throws SQLException;
    8. }

    BaseStatementHandler类:语句处理器基础类,继承StatementHandler,实现准备方法,原因是此方法在简单处理器和预处理处理器都要使用,则提取出共用。并定义了instantiateStatement()由子类实现,

    package df.middleware.mybatis.executor.statement;
    1. public abstract class BaseStatementHandler implements StatementHandler {
    2. protected final Configuration configuration;
    3. protected final Executor executor;
    4. protected final MappedStatement mappedStatement;
    5. protected final Object parameterObject;
    6. protected final ResultSetHandler resultSetHandler;
    7. protected BoundSql boundSql;
    8. public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {
    9. this.configuration = mappedStatement.getConfiguration();
    10. this.executor = executor;
    11. this.mappedStatement = mappedStatement;
    12. this.boundSql = boundSql;
    13. this.parameterObject = parameterObject;
    14. this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, boundSql);
    15. }
    16. @Override
    17. public Statement prepare(Connection connection) throws SQLException {
    18. Statement statement = null;
    19. try {
    20. // 实例化 Statement
    21. statement = instantiateStatement(connection);
    22. // 参数设置,可以被抽取,提供配置
    23. statement.setQueryTimeout(350);
    24. statement.setFetchSize(10000);
    25. return statement;
    26. } catch (Exception e) {
    27. throw new RuntimeException("Error preparing statement. Cause: " + e, e);
    28. }
    29. }
    30. protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    31. }

     PreparedStatementHandler:预处理语句处理器,继承基础语句处理器,实现创建stament初始化,参数化以及查询操作。

    由于是有参数的,所以在instantiateStatement()需要将sql语句传入,参数化时用PreparedStatement进行参数化设置。

    为什么此用定义了query(),原因是由于有参数查询和无参数查询不一样,需要使用PreparedStatement.execute(),普通的则直接可以使用Statement.execute(),所以这里这样就添加了query(),前面讲的查询执行器最终会调用语句处理器的某个具体实现

    package df.middleware.mybatis.executor.statement;
    1. public class PreparedStatementHandler extends BaseStatementHandler{
    2. public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {
    3. super(executor, mappedStatement, parameterObject, resultHandler, boundSql);
    4. }
    5. @Override
    6. protected Statement instantiateStatement(Connection connection) throws SQLException {
    7. String sql = boundSql.getSql();
    8. return connection.prepareStatement(sql);
    9. }
    10. @Override
    11. public void parameterize(Statement statement) throws SQLException {
    12. PreparedStatement ps = (PreparedStatement) statement;
    13. ps.setLong(1, Long.parseLong(((Object[]) parameterObject)[0].toString()));
    14. }
    15. @Override
    16. public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    17. PreparedStatement ps = (PreparedStatement) statement;
    18. ps.execute();
    19. return resultSetHandler. handleResultSets(ps);
    20. }
    21. }

    SimpleStatementHandler类:简单语句处理器,继承基础语句处理器,创建statement方式则可以不用传入Sql语句,参数化也不用设置,执行查询时可用普通的statement执行,

    package df.middleware.mybatis.executor.statement;

    1. public class SimpleStatementHandler extends BaseStatementHandler {
    2. public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, ResultHandler resultHandler, BoundSql boundSql) {
    3. super(executor, mappedStatement, parameterObject, resultHandler, boundSql);
    4. }
    5. @Override
    6. protected Statement instantiateStatement(Connection connection) throws SQLException {
    7. return connection.createStatement();
    8. }
    9. @Override
    10. public void parameterize(Statement statement) throws SQLException {
    11. // N/A
    12. }
    13. @Override
    14. public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    15. String sql = boundSql.getSql();
    16. statement.execute(sql);
    17. return resultSetHandler.handleResultSets(statement);
    18. }

    ResultSetHandler类:结果集处理器,接口,定义了handleResultSets()方法,在configure类里进行实例化子类。依次传递到结果查询再此调用此方法。

    package cn.bugstack.mybatis.executor.resultset;
    1. public interface ResultSetHandler {
    2. List handleResultSets(Statement stmt) throws SQLException;
    3. }

    DefaultResultSetHandler类:实现ResultSetHandler,和之前编码业务逻辑一样,只不过提取出来放到它该放的位置上,每个代码都有自己的职责功能。

    package cn.bugstack.mybatis.executor.resultset;
    1. public class DefaultResultSetHandler implements ResultSetHandler {
    2. private final BoundSql boundSql;
    3. public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, BoundSql boundSql) {
    4. this.boundSql = boundSql;
    5. }
    6. @Override
    7. public List handleResultSets(Statement stmt) throws SQLException {
    8. ResultSet resultSet = stmt.getResultSet();
    9. try {
    10. return resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));
    11. } catch (ClassNotFoundException e) {
    12. e.printStackTrace();
    13. return null;
    14. }
    15. }
    16. private List resultSet2Obj(ResultSet resultSet, Class clazz) {
    17. List list = new ArrayList<>();
    18. try {
    19. ResultSetMetaData metaData = resultSet.getMetaData();
    20. int columnCount = metaData.getColumnCount();
    21. // 每次遍历行值
    22. while (resultSet.next()) {
    23. T obj = (T) clazz.newInstance();
    24. for (int i = 1; i <= columnCount; i++) {
    25. Object value = resultSet.getObject(i);
    26. String columnName = metaData.getColumnName(i);
    27. String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
    28. Method method;
    29. if (value instanceof Timestamp) {
    30. method = clazz.getMethod(setMethod, Date.class);
    31. } else {
    32. method = clazz.getMethod(setMethod, value.getClass());
    33. }
    34. method.invoke(obj, value);
    35. }
    36. list.add(obj);
    37. }
    38. } catch (Exception e) {
    39. e.printStackTrace();
    40. }
    41. return list;
    42. }
    43. }

    Configuration类:此类的更改是添加了上述的执行器,语句处理器,结果器的实例化

    package df.middleware.mybatis.session;
    1. /**
    2. * 创建结果集处理器
    3. */
    4. public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, BoundSql boundSql) {
    5. return new DefaultResultSetHandler(executor, mappedStatement, boundSql);
    6. }
    7. /**
    8. * 生产执行器
    9. */
    10. public Executor newExecutor(Transaction transaction) {
    11. return new SimpleExecutor(this, transaction);
    12. }
    13. /**
    14. * 创建语句处理器
    15. */
    16. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, ResultHandler resultHandler, BoundSql boundSql) {
    17. return new PreparedStatementHandler(executor, mappedStatement, parameter, resultHandler, boundSql);
    18. }

    DefaultSqlSession类:此类更改如下,添加执行器全局变量,则可简化成直接执行query即可达到目的。

    package df.middleware.mybatis.session.defaults;

    1. public class DefaultSqlSession implements SqlSession {
    2. private Configuration configuration;
    3. private Executor executor;
    4. public DefaultSqlSession(Configuration configuration, Executor executor) {
    5. this.configuration = configuration;
    6. this.executor = executor;
    7. }
    8. @Override
    9. public T selectOne(String statement) {
    10. return this.selectOne(statement, null);
    11. }
    12. @Override
    13. public T selectOne(String statement, Object parameter) {
    14. MappedStatement ms = configuration.getMappedStatement(statement);
    15. List list = executor.query(ms, parameter, Executor.NO_RESULT_HANDLER, ms.getBoundSql());
    16. return list.get(0);
    17. }
    18. @Override
    19. public T getMapper(Class type) {
    20. return configuration.getMapper(type, this);
    21. }
    22. @Override
    23. public Configuration getConfiguration() {
    24. return configuration;
    25. }
    26. }

    DefaultSqlSessionFactory类:此类更改为,在执行openSession()时创建执行器,并将执行器传给DefaultSqlSession ,用心感受设计,很巧妙,职责划分以后我们有变动的地方几乎很小的改动就能完成很多的功能

    1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
    2. private final Configuration configuration;
    3. public DefaultSqlSessionFactory(Configuration configuration) {
    4. this.configuration = configuration;
    5. }
    6. @Override
    7. public SqlSession openSession() {
    8. Transaction tx = null;
    9. try {
    10. final Environment environment = configuration.getEnvironment();
    11. TransactionFactory transactionFactory = environment.getTransactionFactory();
    12. tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED, false);
    13. // 创建执行器
    14. final Executor executor = configuration.newExecutor(tx);
    15. // 创建DefaultSqlSession
    16. return new DefaultSqlSession(configuration, executor);
    17. } catch (Exception e) {
    18. try {
    19. assert tx != null;
    20. tx.close();
    21. } catch (SQLException ignore) {
    22. }
    23. throw new RuntimeException("Error opening session. Cause: " + e);
    24. }
    25. }
    26. }

    准备测试:

    mybatis-config-datasource.xml

    1. <configuration>
    2. <environments default="development">
    3. <environment id="development">
    4. <transactionManager type="JDBC"/>
    5. <dataSource type="POOLED">
    6. <property name="driver" value="com.mysql.jdbc.Driver"/>
    7. <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
    8. <property name="username" value="root"/>
    9. <property name="password" value="root"/>
    10. dataSource>
    11. environment>
    12. environments>
    13. <mappers>
    14. <mapper resource="mapper/User_Mapper.xml"/>
    15. mappers>
    16. configuration>

    User_Mapper.xml

    1. "1.0" encoding="UTF-8"?>
    2. mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. <mapper namespace="cn.bugstack.mybatis.test.dao.IUserDao">
    4. <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="cn.bugstack.mybatis.test.po.User">
    5. SELECT id, userId, userName, userHead
    6. FROM user
    7. where id = #{id}
    8. select>
    9. mapper>

    单元测试:这就是职责功能拆分的好处,就是外部调用时没有改内容,但内部已经发生很大的变动

    1. @Test
    2. public void test_SqlSessionFactory() throws IOException {
    3. // 1. 从SqlSessionFactory中获取SqlSession
    4. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    5. SqlSession sqlSession = sqlSessionFactory.openSession();
    6. // 2. 获取映射器对象
    7. IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    8. // 3. 测试验证
    9. User user = userDao.queryUserInfoById(1L);
    10. logger.info("测试结果:{}", JSON.toJSONString(user));
    11. }

     还是一样的结果

  • 相关阅读:
    Java错题归纳day20
    【学习总结】SpringBoot中使用单例模式+ScheduledExecutorService实现异步多线程任务(若依源码学习)
    美团点评CAT部署了各种环境不下10次,遇到的坑整理
    kafka入门03——简单实战
    centos7.9安装X2go
    QT -大小写转换-QPushButton-setGeometry-QLineEdit
    第十二章: 项目采购管理
    网页JS自动化脚本(一)安装油猴或暴力猴等脚本管理器并新建脚本
    CleanMyMac X优秀首选第三方mac清理软件
    【Python基础】多值参数 || 计算多个数字的和 || 元组和字典的拆包 || 面向过程开发 || 面向对象基本概念:类和对象的关系、大驼峰命名法
  • 原文地址:https://blog.csdn.net/dfBeautifulLive/article/details/127511124