mybatis原理之手写mybatis框架(三)手写执行流程

手写mybatis执行流程

上一章将解析流程写完了,这一章我们将手写mybatis的执行流程。
在第一章节中,通过mybatis架构分析,我们知道SqlSession接口是mybatis在接口层专门给开发人员提供的对外操作接口,通过SqlSession接口开发人员就可以完成对数据库CRUD,所以执行流程的入口就是SqlSession,接下来我们将执行流程分解成两个子流程讲解并编写:

  • 一个是 SqlSession的构建流程
  • 一个是SqlSession的内部执行流程

1. SqlSession的构建流程

先来回顾一下mybatis的整体流程,看下面一张图:
在这里插入图片描述
从图中我们可以看到,SqlSession是通过SqlSessionFactory工厂获取的,图中其实少了一步,SqlSessionFactory本身的构建是通过SqlSessionFactoryBuilder构建的,所以流程是按照下面步骤来的:

  • 配置文件 => SqlSessionFactoryBuilder => SqlSessionFactory => SqlSession

先看第一个类SqlSessionFactoryBuilder:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 11:43 上午
 * @Version 1.0
 */
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws Exception {
        XmlConfigurationBuilder xmlConfigurationBuilder = new XmlConfigurationBuilder(inputStream);
        Configuration configuration = xmlConfigurationBuilder.parse();
        return build(configuration);
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }
}

很简单,build方法接收核心配置文件的输入流,然后使用我们在解析流程中说过的XmlConfigurationBuilder对配置文件进行解析,获取Configuration对象,然后交给SqlSessionFactory的实现类,就完成了会话工厂的创建。

再看SqlSessionFactory,本身是一个接口:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 11:41 上午
 * @Version 1.0
 */
public interface SqlSessionFactory {
    SqlSession openSession();
}

提供一个获取SqlSession的方法。DefaultSqlSessionFactory是我们提供的一个默认实现类:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 1:47 下午
 * @Version 1.0
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType());
    }

    private SqlSession openSessionFromDataSource(ExecutorType executorType) {
        Connection connection = null;
        try {
            connection = configuration.getEnvironment().getDataSource().getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        Executor executor = configuration.newExecutor(executorType,connection);
        return new DefaultSqlSession(configuration, executor);

    }
}

需要关注这么几点:
1.return new DefaultSqlSession(configuration, executor)
DefaultSqlSession是我们提供的一个SqlSession默认实现类,我们知道SqlSession只是mybatis对外提供的操作接口,目的只是为了给开发者提供一套方便的API,底层真正执行Sql的操作是依靠Executor执行器的,所以SqlSession的构建,需要依赖执行器,和Configuration对象,一个用来执行,一个用来获取需要执行的MappedStatment信息。

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    private Executor executor;

    public DefaultSqlSession(Configuration configuration, Executor executor) {
    	//configuration用来获取sql等信息
        this.configuration = configuration;
        //真正执行sql的执行器
        this.executor = executor;
    }
    //其他暂时不关注...

2.Executor executor = configuration.newExecutor(executorType,connection);
执行器专门执行增删改查的,那么它最终要执行Sql,肯定需要知道数据库的连接对象,即Connection对象,所以构建Executor的时候暂时只传数据库连接对象即可(因为我们是简单手写,不支持事务,而在源码中连接是放在事务对象中进行管理的)。
另外我们知道Executor接口有两个实现,一个是基础执行器BaseExecutor(是一个抽象类,采用模板方法设计模式,提供了一级缓存功能,基础执行器下具体实现有简单执行器、可重用执行器、批量执行器…)、一个是缓存执行器CachingExecutor,专门提供二级缓存功能,而executorType是专门指定初始化哪种基本执行器的:

/**
 * @Author yangtianhao
 * @Date 2020/2/5 5:28 下午
 * @Version 1.0
 */
public class Configuration {
	//默认简单执行器
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    //默认支持二级缓存
    protected boolean cacheEnabled = true;
    //省略了一些不需要关注的代码....
	
	//创建执行器
    public Executor newExecutor(ExecutorType executorType, Connection connection) {
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
            executor = new BatchExecutor(this, connection);
        } else {
            executor = new SimpleExecutor(this, connection);
        }
        if (cacheEnabled) {
            executor = new CatchingExecutor(executor);
        }
        return executor;

    }
	//获取默认的执行器类型
    public ExecutorType getDefaultExecutorType() {
        return defaultExecutorType;
    }
	//其他暂不关注.....

可以看到executorType专门控制底层用哪种BaseExecutor,它有三种实现,SimpleExecutor,ReuseExecutor,BatchExecutor,我们只实现了一种简单执行器SimpleExecutor,而CachingExecutor是专门提供二级缓存功能的,看到cacheEnabled为true,则用CachingExecutor装饰BaseExecutor,这个是装饰设计模式。

那么通过SqlSessionFactoryBuilder加载配置文件,构建SqlSessionFactory,再通过SqlSessionFactory获取SqlSession,整个构建过程就是这样。

2. SqlSession的内部执行流程

SqlSession的构建过程说完,我们继续讲解SqlSession内部的执行流程,先看下面一张图:
在这里插入图片描述
这张图完美的把SqlSession内部的组件,以及各个组件之间的交互,所负责的功能展现了出来,可以看到,SqlSession是Mybatis工作的顶层外部接口,底层依靠Executor执行器来执行数据库增删改查,Executor执行器在执行过程中,又把内部执行过程中的各个重要步骤或者职责委托给了其他的组件(类或者接口),比如:

  • 获取Sql,先从Configuration对象中获取指定的MappedStatment对象,再从MappedStatment对象中获取对应的SqlSource,最终调用SqlSource的getBoundSql方法,直接返回封装了可执行的Sql和参数映射信息的BoundSql对象,而SqlSource.getBoundSql底层会对Sql进行解析,这个在之前解析流程有说过。
  • 获取Sql后,需要对Sql生成JDBC的Statement对象,并对Statment设置参数,执行查询或更新等,这一系列对Statment的操作,又委托给了StatmentHandler接口,针对不同的Statment,有不同的StatmentHandler实现去处理。
    • SimpleStatementHandler:简单语句处理器(STATEMENT)
    • PreparedStatementHandler:预处理语句处理器(PREPARED)
    • CallableStatementHandler:存储过程语句处理器(CALLABLE)
  • StatmentHandler在进行具体的为Statment设置参数的时候,又委托给了ParameterHandler处理(JDBC中的简单Statement是不需要StatmentHandler处理参数的,因为它在SqlSource解析过程中已经处理好了)
  • StatmentHandler在执行完Sql拿到结果集后,需要对结果集处理封装成java集合,这个职责委托给了ResultSetHandler
  • 另外在处理参数和结果集的时候,java数据类型和jdbc数据类型是需要相互转换的,转换的职责就交给了TypeHandler

接下来我们按照流程从上往下依次编写:

1. SqlSession

/**
* @Author yangtianhao
* @Date 2020/2/18 11:34 上午
* @Version 1.0
*/
public interface SqlSession {
   //statement代表的是MappedStatment的Id
   <T> T selectOne(String statement, Object parameter);

   <E> List<E> selectList(String statement, Object parameter);

   int insert(String statement, Object parameter);
}

因为我们mapper映射文件里的sql标签只支持insert和select,所以SqlSession目前就定义一个查询和插入。statement代表的是MappedStatment的Id。
提供一个默认实现类:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 11:37 上午
 * @Version 1.0
 */
public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;
    private Executor executor;
	//需要通过Configuration获取需要执行的MappedStatment
	//底层具体执行增删改差,依靠Executor
    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<T> objects = this.selectList(statement, parameter);
        if (CollectionUtils.isNotEmpty(objects)) {
            return objects.get(0);
        }
        return null;
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
    	//从configuration中根据Id获取对应的MappedStatment
        MappedStatement ms = configuration.getMappedStatment(statement);
        if (ms == null) {
            return null;
        }
        try {
        	//最终执行还是依靠执行器
            return executor.query(ms, parameter);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public int insert(String statement, Object parameter) {
        MappedStatement ms = configuration.getMappedStatment(statement);
        if (ms == null) {
            return 0;
        }
        try {
            return executor.update(ms, parameter);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }
}

代码中可以看出,SqlSession只干了两件事:

  • 从configuration中获取MappedStatment
  • 将MappedStatment传给执行器处理

2. Executor

我们再看Executor:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 11:46 上午
 * @Version 1.0
 */
public interface Executor {
    int update(MappedStatement ms, Object parameter) throws SQLException;

    <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException;

    <E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException;
}

主要是两个方法,update用来执行增删改,query用来执行查询。
我们知道Executor有两个实现类,一个BaseExecutor,一个CachingExecutor,先看BaseExecutor:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:01 下午
 * @Version 1.0
 */
public abstract class BaseExecutor implements Executor {
    protected Configuration configuration;
    protected Connection connection;

    public BaseExecutor(Configuration configuration, Connection connection) {
        this.configuration = configuration;
        this.connection = connection;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return this.query(ms, parameter, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException {
        // 一级缓存处理 TODO
        // 根据boundSql,parameter等生成一个唯一的缓存key,拿着key去缓存中找
		// 如果缓存中有结果,直接返回结果,否则继续执行queryFronDatabase从数据库中查询
        return queryFromDatabase(ms, boundSql, parameter);
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return doUpdate(ms, parameter);

    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, BoundSql boundSql, Object parameter) {
        return doQuery(ms, boundSql, parameter);
    }

    protected abstract <E> List<E> doQuery(MappedStatement ms, BoundSql boundSql, Object parameter);

    protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
}

这是一个抽象类,采用模板设计模式,目的是让继承该类的执行器都具备一级缓存的功能,仔细看query方法,因为是简写,没有实现缓存,用TODO标识了,在这个地方可以统一做一级缓存处理,如果查询到直接返回,否则走queryFromDatabase方法,从数据库查询,在看queryFromDatabase方法,会走doQuery方法,该方法需要在子类中实现,所以如果从数据库查询,需要具体子类去实现,更新也是也一样的,具体实现走doUpdate方法,它有如下几个实现:

  • SimpleExecutor 简单执行器
  • BatchExecutor 批处理执行器
  • ReuseExecutor 可重用执行器

我们只实现了SimpleExecutor:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 1:49 下午
 * @Version 1.0
 */
public class SimpleExecutor extends BaseExecutor {

    public SimpleExecutor(Configuration configuration, Connection connection) {
        super(configuration, connection);
    }

    @Override
    protected <E> List<E> doQuery(MappedStatement ms, BoundSql boundSql, Object parameter) {
        StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);
        try {
            Statement statement = statmentHandler.prepare(connection);
            statmentHandler.parameterize(statement);
            return statmentHandler.query(statement);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);
        Statement statement = statmentHandler.prepare(connection);
        statmentHandler.parameterize(statement);
        return statmentHandler.update(statement);
    }
}

代码中可以看到,得到boundSql后,生成statment,对statment设置参数,执行stament都是委托给StatmentHandler处理的,StatmentHandler后面会说。

再看CatchingExecutor:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 2:02 下午
 * @Version 1.0
 */
public class CatchingExecutor implements Executor {
    private Executor delegate;

    public CatchingExecutor(Executor delegate) {
        this.delegate = delegate;
    }

    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        return delegate.update(ms, parameter);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return this.query(ms, parameter, boundSql);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException {
        // 二级缓存 TODO
        // 根据boundSql,parameter等生成一个唯一的缓存key,拿着key去缓存中找
		// 如果缓存中有结果,直接返回结果,否则执行装饰的基本执行器
        return delegate.query(ms, parameter, boundSql);
    }
}

很简单,用的装饰模式,在构造的时候传入一个被装饰的执行器即可,具体传的就是上面说的BaseExecutor的子类,在执行查询的时候,先查询二级缓存,有的话直接返回结果,否则执行被装饰的那个执行器,即delegate。

所有执行器讲完了,那么是什么时候构建执行器的呢?
其实刚开始已经讲过了,在构建SqlSession的时候,就会对执行器初始化:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 1:47 下午
 * @Version 1.0
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType());
    }

    private SqlSession openSessionFromDataSource(ExecutorType executorType) {
        Connection connection = null;
        try {
            connection = configuration.getEnvironment().getDataSource().getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        Executor executor = configuration.newExecutor(executorType,connection);
        return new DefaultSqlSession(configuration, executor);

    }
}

openSessionFromDataSource()方法中可以看到,初始化就是在Executor executor = configuration.newExecutor(executorType,connection);这一行
再看Configuration:

/**
 * @Author yangtianhao
 * @Date 2020/2/5 5:28 下午
 * @Version 1.0
 */
public class Configuration {
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
    protected boolean cacheEnabled = true;
  	//省略其他不需要关注的....

    public Executor newExecutor(ExecutorType executorType, Connection connection) {
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
        	//简单手写,只实现了SimpleExecutor,这边为了演示
            executor = new BatchExecutor(this, connection);
        } else {
            executor = new SimpleExecutor(this, connection);
        }
        if (cacheEnabled) {
            executor = new CatchingExecutor(executor);
        }
        return executor;

    }

    public ExecutorType getDefaultExecutorType() {
        return defaultExecutorType;
    }
}

configuration的newExecutor会根据传入的ExecutorType选择创建哪种BaseExecutor,默认就是SimpleExecutor简单执行器,另外二级缓存也是默认开启的,如果开启,就创建CatchingExecutor装饰基本执行器。

3. StatmentHandler

首先看下StatmentHandler接口:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 4:45 下午
 * @Version 1.0
 */
public interface StatmentHandler {
    // 准备语句
    Statement prepare(Connection connection) throws SQLException;

    // 参数化
    void parameterize(Statement statement) throws SQLException;

    // update
    int update(Statement statement) throws SQLException;

    // select
    <E> List<E> query(Statement statement) throws SQLException;
}

我们定义了4个方法(源码不只四个,我们需求只需要4个):

  • prepare:创建Statment
  • parameterize:对Statment设置参数
  • update:执行statment的update
  • query:执行statment的query

针对JDBC有三种类型Statment,对应有三个实现类处理各自的Statment:

  • SimpleStatementHandler 简单语句处理器(STATEMENT)
  • PreparedStatementHandler 预处理语句处理器(PREPARED)
  • CallableStatementHandler 存储过程语句处理器(CALLABLE)

我们只实现了一种PreparedStatmentHanlder:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:17 下午
 * @Version 1.0
 */
public class PreparedStatementHandler implements StatmentHandler {

    private BoundSql boundSql;
    private ParameterHandle parameterHandle;
    private ResultSetHandler resultSetHandler;
    private Configuration configuration;

    public PreparedStatementHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
        this.boundSql = boundSql;
        this.configuration = mappedStatement.getConfiguration();
        this.parameterHandle = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = configuration.newResultSetHandler(mappedStatement);
    }

    @Override
    public Statement prepare(Connection connection) throws SQLException {
        String sql = boundSql.getSql();
        return connection.prepareStatement(sql);
    }

    @Override
    public void parameterize(Statement statement) throws SQLException {
        parameterHandle.setParameters((PreparedStatement)statement);
    }

    @Override
    public int update(Statement statement) throws SQLException {
        PreparedStatement preparedStatement = (PreparedStatement)statement;
        return preparedStatement.executeUpdate();
    }

    @Override
    public <E> List<E> query(Statement statement) throws SQLException {
        PreparedStatement preparedStatement = (PreparedStatement)statement;
        preparedStatement.executeQuery();
        return resultSetHandler.handleResultSets(preparedStatement);
    }
}

除了基本的对PreparedStatment的创建、执行外,可以发现在构造的时候,会通过conection对象初始化ParameterHandler和ResultSetHandler,并在对Statment设置参数,处理结果集起委托给这两个组件。

StatmentHandler讲完了,那么是在什么时候构造的呢?在上面讲SimpleExecutor的时候,当执行doQuery或者doUpdate的时候我们能看到StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);,就是在这个时候构造的,并且也是在Connection中获取的:

public class Configuration {

    //省略不关注的部分...

    public StatmentHandler newStatementHandler(MappedStatement ms, Object parameter, BoundSql boundSql) {
        String statementType = ms.getStatementType();
        StatmentHandler statmentHandler = null;
        switch (statementType) {
            case "prepared":
                statmentHandler = new PreparedStatementHandler(boundSql, parameter, ms);
                break;
            default:
			//比较简单,我们只实现了一种
        }
        return statmentHandler;
    }
}

4. ParameterHandler

StatmentHandler在执行parameterize方法的时候,是委托给parameterHandler处理的,我们看下ParameterHandler接口:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:25 下午
 * @Version 1.0
 */
public interface ParameterHandle {
    // 设置参数
    void setParameters(PreparedStatement ps) throws SQLException;
}

有一个默认的实现类:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:26 下午
 * @Version 1.0
 */
public class DefaultParameterHandler implements ParameterHandle {
    private BoundSql boundSql;
    private Object parameterObject;
    private MappedStatement mappedStatement;
    private Configuration configuration;

    public DefaultParameterHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
        this.boundSql = boundSql;
        this.parameterObject = parameterObject;
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
    }

    @Override
    public void setParameters(PreparedStatement ps) throws SQLException {

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (CollectionUtils.isNotEmpty(parameterMappings)) {
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                String propertyName = parameterMapping.getProperty();
                Object value = null;
                if (parameterObject == null) {
                    value = null;
                } else if (SimpleTypeRegistry.isSimpleType(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    try {
                        // 写的比较简单,直接用反射取值,只支持一级
                        Field field = parameterObject.getClass().getDeclaredField(propertyName);
                        field.setAccessible(true);
                        value = field.get(parameterObject);
                    } catch (NoSuchFieldException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
                TypeHandler typeHandler = parameterMapping.getTypeHandler();
                JdbcType jdbcType = parameterMapping.getJdbcType();
                if (value == null && jdbcType == null) {
                    // 另外如果值为null,并且获取jdbcType也为null的话,从
                    // configuration中获取一个默认的jdbcType,让statment设置为NULL值。
                    jdbcType = configuration.getJdbcTypeForNull();
                }
                typeHandler.setParameter(ps, i + 1, value, jdbcType);
            }
        }
    }
}

只考虑了参数为null,简单数据类型,javaBean的情况,并且javaBean也只支持一级成员属性的处理(源码中对参数的处理是支持类似于ognl表达式那种类似的语法),整个流程是先从boundSql中获取参数映射列表信息List<ParameterMapping>,遍历处理,根据每个ParameterMapping的propertyName从传递的参数中取到值以后,再从ParameterMapping中获取指定的TypeHandler对Statment进行设值(TypeHandler是什么时候放入ParameterMapping的呢?其实它的初始化时机是在解析Sql的时候,即替换#{}为?的时候,但是在第二章中是没有讲的,等后面说TypeHandle会补上)

jdbcType = configuration.getJdbcTypeForNull();这段代码的目的,是因为在解析sql的时候,如果映射文件中的sql,没有配置"#{base_duration,jdbcType=INTEGER}",类似于这样用jdbcType属性指定JdbcType,那么ParameterMapping中的jdbcType就会为null,是为了处理这个情况的,如果值为null,并且获取jdbcType也为null的话,就从configuration中获取一个默认的jdbcType,让statment设置为NULL值需要这个JdbcType。

ParameterHandle也讲完了,它构造时机其实上面也讲过了,在PreparedStatementHandler实例化的时候,构造函数中会初始化一个ParameterHandler:

public class PreparedStatementHandler implements StatmentHandler {

    private BoundSql boundSql;
    private ParameterHandle parameterHandle;
    private ResultSetHandler resultSetHandler;
    private Configuration configuration;

    public PreparedStatementHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
        this.boundSql = boundSql;
        this.configuration = mappedStatement.getConfiguration();
        //构造参数处理器
        this.parameterHandle = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = configuration.newResultSetHandler(mappedStatement);
    }
}

看到也是在configuration中获取的:

public class Configuration {
  	//略不关注的....
   public ParameterHandle newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
       BoundSql boundSql) {
       return new DefaultParameterHandler(boundSql, parameterObject, mappedStatement);
   }
}

5. ResultSetHandler

StatmentHandler在执行query方法的时候,当statment执行完executeQuery后,会得到结果集,需要将其处理封装成java对象集合,这个是委托给ResultSetHandler处理的:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:28 下午
 * @Version 1.0
 */
public interface ResultSetHandler {
    // 处理结果集
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;
}

源码处理挺复杂的,因为涉及到ResultMap,ResultHandler等,还考虑了ResultMap层层嵌套等很多复杂情况,我们只是简单的实现,我们定义的映射配置文件中,insert和select标签是没有ResultMap属性的,只有resultType属性,而源码在处理resultType的时候,会将resultType解析成一个ResultMap,该ResultMap对象中只有id,和javaType(笔者发现在转换的时候,生成的ResultMap对象 只有一个Id,和javaType,如果有错误,希望读者可以指出),而ResultSetHandler在处理结果集的时候,就会获取这个ResultMap,并且会有两个流程,一个是自动映射,即基于属性名自动映射列到 JavaBean 的属性上,再根据resultMap的propertyMapping信息进行指定的属性映射。这个我们会在TypeHandler中讲解。

先看下默认实现类:

/**
 * @Author yangtianhao
 * @Date 2020/2/18 6:32 下午
 * @Version 1.0
 */
public class DefaultResultSetHandler implements ResultSetHandler {

    private MappedStatement mappedStatement;
    private Configuration configuration;

    public DefaultResultSetHandler(MappedStatement mappedStatement) {
        this.mappedStatement = mappedStatement;
        this.configuration = mappedStatement.getConfiguration();
    }

    @Override
    public List<Object> handleResultSets(Statement stmt) throws SQLException {
        List<Object> result = new ArrayList<>();
        // 获取对应的ResultMap
        ResultMap resultMap = mappedStatement.getResultMap();
        ResultSet resultSet = stmt.getResultSet();

        while (resultSet.next()) {
            try {
                Object resultObject = resultMap.getType().newInstance();
                // 自动映射,基于属性名自动映射列到 JavaBean 的属性上
                applyAutomaticMappings(resultObject, resultSet);

                if (CollectionUtils.isNotEmpty(resultMap.getResultMappings())) {
                    // 处理有映射关系的
                    for (ResultMapping propertyMapping : resultMap.getResultMappings()) {
                        String property = propertyMapping.getProperty();
                        TypeHandler typeHandler = propertyMapping.getTypeHandler();
                        String column = propertyMapping.getColumn();
                        Object value = typeHandler.getResult(resultSet, column);

                        Field declaredField = resultObject.getClass().getDeclaredField(property);
                        declaredField.setAccessible(true);
                        declaredField.set(resultObject, value);
                    }
                }

                result.add(resultObject);
            } catch (Exception e) {

            }
        }

        return result;
    }
	//自动映射,根据数据库列名,自动映射到javaType属性名一样的字段
    private void applyAutomaticMappings(Object resultObject, ResultSet resultSet) throws SQLException {
        ResultSetMetaData rsm = resultSet.getMetaData(); // 获得列集
        int col = rsm.getColumnCount(); // 获得列的个数
        String colName[] = new String[col];
        // 取结果集中的表头名称, 放在colName数组中
        for (int i = 0; i < col; i++) {
            colName[i] = rsm.getColumnName(i + 1);
        }
        for (String s : colName) {
            try {
                Field declaredField = resultObject.getClass().getDeclaredField(s);
                declaredField.setAccessible(true);
                Class<?> type = resultObject.getClass().getDeclaredField(s).getType();
                TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(type);
                Object value = typeHandler.getResult(resultSet, s);
                declaredField.set(resultObject, value);
            } catch (NoSuchFieldException e) {
            	//简单处理了,不打印报错信息,看的难受
                //e.printStackTrace();
            } catch (IllegalAccessException e) {
                //e.printStackTrace();
            }

        }

    }
}

上面代码演示处理结果集映射时,会先进行自动映射,在进行属性映射两个过程,做的事情和源码是一样的,但是考虑的远没有源码全面。重在理解吧。

6. TypeHandler

在StatmentHanlder对Statment设置参数,处理返回的结果集的时候,需要在javaType和JdbcType之间的进行转换,数据才能正确显示,这个职责就委托给了TypeHandler。

先看下接口:

/**
 * @Author yangtianhao
 * @Date 2020/2/26 9:38 下午
 * @Version 1.0
 */
public interface TypeHandler<T> {
    // 设置参数
    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    // 取得结果,供普通select用
    T getResult(ResultSet rs, String columnName) throws SQLException;

}

两个方法,一个对Statment进行设值,一个从结果集取值,泛型T就对应的JavaType,JdbcType就是对应的数据库的数据类型。

对于JdbcType,我们直接拷贝源码, 很简单,就是对java.sql.Type包装了一层,而java.sql.Type就是对应的数据库数据类型:

/**
 * @Author yangtianhao
 * @Date 2020/2/26 9:40 下午
 * @Version 1.0
 */
public enum JdbcType {

    // 就是包装一下java.sql.Types
    ARRAY(Types.ARRAY), BIT(Types.BIT), TINYINT(Types.TINYINT), SMALLINT(Types.SMALLINT), INTEGER(Types.INTEGER),
    BIGINT(Types.BIGINT), FLOAT(Types.FLOAT), REAL(Types.REAL), DOUBLE(Types.DOUBLE), NUMERIC(Types.NUMERIC),
    DECIMAL(Types.DECIMAL), CHAR(Types.CHAR), VARCHAR(Types.VARCHAR), LONGVARCHAR(Types.LONGVARCHAR), DATE(Types.DATE),
    TIME(Types.TIME), TIMESTAMP(Types.TIMESTAMP), BINARY(Types.BINARY), VARBINARY(Types.VARBINARY),
    LONGVARBINARY(Types.LONGVARBINARY), NULL(Types.NULL), OTHER(Types.OTHER), BLOB(Types.BLOB), CLOB(Types.CLOB),
    BOOLEAN(Types.BOOLEAN), CURSOR(-10), // Oracle
    UNDEFINED(Integer.MIN_VALUE + 1000),
    // 太周到了,还考虑jdk5兼容性,jdk6的常量都不是直接引用
    NVARCHAR(Types.NVARCHAR), // JDK6
    NCHAR(Types.NCHAR), // JDK6
    NCLOB(Types.NCLOB), // JDK6
    STRUCT(Types.STRUCT);

    public final int TYPE_CODE;
    private static Map<Integer, JdbcType> codeLookup = new HashMap<Integer, JdbcType>();

    // 一开始就将数字对应的枚举型放入hashmap
    static {
        for (JdbcType type : JdbcType.values()) {
            codeLookup.put(type.TYPE_CODE, type);
        }
    }

    JdbcType(int code) {
        this.TYPE_CODE = code;
    }

    public static JdbcType forCode(int code) {
        return codeLookup.get(code);
    }

}

因为我们测试的表只涉及varchar,datatime,bigint,char,所以我们就实现4个TypeHandler。

  • BaseTypeHandler 基础handler,提供处理null的功能
    • ObjectTypeHandler 不知道类型的时候就用Object
    • StringTypeHandler 处理varchar和char
    • IntegerTypeHandler 处理bigint
    • DateTypeHandler 处理datatime

我们测试的表:

CREATE TABLE `user` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `creator` varchar(255) DEFAULT 'system' COMMENT '创建者',
  `creation_time` datetime DEFAULT NULL COMMENT '创建时间',
  `modifier` varchar(255) DEFAULT 'system' COMMENT '修改者',
  `modification_time` datetime DEFAULT NULL COMMENT '修改时间',
  `is_delete` char(1) DEFAULT 'n' COMMENT '是否删除',
  `name` varchar(255) DEFAULT NULL COMMENT '用户姓名',
  `gender` char(1) DEFAULT NULL COMMENT '性别',
  `phone` varchar(255) DEFAULT NULL COMMENT '电话',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

先抽象一个BaseTypeHandler,用来处理NULL的情况:

/**
 * @Author yangtianhao
 * @Date 2020/2/26 11:32 下午
 * @Version 1.0
 */
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {

    @Override
    public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // 特殊情况,设置NULL
        if (parameter == null) {
            if (jdbcType == null) {
                // 如果没设置jdbcType,报错啦
                throw new RuntimeException(
                    "JDBC requires that the JdbcType must be specified for all nullable parameters.");
            }
            try {
                // 设成NULL
                ps.setNull(i, jdbcType.TYPE_CODE);
            } catch (SQLException e) {
                throw new RuntimeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType
                    + " . "
                    + "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
                    + "Cause: " + e, e);
            }
        } else {
            // 非NULL情况
            setNonNullParameter(ps, i, parameter, jdbcType);
        }
    }

    @Override
    public T getResult(ResultSet rs, String columnName) throws SQLException {
        T result = getNullableResult(rs, columnName);
        // 通过ResultSet.wasNull判断是否为NULL
        if (rs.wasNull()) {
            return null;
        } else {
            return result;
        }
    }

    // 不为null就设置参数,通过具体子类去实现
    protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
        throws SQLException;

    // 取得可能为null的结果,通过具体子类去实现
    public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
}

不知道类型的时候就用Object的处理方式:

/**
 * @Author yangtianhao
 * @Date 2020/2/27 12:27 上午
 * @Version 1.0
 */
public class ObjectTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
        throws SQLException {
        ps.setObject(i, parameter);
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getObject(columnName);
    }
}

处理varchar和char :

/**
 * @Author yangtianhao
 * @Date 2020/2/26 11:39 下午
 * @Version 1.0
 */
public class StringTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
        throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }
}

处理bigint:

/**
 * @Author yangtianhao
 * @Date 2020/2/26 11:40 下午
 * @Version 1.0
 */
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
        throws SQLException {
        ps.setInt(i, parameter);
    }

    @Override
    public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getInt(columnName);
    }
}

处理datetime:

/**
 * @Author yangtianhao
 * @Date 2020/2/26 11:41 下午
 * @Version 1.0
 */
public class DateTypeHandler extends BaseTypeHandler<Date> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
        throws SQLException {
        ps.setTimestamp(i, new Timestamp((parameter).getTime()));
    }

    @Override
    public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp sqlTimestamp = rs.getTimestamp(columnName);
        if (sqlTimestamp != null) {
            return new Date(sqlTimestamp.getTime());
        }
        return null;
    }
}

具体实现有了,那么typeHandler是在什么时候注册的呢?又是如何和JavaType以及JdbcType绑上关系的呢?其实就是在实例化Configuration的时候,并且由TypeHandlerRegistry对象负责管理:

public class Configuration {
	//省略不需要关注的代码.....
    // 类型处理器注册机
    protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
    
    public TypeHandlerRegistry getTypeHandlerRegistry() {
        return typeHandlerRegistry;
    }
}

看到在实例化Configuration对象的时候,就会实例化一个TypeHandlerRegistry对象,而在TypeHandlerRegistry对象构造的时候,会在构造方法中对TypeHandler进行注册,并绑定对和javaType、jdbcType的关系:

/**
 * @Author yangtianhao
 * @Date 2020/2/27 12:03 上午
 * @Version 1.0
 */
public final class TypeHandlerRegistry {

    // 枚举型map
    private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP =
        new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
    private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP =
        new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
    private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

    public TypeHandlerRegistry() {
        // 构造函数里注册系统内置的类型处理器
        // 以下是为多个类型注册到同一个handler

        register(Integer.class, new IntegerTypeHandler());
        register(int.class, new IntegerTypeHandler());
        register(Integer.class, JdbcType.INTEGER, new IntegerTypeHandler());
        register(int.class, JdbcType.INTEGER, new IntegerTypeHandler());

        // 以下是为同一个类型的多种变种注册到多个不同的handler
        register(String.class, new StringTypeHandler());
        register(String.class, JdbcType.CHAR, new StringTypeHandler());
        register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
        register(JdbcType.CHAR, new StringTypeHandler());
        register(JdbcType.VARCHAR, new StringTypeHandler());

        register(Date.class, new DateTypeHandler());
        register(JdbcType.TIMESTAMP, new DateTypeHandler());

        register(Object.class, new ObjectTypeHandler());
        register(Object.class, JdbcType.OTHER, new ObjectTypeHandler());

    }

    public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
        return getTypeHandler((Type)type, null);
    }

    public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
        return getTypeHandler((Type)type, jdbcType);
    }

    @SuppressWarnings("unchecked")
    private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
        Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
        TypeHandler<?> handler = null;
        if (jdbcHandlerMap != null) {
            handler = jdbcHandlerMap.get(jdbcType);
            if (handler == null) {
                handler = jdbcHandlerMap.get(null);
            }
        }
        return (TypeHandler<T>)handler;
    }

    public void register(JdbcType jdbcType, TypeHandler<?> handler) {
        JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
    }

    // java type + handler

    public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
        register((Type)javaType, typeHandler);
    }

    private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {

        register(javaType, null, typeHandler);

    }

    // java type + jdbc type + handler

    public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
        register((Type)type, jdbcType, handler);
    }

    private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
        if (javaType != null) {
            Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
            if (map == null) {
                map = new HashMap<JdbcType, TypeHandler<?>>();
                TYPE_HANDLER_MAP.put(javaType, map);
            }
            map.put(jdbcType, handler);
        }
        ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
    }

}

注册完以后,typeHandler又是在什么时候使用的呢?上面已经说过了,在ParameterHandler和ResultSetHandler处理参数和结果集的时候,会委托typeHandler进行设值和取值,我们具体分析一下两处的处理。

ParameterHandler处理参数的时候,从boundSql中获取ParameterMapping的集合,然后遍历处理,每次再从parameterMapping拿到TypeHandler,那么这个TypeHandler是怎么来的呢?其实是在解析流程的时候放进去的,但是在第二章我们并没有做这件事,现在我们补上,对原来代码进行改造。

当我们用SqlSource获取boundSql的时候,sql会经历拼接和解析两个过程,而在解析的时候,会将sql中对应的参数信息映射成ParameterMapping对象,也就是在这个时候,处理了TypeHandler,所以我们对关键代码进行改造,即SqlSourceParser(改造SqlSourceParser后,带来的代码其他影响就不一一展示了,主要也就是构造传入的参数多了后引起上层代码变动):

public class SqlSourceParser {

    private Configuration configuration;

    public SqlSourceParser(Configuration configuration) {
        this.configuration = configuration;
    }

    public SqlSource parse(String sql, Class<?> parameterType) {
        ParameterMappingTokenHandler parameterMappingTokenHandler =
            new ParameterMappingTokenHandler(configuration, parameterType);
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappingList = parameterMappingTokenHandler.getParameterMappingList();
        return new StaticSqlSource(parseSql, parameterMappingList);
    }

    public class ParameterMappingTokenHandler implements TokenHandler {
        private List<ParameterMapping> parameterMappingList = new ArrayList<>();
        private Class<?> parameterType;
        private Configuration configuration;

        public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType) {
            this.configuration = configuration;
            this.parameterType = parameterType;
        }

        @Override
        public String handleToken(String content) {
            String property = content;
            // 像这样#{base_duration,jdbcType=INTEGER}里面配置了jdbcType,那么可以获取,并赋值,我们就不处理了
            JdbcType jdbcType = null;
            Class<?> propertyType;
            if (parameterType == null) {
                propertyType = Object.class;
            } else if (SimpleTypeRegistry.isSimpleType(parameterType)) {
                propertyType = parameterType;
            } else {
                try {
                    // 简单处理,只支持一级
                    Field declaredField = parameterType.getClass().getDeclaredField(content);
                    propertyType = declaredField.getType();
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                    propertyType = Object.class;
                }
            }
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
            parameterMappingList.add(new ParameterMapping(property, parameterType, jdbcType, typeHandler));
            return "?";
        }

        public List<ParameterMapping> getParameterMappingList() {
            return parameterMappingList;
        }
    }
}

重点关注ParameterMappingTokenHandler.handleToken方法,当然我们的处理考虑的不是很全面,只支持基本数据类型,或者java类型的一级,重在理解就可以了。可以看到,获取指定参数的类型后,在去typeHandlerRegistry获取对应的typeHandler进行赋值。

再看ResultSetHandler处理的时候,上面DefaultResultSetHandler已经演示过了,主要是先获取MappedStatment中的ResultMap,然后遍历ResultMap中的ResultMapping集合,每个ResultMapping都维护了参数映射关系,并且也维护了一个参数类型对应的TypeHandler,拉取部分代码:

if (CollectionUtils.isNotEmpty(resultMap.getResultMappings())) {
        // 处理有映射关系的
        for (ResultMapping propertyMapping : resultMap.getResultMappings()) {
            String property = propertyMapping.getProperty();
            //获取TypeHandler
            TypeHandler typeHandler = propertyMapping.getTypeHandler();
            String column = propertyMapping.getColumn();
            Object value = typeHandler.getResult(resultSet, column);

            Field declaredField = resultObject.getClass().getDeclaredField(property);
            declaredField.setAccessible(true);
            declaredField.set(resultObject, value);
        }
}

另外想讲的是我们的映射文件只有resultType,所以我们改造了XMLStatmentBuilder,在处理resultType属性的时候,会转换成resultMap:

/**
 * @Author yangtianhao
 * @Date 2020/2/13 3:09 下午
 * @Version 1.0
 */
public class XMLStatementBuilder {
    private Element statmentELement;
    private String namespace;
    private Configuration configuration;

    public XMLStatementBuilder(Element statmentElement, String namespace, Configuration configuration) {
        this.statmentELement = statmentElement;
        this.namespace = namespace;
        this.configuration = configuration;
    }

    public MappedStatement parseStatementNode() throws ClassNotFoundException {
        String id = namespace + "." + statmentELement.attributeValue("id");
        String parameterType = statmentELement.attributeValue("parameterType");
        String resultType = statmentELement.attributeValue("resultType");
        Class<?> aClass = Class.forName(resultType);
        // resultType转换resultMap,主要生成一个id以及获取Type即可.
        ResultMap resultMap = new ResultMap(id + "-Inline", aClass);

        String statmentType = statmentELement.attributeValue("statmentType");
        XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(statmentELement, configuration, aClass);
        SqlSource sqlSource = xmlScriptBuilder.parseScriptNode();
        return new MappedStatement(id, parameterType, resultType, sqlSource, statmentType, configuration, resultMap);
    }
}

其实此时是没有处理ResultMapping以及TypeHandler的,只有一个id和javaType,我看源码中也是这样就模仿了,而为什么没有ResultMapping也能完成映射处理,我想大概是因为当ResultSetHandler处理这个ResultMap的时候,会先进行自动映射的关系。当获取ResultMap的javaType进行自动映射时,会基于属性名自动映射列到 JavaBean 的属性上,这个时候每个属性都会根据它的数据类型,取对应的TypeHandler进行处理。

private void applyAutomaticMappings(Object resultObject, ResultSet resultSet) throws SQLException {
        ResultSetMetaData rsm = resultSet.getMetaData(); // 获得列集
        int col = rsm.getColumnCount(); // 获得列的个数
        String colName[] = new String[col];
        // 取结果集中的表头名称, 放在colName数组中
        for (int i = 0; i < col; i++) {
            colName[i] = rsm.getColumnName(i + 1);
        }
        for (String s : colName) {
            try {
                Field declaredField = resultObject.getClass().getDeclaredField(s);
                declaredField.setAccessible(true);
                Class<?> type = resultObject.getClass().getDeclaredField(s).getType();
                TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(type);
                Object value = typeHandler.getResult(resultSet, s);
                declaredField.set(resultObject, value);
            } catch (NoSuchFieldException e) {
                // e.printStackTrace();
            } catch (IllegalAccessException e) {
                // e.printStackTrace();
            }

        }

    }

到此全部编写完成,最后测试代码:
对应映射配置文件:

<?xml version="1.0" encoding="UTF-8" ?>
<mapper xmlns="http://www.aiduoduo.site/schema/mybatis-mapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.aiduoduo.site/schema/mybatis-mapper  site/aiduoduo/mybatis/builder/xml/xsd/mybatis-mapper.xsd"
        namespace="site.aiduoduo.user">


    <select id="selectAll" resultType="site.aiduoduo.mybatis.pojo.User" parameterType="site.aiduoduo.mybatis.pojo.User"
            statmentType="prepared">
        select * from user
    </select>

    <select id="selectByPhone" resultType="site.aiduoduo.mybatis.pojo.User"
            parameterType="site.aiduoduo.mybatis.pojo.User" statmentType="prepared">
        select * from user
        <where>
            <if test="phone != null">
                and phone = #{phone}
            </if>
        </where>
    </select>

    <insert id="insert" parameterType="site.aiduoduo.mybatis.pojo.User" statmentType="prepared"
            resultType="site.aiduoduo.mybatis.pojo.User">
        insert into user(name,gender,phone,address) values(#{name},#{gender},#{phone},#{address});
    </insert>
</mapper>
/**
 * @Author yangtianhao
 * @Date 2020/1/29 4:28 下午
 * @Version 1.0
 */
public class FrameworkTest {

    @Test
    public void test2() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config-schema.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user1 = new User();
        user1.setPhone("18115610044");
        User user = sqlSession.selectOne("site.aiduoduo.user.selectByPhone", user1);
        System.out.println(user);
    }

结果:
User{name='貂蝉', gender=0, phone='18115610044', address='山西', creation_time=Thu Feb 27 15:50:35 CST 2020}

猜你喜欢

转载自blog.csdn.net/weixin_41947378/article/details/104171291