单表的CRUD。 C:Create,创建;R:Retrieve,读取;U:Update,更新;D,Delete,删除。
1. 首先配置映射文件User.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.alex.app.entity.User"> <select id="selectUser" parameterType="int" resultType="com.alex.app.entity.User"> select * from t_user where id = #{id} </select> <insert id="insertUser" parameterType="com.alex.app.entity.User"> insert into t_user(username,password) values(#{username},#{password}) </insert> <update id="updateUser" parameterType="com.alex.app.entity.User"> update t_user set username=#{username},password=#{password} where id = #{id} </update> <delete id="deleteUser" parameterType="int"> delete from t_user where id = #{id} </delete> </mapper>
2. 先提供一个工具类,方便测试
package com.alex.app.util; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class MyBatisUtil { private MyBatisUtil() { } private static SqlSessionFactory factory; private static String configFile = "mybatis-config.xml"; static{ InputStream in; try { // 加载总配置文件 in = Resources.getResourceAsStream(configFile); // 创建SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); } catch (IOException e) { e.printStackTrace(); } } /** * 获取SqlSession,并关闭自动提交,打开事务。 * 通俗点说就是将自动提交事务,改为手动提交 * @return */ public static SqlSession openSession(){ return factory.openSession(); } /** * 关闭session * @param session */ public static void coloseSession(SqlSession session){ if(session != null){ session.close(); } } }
SqlSession创建的时候,会关闭自动提交,打开事务。我们扫一眼SqlSessionFactory的openSessoin方法的源码就知道了。
以下源码分析不是重点,可以略过
eclipse中,使用 maven构建的项目要把源码一并下载下来,右击pom.xml选择Run As-->Maven Build..,在对话框Goals中输入命令dependency:sources,点Run,相关maven的插件和源码就都下载下来。
源码已经关联上,图标上有了个文本的标识
SqlSessionFactory是个接口,这里调用到的是DefaultSqlSessionFactory实现类的openSessoin方法。
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }我们看到第4个参数的值就是false。往里看代码直到JdbcTransaction类中,构造方法有
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommmit = desiredAutoCommit; }flase这个值会传递给JdbcTransaction构造方法的desiredAutoCommit,设置了autoCommmit 这个值为false。
那么肯定有地方调用了这个构造方法,在哪里呢,是在SqlSessoin调用insert/update/delete方法的时候
public int insert(String statement, Object parameter) { return update(statement, parameter); }调用了update(statement, parameter)
public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }继续往里跟executor.update(ms, wrapCollection(parameter));直到BatchExecutor类的doUpdate方法
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { //.....省略这些代码,节省篇幅 if (sql.equals(currentSql) && ms.equals(currentStatement)) { //.....省略这些代码,节省篇幅 } else { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection); //.....省略这些代码,节省篇幅 } //.....省略这些代码,节省篇幅 }其中有一句代码是Connection connection = getConnection(ms.getStatementLog());
protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } }Connection connection = transaction.getConnection(); transaction对象这里是JdbcTransaction对象
public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; }openConnection();
protected void openConnection() throws SQLException { // 省略... setDesiredAutoCommit(autoCommmit); }
protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { //..... connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // .... } }connection.setAutoCommit(desiredAutoCommit); desiredAutoCommit参数的值就是在SqlSession调用openSessoin()方法的时候,第4个参数值 false
public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }上面的源分析码可以无视,不是重点
3. 编写UserTest测试类。
package com.alex.app.test; import org.apache.ibatis.session.SqlSession; import org.junit.Assert; import org.junit.Test; import com.alex.app.entity.User; import com.alex.app.util.MyBatisUtil; public class UserTest { /** * C,Create 创建 */ @Test public void testInsertUser() { SqlSession session = null; try { // 获取SqlSession session = MyBatisUtil.openSession(); // 准备要添加的数据 User user = new User(); user.setUsername("belongtou"); user.setPassword("9988"); /*insert()方法说明 * 第一个参数,就是User.xml的namespace属性 + "." + insert标签的id属性 * 我们看到User.xml的namespace属性正好与我们的实体User的完整类名一致 * 所以我们可以使用反射的方式取到这个完整的类名,然再后 + "." + insert标签的id属性, * 一长串的字符写起来很容易出错的。我们说约定优与配置::: * */ session.insert(User.class.getName()+".insertUser",user); /* 需要手动提交事务 * mybaits的事务在创建SqlSessoin时,自动将作了一个操作:Connection.setAutoCommint(false),将事务设置为非自动提交。 */ session.commit(); } catch (Exception e) { e.printStackTrace(); // 异常回滚 session.rollback(); }finally{ // 关闭SqlSession MyBatisUtil.coloseSession(session); } } /** * R,Retrieve 读取 */ @Test public void testSelectUser() { SqlSession session = null; try { session = MyBatisUtil.openSession(); User user = (User)session.selectOne(User.class.getName()+".selectUser",2); // 断言 user不为空 Assert.assertNotNull(user); // 断言user对象的Id就是我们刚上面所查询到的ID值 2 // assertEquals第一个参数是预期值,第二个参数是实际 Assert.assertEquals(user.getId(), 2); } catch (Exception e) { e.printStackTrace(); } } /** * U, Update 更新 */ @Test public void testUpdateUser() { SqlSession session = null; try { session = MyBatisUtil.openSession(); User user = (User)session.selectOne(User.class.getName()+".selectUser", 1); Assert.assertNotNull(user);//断言user对象不为null user.setUsername("Nunu"); user.setPassword("7788"); session.update(User.class.getName()+".updateUser", user); session.commit(); User user2 = (User)session.selectOne(User.class.getName()+".selectUser", 1); Assert.assertNotNull(user2); //断言,user2所查询到的值,与我们上面更新时(更新user对象)所设置的值是否一致。一致则测试通过 Assert.assertEquals(user2.getUsername(), "Nunu"); Assert.assertEquals(user2.getPassword(), "7788"); } catch (Exception e) { e.printStackTrace(); session.rollback(); }finally{ MyBatisUtil.coloseSession(session); } } /** * D ,Delete 删除 */ @Test public void testDeleteUser() { SqlSession session = null; try { session = MyBatisUtil.openSession(); session.delete(User.class.getName()+".deleteUser", 3); session.commit(); } catch (Exception e) { e.printStackTrace(); session.rollback(); }finally{ MyBatisUtil.coloseSession(session); } } }
注意,SqlSession在创建的时候,MyBatis把事务自动设置为了false,就是JDBC规范的Connection.setAutoCommit(false);事务不会自动提交。再做URD的时候,需要调用SqlSession的commint() 方法提交。
4. 映射文件问题
现在我们映射文件有很多重复主要是parameterType="com.alex.app.entity.User" resultType="com.alex.app.entity.User" 这里,都是重复的。
我们可以在mybatis-config.xml配置文件中properties标签和environments之前,使用typeAlias 标签配置一个别名
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入外部properties文件 --> <properties resource="jdbc.properties" /> <typeAliases> <typeAlias type="com.alex.app.entity.User" alias="User"/> </typeAliases> <environments default="development"> <!-- 省略部分配置 --> </environments> <!-- 映射文件 --> <mappers> <mapper resource="com/alex/app/entity/User.xml"/> </mappers> </configuration>
那么在映射文件中,就可以使用这个别名来定义这些属性 parameterType="User" resultType="User"
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.alex.app.entity.User"> <select id="selectUser" parameterType="int" resultType="User"> select * from t_user where id = #{id} </select> <insert id="insertUser" parameterType="User"> insert into t_user(username,password) values(#{username},#{password}) </insert> <update id="updateUser" parameterType="User"> update t_user set username=#{username},password=#{password} where id = #{id} </update> <delete id="deleteUser" parameterType="int"> delete from t_user where id = #{id} </delete> </mapper>
单元的测试断言部分不用纠结,可以直接去数据库中查看。或者最笨的方式直接把信息打印到控制台上查看。
另外还有几个问题,就是在执行CRUD操作的时候,会破坏数据库中的数据;每个单元测都是独立的,不依赖与其它单元测试,假如我先执行一个删除动作,删除Id 为2的数据,之后执行了查询动作,查询ID为2的数据,这时候可能会出错。
单元测试是为了测试我们的功能片断是否正常,每个分支是否覆盖到,一些代码的边界是否存在的问题等等,这里不纠结它了,后面我们再研究。