手敲Mybatis(十)-完善ORM框架支持增删改查

我们把基本的功能都完成了,解析xml、构建映射代理、执行sql,解析处理结果,目前这些只支持查询,我们还差添加下增删改的功能,本章节就来完善下增删改,其实本章节比较简单,因为之前的每个章节都已设计好,所以这个章节主要在其基础上进行扩展以及少部分的修改即可。

要修改的有以下几点

1.在解析XML的时候,我们只解析了SELECT的资源,我们要加入也可以解析INSERT、UPDATE、DELETE、等的资源。

 2.SqlSession里也要加入insert、delete、update、的方法,供调用使用。

 3.再就是代理方法执行标签选择的时候,要执行哪个方法,也要加上增删改

 4.Sql执行器也要更改添加,唯一不同的是执行器只增加update即可,由于jdbc对于Sql的增删改操作都是修改,所以增删改的操作指用调用update就可以

 还有一些小细节都改动,咱们代码里细细说来。

1.代码实现

XMLMapperBuilder类里configurationElement方法里添加解析UPDATE、INSERT、DELETE、标签,buildStatementFromContext可接收多个参数,并添加for循环解析多个标签问题。

public class XMLMapperBuilder extends BaseBuilder {
// 省略其他方法

 private void configurationElement(Element element) {
        // 配置namespace
        String namespace = element.attributeValue("namespace");
        if (namespace.equals("")) {
            throw new RuntimeException("Mapper's namespace cannot be empty");
        }
        builderAssistant.setCurrentNamespace(namespace);
        // select,新增解析update、delete、insert
        buildStatementFromContext(element.elements("select"),
                element.elements("update"),
                element.elements("insert"),
                element.elements("delete"));
    }

private void buildStatementFromContext(List<Element>... lists) {
        for(List<Element> list:lists){
            for (Element element : list) {
                // 解析语句
                final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, element);
                statementParser.parseStatementNode();
            }
        }
    }
}

SqlSession类:除了添加更新、新增、删除还有查询集合数据方法以及提交事务方法的定义

public interface SqlSession {
    /**
     * Execute an update statement. The number of rows affected will be returned.
     * 更新记录
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the update. 返回的是受影响的行数
     */
    int update(String statement, Object parameter);

    /**
     * Execute an insert statement with the given parameter object. Any generated
     * autoincrement values or selectKey entries will modify the given parameter
     * object properties. Only the number of rows affected will be returned.
     * 插入记录,容许传入参数。
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the insert. 注意返回的是受影响的行数
     */
    int insert(String statement, Object parameter);

    /**
     * Execute a delete statement. The number of rows affected will be returned.
     * 删除记录
     *
     * @param statement Unique identifier matching the statement to execute.
     * @param parameter A parameter object to pass to the statement.
     * @return int The number of rows affected by the delete. 返回的是受影响的行数
     */
    Object delete(String statement, Object parameter);
   
    <E> List<E> selectList(String statement, Object parameter);

    /**
     * 以下是事务控制方法 commit,rollback
     * Flushes batch statements and commits database connection.
     * Note that database connection will not be committed if no updates/deletes/inserts 
       were called.
     */
    void commit();

    // 省略其他
}

 SqlSession的实现类,增删改都调用了update的方法,而update的方法最终也调用了Sql执行器的更新操作,除此之外添加了查询集合的方法(selectList),这样selectOne就可以直接调用selectList方法得到数据,并在自己的方法获取第一个数据返回即可。

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;
    private Executor executor;

    //private MapperRegistry mapperRegistry;
    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    @Override
    public int update(String statement, Object parameter) {
        // 查询要处理哪个mapper语句
        MappedStatement ms = configuration.getMappedStatement(statement);
        try {
            return executor.update(ms, parameter);
        } catch (SQLException e) {
            throw new RuntimeException("Error updating database.  Cause: " + e);
        }
    }

    @Override
    public int insert(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public Object delete(String statement, Object parameter) {
        return update(statement, parameter);
    }

    @Override
    public <T> T selectOne(String statement) {
        return this.selectOne(statement, null);
    }

    // 更改调用selectList最后返回第一个数据
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<T> list = this.<T>selectList(statement, parameter);
        try {
            if (list.size() == 0) {
                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;
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        System.out.println("执行查询 statement:" + statement + "parameter:" + 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);
        }
    }

    @Override
    public void commit() {
        try {
            executor.commit(true);
        } catch (SQLException e) {
            throw new RuntimeException("Error committing transaction.  Cause: " + e);
        }
    }
 // 省略其他
}

MapperMethod类的更改,想必都知道了,根据标签来调用具体的SqlSession的增删改查,没有这步的话也没办法执行Sql语句了,哈哈哈

public class MapperMethod {
   
    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.returnMany) {
                    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;
    }

    // 省略其他方法
}

Executor:执行器更改点需添加update方法支持用户对数据的更改(增删改)操作

public interface Executor {
    // 省略其他
    int update(MappedStatement ms, Object parameter)throws SQLException;
}

 BaseExecutor 实现了Executor类的update方法,并抽象出doUpdate方法,供不同的类执行逻辑

public abstract class BaseExecutor implements Executor {
 
    // 省略其他

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

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

}

SimpleExecutor类:此类实现doUpdate的方法,处理实例化语句处理器、获取连接、语句准备、参数设置、更新Sql等流程,其实也算是个模板模式思路。

public class SimpleExecutor extends BaseExecutor {

    @Override
    protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // 调用创建语句处理器-PreparedStatementHandler
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            Connection connection = transaction.getConnection();
            // 调用语句处理器-准备操作,如初始化参数
            stmt = handler.prepare(connection);
            // 设置参数
            handler.parameterize(stmt);
            // 调用语句处理器的更新方法
            return handler.update(stmt);

        } finally {
            closeStatement(stmt);
        }
    }
}

StatementHandler类:SimpleExecutor中是通过语句处理器执行Sql的,所以我们需要在语句处理器中定义update方法

public interface StatementHandler {
    // 省略其他...

    int update(Statement statement) throws SQLException;
}

BaseStatementHandler类:此类主要是SqlSession调用update方法时不传绑定的Sql,所以我们在构造参数里需要获取下BoundSql即可,不绑定执行会报错哦,毕竟没用Sql怎么执行呢!

 public BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 省略....

        // 新增判断,update不传参数
        if (boundSql == null) {
            boundSql = mappedStatement.getBoundSql(parameterObject);
        }
        this.boundSql = boundSql;
    }

SimpleStatementHandler:简单语句处理器,没有参数情况下的一种处理器,可直接调用JDBC的statement执行update方法。

public class SimpleStatementHandler extends BaseStatementHandler {

    // 省略其他...

    @Override
    public int update(Statement statement) throws SQLException {
        String sql = boundSql.getSql();
        return statement.executeUpdate(sql);
    }
}

PreparedStatementHandler类:预处理语句处理器,支持带参数的sql执行,可直接通过JDBC与预处理语句调用execute()方法来增删改查操作

public class PreparedStatementHandler extends BaseStatementHandler {


    // 省略其他....

    @Override
    public int update(Statement statement) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return ps.getUpdateCount();
    }
}

其实看到这里发现基本没有什么修改,而扩展居多,而在对于设计方面来说了最基本的原则也是对修改关闭,对扩展开放,我们在设计好的代码里面修改,思路就会很清晰,一点也不会乱,这就是我们需要学习和借鉴的代码设计能力。

2.单元测试

mapper.xml更改,添加标签INSERT,UPDATE,DELETE等sql语句

<mapper namespace="df.middleware.mybatis.dao.IUserDao">

    <!--<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userHead,userName
        FROM user
        where id = #{id}
    </select>-->
    <update  id="updateById" parameterType="java.lang.Long">
        update user set userName='df_lucky_forever' where id = #{id}
    </update>

    <update  id="insert" parameterType="df.middleware.mybatis.po.User" >
        INSERT INTO user
        (userId, userName, userHead, createTime, updateTime)
        VALUES (#{userId}, #{userName}, #{userHead}, now(), now())
    </update>

    <delete id="deleteUserInfoByUserId" parameterType="java.lang.String">
        DELETE FROM user WHERE userId = #{userId}
    </delete>

    <select id="queryUserInfoList" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userName, userHead
        FROM user
    </select>
<!--   <select id="queryUserInfo" parameterType="df.middleware.mybatis.po.User" resultType="df.middleware.mybatis.po.User">-->
<!--        SELECT id, userId, userName, userHead-->
<!--        FROM user-->
<!--        where id = #{id} and userId = #{userId}-->
<!--    </select>-->
</mapper>

单元测试类,添加了增加、修改、删除、查询列表的测试,你们可以将增删改查都试下,

public class TestApi {
    private SqlSession sqlSession;

    @Before
    public void init() throws IOException {
        // 1. 从SqlSessionFactory中获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
        sqlSession = sqlSessionFactory.openSession();
    }


    @org.junit.Test
    public void test_queryUserInfoById() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:基本参数
        User user = userDao.queryUserInfoById(1L);
        System.out.println("测试结果:" + user.getUserName());
    }

    @org.junit.Test
    public void test_queryUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        User user = userDao.queryUserInfo(new User(1L, "10001"));
        System.out.println(user);
        System.out.println(user.getUserName());
    }


    @org.junit.Test
    public void test_updateById() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:基本参数
        userDao.updateById(1L);


        sqlSession.commit();
        //System.out.println("测试结果:" + user.getUserName());
    }

    @Test
    public void test_insertUserInfo() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        User user = new User();
        user.setUserId("10003");
        user.setUserName("价钱");
        user.setUserHead("1_06");
        userDao.insert(user);
        System.out.println("测试结果:" + "Insert OK");

        // 3. 提交事务
        sqlSession.commit();
    }

    @Test
    public void test_deleteUserInfoByUserId() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证
        int count = userDao.deleteUserInfoByUserId("10003");
        System.out.println("测试结果:"+count);

        // 3. 提交事务
        sqlSession.commit();
    }


    @Test
    public void test_queryUserInfoList() {
        // 1. 获取映射器对象
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        // 2. 测试验证:对象参数
        List<User> users = userDao.queryUserInfoList();
        System.out.println(users.get(0).getUserName());
        System.out.println(users.get(1).getUserName());
        System.out.println(users.get(2).getUserName());
    }

}

执行成功,大家可以试下,不熟悉断点调试下 ,END...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值