我们把基本的功能都完成了,解析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...