前言
前面我们学习了 MyBatis 持久层框架的原生开发方式和 Mapper 代理开发两种方式,解决了使用 JDBC 基础性代码操作数据库时存在的硬编码和操作繁琐的问题。
在配置文件实现增删改查上篇中,我们详细讲解了常用的查询操作,例如查询所有数据,查询数据详情以及使用动态 sql 查询等。文章带来了不错的反馈,并被推荐到热榜,所以继续本系列文章的更新,今天对增删改操作及过程做详细讲解。
- 【MyBatis持久层框架】配置文件实现的查询操作案例
回顾一下,我们为什么使用 MyBatis 开发呢?前面说过,MyBatis 支持自定义 sql,存储过程以及高级映射,它几乎免除了所有的 JBDC 代码以及设置参数和获取结果集的工作。解决了使用 JBDC 基础性的代码操作数据库时面临的 Java 代码的硬编码和操作繁琐的问题,如图:
本节案例训练目标:能够使用配置文件来实现增删改查操作。
准备工作
今天我们使用MyBatis完成数据库数据的增删改查操作,具体实现如下:
- 查询数据
- 查询所有数据
- 查询数据详情
- 条件查询
- 添加数据
- 修改数据
- 修改全部字段
- 修改动态字段
- 删除数据
- 单个数据的删除
- 批量数据的删除
今天的案例是给定一个学生数据表,包括学生id ,姓名,性别,成绩等字段信息,通过MyBatis对数据库中的数据进行增删改操作。
首先,我们要创建好数据库表和添加数据,涉及到的 sql 如下:
drop table if exists student;
create table student(
id int primary key auto_increment,
name varchar(10),
gender char(1),
score_english int,
score_math int
);
insert into student(name,gender,score_english,score_math) values
('张三','男',61,65),
('李四','女',45,38),
('王五','男',85,53),
('小王','男',56,58),
('小樊','女',85,92);
数据表如下图:
接下来在 idea中org.chengzi.pojo
包下创建实体类 Student :
public class Student{
//id 主键
private int id;
//学生姓名
private String name;
//学生性别
private String gender;
//学生英语成绩
private int scoreEnglish;
//学生数学成绩
private int scoreMath;
//这里省略了Getter and Setter方法和重写的Object中的toString方法
}
接下来编写测试用例,这里在 Test 中写单元测试的代码,在测试代码 Java 文件目录下创建 MyBatisTest 类。如图:
为了提高 MyBatis 开发效率,我们再安装 MyBatisX 插件,这个插件主要有两个作用,首先是 XML 映射配置文件和 Mappe r接口的相互跳转,其次是根据 Mapper 接口方法自动生成 statement,如图:
在 File / setting / plugins 中搜索 MyBatisX 插件即可。
其中蓝色图片代表 Mapper 接口文件,红色图标表示 sql 映射配置文件,使用该插件即可在 Mapper 接口中定义方法,在配置文件中自动生成 statement ,在配置文件可快速跳转到对应的 Mapper 接口。这个插件小编认为在 MyBatis 开发中是十分高效的,小编自从学习MyBatis就在使用哦。
添加数据
在客户端中,添加数据是非常常用的需求,用户输入的数据被发送到 Java 代码,Java 代码操作数据库,将数据存入数据表中。在数据库多表关系中,我们有时需要将添加到一张表的数据的主键返回,用与和其他的表建立关系。
例如,在一对多的表关系中,我们在多的一方建立外键关联相关表的主键,此时添加数据并返回之间的操作就是非常需要的,接下来我们先进行添加数据到数据表并通过程序异常处理的方式判断是否添加成功。
我们通过以下步骤完成添加数据的需求:
- 编写接口方法
- 参数:除了 id 以外的所有数据,因为id 为主键并设置为自增长
- 返回值:无
- 编写 sql 映射配置文件
- 编写并执行测试方法
编写接口方法
在 StudentMapper 接口中定义添加数据的方法:
/*
添加数据到数据库表
*/
void add(Student student);
编写sql语句
在 sql 映射配置文件中编写对应的 sql 语句,该文件和 Mapper 接口文件位于同一文件目录下,并且 id 的值与 Mapper 接口中定义的对应方法名一致。
<insert id="add">
insert into student (name, gender, score_english, score_math)
values (#{name}, #{gender}, #{scoreEnglish}, #{scoreMath})
</insert>
编写测试方法
按照往常的方法,Java 代码中接收到用户数据的数据,并将其封装在 Student 类对象中,将该对象作为参数传递给 add() 方法,如下:
public class MyBatisTest {
@Test
public void testAdd() throws IOException {
//接收参数
String name="小美";
String gender="女";
int scoreEnglish=100;
int scoreMath=100;
//封装对象
Student student = new Student();
student.setName(name);
student.setGender(gender);
student.setScoreEnglish(scoreEnglish);
student.setScoreMath(scoreMath);
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
//4. 执行方法
studentMapper.add(student);
//5. 释放资源
sqlSession.close();
}
}
此时程序运行成功数据也被成功的封装在对象中,但是查看数据库发现数据并没有被成功添加,查看控制台日志信息不难发现,数据虽然操作成功,但是MyBatis默认关闭了事务自动提交,即开启了事务,在操作完成以后,数据库回滚事务,所以到导致数据并没有添加到数据库,如图:
我们只需要在数据添加操作完场以后设置提交事务便可以解决这个问题。如下:
//4. 执行方法
studentMapper.add(student);
sqlSession.commit();
解决方法2,在获取 SqlSession 对象时往 openSession() 方法中传入 true ,表示自动提交事务即不开启事务。如下:
SqlSession sqlSession = sqlSessionFactory.openSession(true);
此时执行程序以后数据添加成功。
这里设置主键id 自增长不用手动添加数据,在 MySQL 中,即使发生了事务回滚等问题,id 自增长也会发生并被占用。例如第一次添加 id=5 的记录发生了事务回滚,下一次添加时 id = 6。
主键返回问题
前面说到,在数据添加成功以后,有时需要返回主键的信息,安装上面的方法,add 方法返回值为 void ,其实,即使你添加返回值为 int 的add() 方法,此时主键也不能正常返回,MyBatis 提供了解决这个问题的办法。
解决这个问题,只需要在sql映射配置文件中修改 statement ,如下:
<insert id="add" useGeneratedKeys="true" keyProperty="id">
insert into student (name, gender, score_english, score_math)
values (#{name}, #{gender}, #{scoreEnglish}, #{scoreMath})
</insert>
其中,useGeneratedKeys 属性表示可以获取自增长的主键值,获取的属性封装在 keyProperty 属性中。
修改
用户有时需要根据需求修改数据,用户提交输入发送给 Java 代码,Java 操作数据库修改数据,但是用户可能不会一次修改所有字段的值,此时就需要对用户的输入进行判断。
我们按照一下的步骤完成需求:
- 编写接口方法
- 参数:所有要修改的值
- 返回值:无
- 编写 sql 语句
- 编写测试案例并执行
接下来演示根据用户的id 修改该用户的信息。
编写接口方法
在 StudentMapper 接口中添加修改数据的方法,如下:
void update(Map map);
编写SQL语句
在 sql 映射配置文件中编写修改数据的 statement 。如下:
<update id="update">
update student
<set>
<if test="name !=null and name !=''">
name= #{name},
</if>
<if test="gender !=null and gender !=''">
gender= #{gender},
</if>
<if test="scoreEnglish!=null">
score_english= #{scoreEnglish},
</if>
<if test="scoreMath!=null">
score_math= #{scoreMath}
</if>
</set>
where id=#{id}
</update>
由于用户可能不会修改数据表的全部字段,这里 <if>
标签来选择,忽略不更新的列,最后拼 sql 字符串。<set>
标签用于替代 set
关键字,如果后面的数据不存在,则会自动去掉 ,
。
编写测试方法
在 MyBatisTest 中编写单元测试的方法,如下:
@Test
public void testUpdate() throws IOException {
//接收参数
int id=1;
String name="小美";
String gender="女";
int scoreEnglish=100;
int scoreMath=100;
//封装对象
Map map=new HashMap();
map.put("id",id);
map.put("name",name);
map.put("gender",gender);
// map.put("scoreEnglish",scoreEnglish);
// map.put("scoreMath",scoreMath);
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//3. 获取Mapper接口的代理对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
//4. 执行方法
studentMapper.update(map);
sqlSession.commit();
//5. 释放资源
sqlSession.close();
}
如果客户端只发送了 id ,name ,gender 属性到 Java 代码,则 sql 字符串拼接如下,执行程序,此时只有给定数据的字段被修改。
注意!!!
建议将 POJO 类中的基本数据类型的数据定义为包装类型,因为在sql 映射配置文件中,在判断修改哪些字段时,我们使用变量是否为 NULL 的方式判断了 Java 代码是否接收到了修改该字段的值,如果没有,则不修改该字段,最后在拼 sql 字符串时,会舍弃这一段 sql 。
当然如果你使用基本数据类型,那么一定要判断是否为0,小编初学时在这里卡住到凌晨两点,什么都别说,当时只想砸掉我的电脑!!!
删除
在客户端中,每行数据都有一个删除选项,当然也可以选择批量删除,一般是通过主键删除,Java 代码接收到主键参数后,会操作数据库删除对应的数据。
删除一行数据
分析方法和前面类似,都是从解决问题的步骤和定义 Mapper 接口中的方法是否需要参数和返回值出发,这里不在赘述。
编写接口方法
在 StudentMapper 接口中编写通过 id 删除一条数据的方法,例如:
void deleteById(int id);
编写SQL语句
在 sql 映射配置文件中编写 sql 语句,例如:
<delete id="deleteById">
delete from student where id=#{id};
</delete>
编写测试方法
在 MyBatisTest 中编写单元测试的方法,执行程序,例如:
@Test
public void testDeleteById() throws IOException {
//接收参数,该id以后需要传递过来
int id=5;
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//3. 获取Mapper接口的代理对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
//4. 执行方法
studentMapper.deleteById(id);
//5. 释放资源
sqlSession.close();
}
注意:设置自动提交事务。
运行结果,此时数据已经从数据表中删除。如图:
批量删除
用户有时有删除多条数据的需求,此时需要删除的数据被发送到 Java 代码中,其每条数据的主键 id 会被封装在数组中,删除数据时遍历数组即可删除数据库中的对应记录。
编写接口方法
在 StudentMapper 接口中定义删除多条数据的方法,如下:
编写SQL语句
在写 sql 语句时,需要考虑遍历数组的方式,MyBatis 提供了增强 for 的形式在 sql 中遍历数组。这里删除的数组中存放的是需要批量删除的数据的主键 id。如下:
<delete id="deleteByIds">
delete from student where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
;
</delete>
使用 <foreach>
标签,用来迭代任何可迭代的对象,例如数组,集合等,在标签中通常添加以下的属性:
1,collection 属性
在底层中,MyBatis 会将数组参数封装为一个 Map 集合,在集合中默认的键名为 array ,值为数组,即:array = 数组。当然我们也可以使用注解的方式改变 Map 集合中默认的键名。
例如在定义接口方法时使用注解:
void deleteByIds(@Param("ids") int[] ids);
2,item 属性
其变量用来存放本次遍历获取到的数组元素
3,separator 属性
迭代项中间的分隔符,例如上面的 ,
,使用 <foreeach>
标签迭代对象时,其不会错误的添加多余的分隔符,例如最后一项不应该添加分隔符。
4,open ,close 属性
这两个属性值拼在拼接 sql 语句之前和最后,均只会拼接一次。例如上面的 in 后面的一对括号就可以使用该属性封装,这样提高了 sql 的可读性并让代码变得整洁。
编写测试方法
在 MyBatisTest 中编写单元测试的方法,如下:
@Test
public void testDeleteByIds() throws IOException {
//接收参数,该id以后需要传递过来
int[] ids={1,2,3};
//1. 获取SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2. 获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);
//3. 获取Mapper接口的代理对象
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
//4. 执行方法
studentMapper.deleteByIds(ids);
//5. 释放资源
sqlSession.close();
}
我们批量删除数据库表中的 id 为1,2,3 的三条数据。执行程序,运行结果如图:
此时可以看到,使用标签时,sql语句被拼接为正确的形式,如下:
delete from student where id in ( ? , ? , ? ) ;
这里也是动态 sql 的一种体现。
总结
我们已经结合实战案例,通过配置文件的方式实现了数据库中数据的增删改查操作。其中,由于现实中操作的需求,我们也讲解了动态 sql 的实现,例如在删除多条数据时就需要使用动态 sql 来实现。不难看出,使用了框架开发以后,不仅代码的可读性变强了,编码的效率也大大提高了。
总的来说,使用配置文件的方式来写 sql 语句,确实比 JDBC 原生代码方便呢很多。其实,在简单的 sql 操作中,我们还会使用注解的方式进行开发。下期见。