前面我们学习了 MyBatis 持久层框架的原生开发方式和 Mapper 代理开发两种方式,解决了使用 JDBC 基础性代码操作数据库时存在的硬编码和操作繁琐的问题。既然文章带来的反馈还不错,那么今天使用前面学习的内容做一个实战案例的训练。
回顾一下,我们为什么使用 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 映射配置文件和 Mapper 接口的相互跳转,其次是根据 Mapper 接口方法自动生成 statement,如图:
在 File / setting / plugins 中搜索 MyBatisX 插件下载安装即可。
其中蓝色图片代表 Mapper 接口文件,红色图标表示 sql 映射配置文件,使用该插件即可在 Mapper 接口中定义方法,在配置文件中自动生成 statement ,在配置文件可快速跳转到对应的 Mapper 接口。这个插件小编认为在 MyBatis 开发中是十分高效的,小编自从学习 MyBatis 就在使用哦。
在客户端页面中,我们通常需要展示所有的数据信息,此时在底层代码中就是使用 Java 操作数据库,并查询所有信息并发送到客户端页面。
我们可以通过以下几个步骤来实现查询所有信息:
List
在分析 Mapper 接口中的方法时,主要是根据操作数据的需求分析是否需要参数和返回值类型。
在 org.chengzi.mapper
包中创建 StudentMapper 接口,并在该接口中定义查询所有学生数据的方法:
public interface StudentMapper {
/*
需求:查询所有学生信息
*/
List<Student> selectAll();
}
在 resources 路径下创建org/chengzi/mapper
目录结构,路径分隔符一定使用 /
,保证了 Mapper 接口和对应的 sql 映射配置文件在同一文件目录下。在该目录下创建 StudentMapper.xml 配置文件。
在 StudentMapper 接口中编写了对应的方法以后,通过 MyBatisX 插件的图标,我们可以快速跳转到对应的 sql 映射配置文件,并且自动生成了 statement 。在 sql 映射配置文件中编写查询所有学生信息的 sql 语句:
<?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="org.chengzi.mapper.StudentMapper">
<select id="selectAll" resultType="student">
select *
from student;
</select>
</mapper>
在 MyBatisTest 类中编写测试查询所有学生信息的代码,如下:
public class MyBatisTest {
@Test
public void testSelectAll() throws IOException {
//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. 执行方法
List<Student> students = studentMapper.selectAll();
System.out.println(students);
//5. 释放资源
sqlSession.close();
}
}
以后学习了 SSM 框架后,这部分代码将变的很简单,这里的重点在于执行方法的一行代码。
运行结果:
这里发现,数据库中部分字段的信息并没有封装到 Java 对象中,问题的原因很简单,由于数据库中表的列名与 Java 代码中实体类的属性名不一致,比如说,学生数学成绩,在 MySQL 中采用 score_math 的方法命名,而在 Java 中命名为 scoreMath,两种命名方式都没有问题。
解决这个问题有两种方法:
例如:
<mapper namespace="org.chengzi.mapper.StudentMapper">
<select id="selectAll" resultType="student">
select id,name,gender,score_english as scoreEnglish,score_Math as scoreMath
from student;
select>
mapper>
此时,数据无法封装到对象中的问题已经解决,但是查询所有学生信息时,我们要列出所有字段名,显然这样做的效率低,是不可取的。MyBatis 提供了 sql 片段的方式解决这个问题。
例如:
<mapper namespace="org.chengzi.mapper.StudentMapper">
<sql id="student_column">
select id,name,gender,score_english as scoreEnglish,score_Math as scoreMath
sql>
<select id="selectAll" resultType="student">
<include refid="student_column"/>
from student;
select>
mapper>
id 做为该 sql 片段的唯一标识,使用时在原 sql 语句中使用
标签引用即可。
这个又出现了问题,如果是对数据表中部分字段进行操作,那么又会出现大量的 sql 片段,显然是不可取的,所有 MyBatis 使用 resultMap 的方法解决这个问题。
在解决数据表中部分字段无法封装到 Java 对象中的问题时,使用起别名的方式效率低,sql 片段的方式不灵活,MyBatis 提供了 resultMap 方法来定义字段名和属性名的映射关系。
使用时,只需要在 sql 映射配置文件中使用下面的方法来定义:
<mapper namespace="org.chengzi.mapper.StudentMapper">
<resultMap id="studentResultMap" type="student">
<result column="score_english" property="scoreEnglish"/>
<result column="score_math" property="scoreMath"/>
resultMap>
<select id="selectAll" resultMap="studentResultMap">
select *
from student;
select>
mapper>
使用此方法时,只需要对数据表中字段名和 Java 实体类中属性不一样的部分进行映射,大大的提高了效率。resultMap 标签中 id做为唯一标识,在 statement 中使用。
在 resultMap 中,有两个标签可以使用, 用于对一般数据的映射, 用于对主键数据的映射。
运行程序:
数据表中的数据已经全部封装到 Java 对象中。
在客户端中,数据往往不会全部显示,而是显示一部分,另一部分通常需要使用查看详情的方式来查看。此时当用户选择指定的学生并查看信息时, 该学生的 id 发送到 Java 代码,通过用户 id 查询该学生的所有信息。
我们可以通过以下几个步骤来实现查询详情功能:
在查询详情的时候,往往需要传入一个参数,例如 id,根据 id 查询此学生的所有信息,返回结果只有一条记录数据,所以只需要封装到一个对象中。
在 StudentMapper 接口中定义根据id查询的数据的方法:
/**
* 查看详情:根据Id查询
*/
Student selectById(int id);
通过 MyBatisX 的快速跳转功能,跳转到对应的 sql 映射配置文件,此时已经自动生成 statement ,并且可以直接使用之前定义的 resultMap 。
<select id="selectById" resultMap="studentResultMap">
select *
from student where id = #{id};
select>
上面的 #{id}
为参数占位符,类似于之前使用的 ?
,稍后讲解。
在 MyBatisTest 类中编写测试查询所有学生信息的代码,如下:
@Test
public void testSelectById() throws IOException {
//接收参数
int id =2;
//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. 执行方法
Student students = studentMapper.selectById(id);
System.out.println(students);
//5. 释放资源
sqlSession.close();
}
此时,与前面查询所有学生信息不同的是,该方法的参数和返回值不同。我们只需要传入 Java 代码接收的参数,便可以将查询到的数据封装到一个 Student 类对象中。
运行结果:
前面说到,sql 语句中使用 #{xx}
作为参数占位符,查看运行日志可以看到,其实在运行过程中 Java 把参数占位符位置自动替换为 ?
,这样就解决了拼 sql 字符串带来的 sql 注入问题。如图:
其实 MyBatis 中的占位符除了 #{xx}
还可以使用 ${xx}
,后者在拼字符串时,容易引发 sql 注入问题,所以其一般用于查询的表名不确定时,用在 sql 语句的 from 后面。
不难发现,两者的不同点在于,#{xx}
占位符其底层使用的是 preparedStatement ,而 ${xx}
则使用的是 statement ,存在 sql 注入的问题。所以在 MyBatis 开发中建议使用前者作为参数占位符。
在 Mapper 接口中的方法如果存在参数,在对应的 sql 映射文件中应该配置 parameterType 来指定参数的数据类型,但是此属性可以省略不写。不难理解,之所以可以省略是因为在 Mapper 接口文件中该参数的数据类型已经被定义。
如下:
<mapper>
<select id="selectById" parameterType="int" resultMap="studentResultMap">
select * from student where id=#{id};
select>
mapper>
MyBatis 在对应的 sql 映射配置文件中写 sql 时,会出现一些特殊的字符,例如使用小于号时,会和标签的 <
部分混淆。如图:
MyBatis 也提供了对应的方法解决这个问题,你可以使用这两种方式:
例如使用转移字符:
或使用 CDATA 的方法:
使用具有代码自动补全功能的 IDE 时,只需要输入一部分就有自动补全,此方法用于特殊字符较多时。
在客户端实际操作中,我们往往或根据多个条件同时满足来查询数据,例如查询案例中的英语成绩和数学成绩同时大于 60 的所有学生信息。
这里的重点是 sql 语句如何编写。
我们可以通过以下几个步骤来实现查询详情功能:
List
在 StudentMapper 接口中定义多条件查询的方法,在定义接口时同样要定义参数,MyBatis 针对多条件查询的多个参数有多种实现方式。
方法一:
使用 @Param("参数名称")
标记每一个参数,在映射配置文件中就需要使用 #{参数名称}
进行占位
List<Student> selectByCondition(@Param("scoreEnglish") int scoreEnglish, @Param("scoreMath") int scoreMath);
方法二:
将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容}
时,里面的内容必须和实体类属性名保持一致。
List<Student> selectByCondition(Student student);
方法三:
将多个参数封装到 map 集合中,将 map 集合作为接口的方法参数。该方式要求在映射配置文件的 SQL 中使用 #{内容}
时,里面的内容必须和map集合中键的名称一致。
List<Student> selectByCondition(Map map);
在 StudentMapper.xml 文件中编写对应的 statement,这里同样使用 resultMap。示例:
<mapper>
<select id="selectByCondition" resultMap="studentResultMap">
select * from student
where score_english > #{scoreEnglish} and score_math > #{scoreMath};
select>
mapper>
在多条件查询时,因为定义 Mapper 接口时有三种不同的设置参数的方法,所以这里的具体执行方法也分为不同的三种。
@Test
public void testSelectByCondition() throws IOException {
//接收参数
int scoreEnglish=60;
int scoreMath=60;
//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. 执行方法
//方法1.使用第一种方法定义参数时使用
List<Student> students = studentMapper.selectByCondition(scoreEnglish,scoreMath);
System.out.println(students);
//方法2.使用第二种方法定义参数时使用
Student student = new Student();
student.setScoreEnglish(scoreEnglish);
student.setScoreMath(scoreMath);
List<Student> students = studentMapper.selectByCondition(student);
System.out.println(students);
//方法3.使用第三种方法定义参数时使用
Map map = new HashMap();
map.put("scoreEnglish" , scoreEnglish);
map.put("scoreMath" , scoreMath);
List<Student> students = studentMapper.selectByCondition(map);
System.out.println(students);
//5. 释放资源
sqlSession.close();
}
}
使用三种方式执行程序,其结果相同,如图:
上面我们在多条件查询时设置了三个参数,并且执行测试程序时传入了三个参数。但是,现实中的情况是用户可能不会给每个参数输入值,此时上面的 sql 就出现了问题。
当用户输入两个参数时,sql语句如下:
select * from student where score_english > #{scoreEnglish} and score_math > #{scoreMath};
当用户只输入一个条件是,sql语句如下:
select * from student where score_english > #{scoreEnglish} ;
针对这个问题,MyBatis 具有强大的解决办法:
例如使用下面的方法解决:
<select id="selectByCondition" resultMap="studentResultMap">
select * from student
where
<if test="scoreEnglish !=null">
score_english > #{scoreEnglish}
</if>
<if test="scoreMath !=null">
and score_math > #{scoreMath}
</if>
</select>
使用这种方式,在程序执行时会进行字符串的动态拼接,如果两者都传入了数据,则 sql 语句拼接为:
并且如果用户没有传入后者的值,那么程序也是正常执行的,此时的 sql 字符串:
但是如果前者没有输入数据而只有后者给定了数据,则程序会出现错误,因为这样的话在拼接 sql 字符串时,where 后面会出现 and ,此时 sql 语法错误。如下:
select * from student where and scoreMath > ? ;//语法错误
这个问题可以使用 where 标签来解决,使用 where 标签可以替换 where 关键字,并且会动态的去掉第一个条件后面的 and ,如果所有的参数都没有给定值,则不使用 where 关键字。
注意:此时需要给每个条件加上 and 关键字,
会动态的去掉第一个条件后面的 and 。
此时程序执行成功,并且满足用户可能不会输入所有参数的值的问题。如图:
在客户端中,有时用户可以选择一个条件查询,而选择哪一个条件 Java 代码并不知道,此时就要使用动态 sql 的单条件查询。
这样的需求可以使用 choose(when,otherwise)
标签来实现,choose 标签的使用类似于Java 中的 Switch 语句。
在Mapper接口中写动态 sql 的单条件查询方法:
List<Student> selectByConditionSingle(Map map);
在 sql 映射配置文件中写 sql 语句,如下:
<mapper>
<select id="selectByConditionSingle" resultMap="studentResultMap">
select * from student
<where>
<choose>
<when test="scoreEnglish !=null">
score_english > #{scoreEnglish}
when>
<when test="scoreMath !=null">
score_math > #{scoreMath}
when>
<otherwise>
1=1
otherwise>
choose>
where>
select>
mapper>
在 MyBatisTest 类中编写单元测试代码,如下:
@Test
public void testSelectByConditionSingle() throws IOException {
//接收参数
int scoreEnglish=60;
int scoreMath=60;
//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);
Map map = new HashMap();
map.put("scoreEnglish" , scoreEnglish);
//map.put("scoreMath" , scoreMath);
List<Student> students = studentMapper.selectByConditionSingle(map);
System.out.println(students);
//5. 释放资源
sqlSession.close();
}
运行结果正确,如图:
本文是 MyBatis 开发使用配置文件实现增删改查操作的实战练习篇,由于文章篇幅的原因,本文只涉及到查询操作,查询操作作为操作数据库最常用的方法,一定要不断地练习才能熟练。在写 sql 时,虽然不强调大小写问题,但是建议使用大写,因为大写是更加规范的 sql 方式。
落笔为终,看了下时间,现在是凌晨两点,刚写完这篇文章,外面格外的安静。创作不易,希望能够帮助到你。先赞后看,养成习惯,我们下期见。