MyBatis持久层框架详细解读:配置文件实现增删改查操作实战训练

1. 前言

前面我们学习了 MyBatis 持久层框架的原生开发方式和 Mapper 代理开发两种方式,解决了使用 JDBC 基础性代码操作数据库时存在的硬编码和操作繁琐的问题。既然文章带来的反馈还不错,那么今天使用前面学习的内容做一个实战案例的训练。

回顾一下,我们为什么使用 MyBatis 开发呢?不难理解,MyBatis 作为一款优秀的持久层框架,支持自定义 sql,存储过程以及高级映射,它几乎免除了所有的 JBDC 代码以及设置参数和获取结果集的工作。解决了使用 JBDC 基础性的代码操作数据库时面临的 Java 代码的硬编码和操作繁琐的问题,如图:

本节案例训练目标:能够使用配置文件来实现增删改查操作。

2. 准备工作

今天我们使用 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);

数据表如下图:

image-20230129193400530

接下来在 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 类。如图:

image-20230129194459753

为了提高 MyBatis 开发效率,我们再安装 MyBatisX 插件,这个插件主要有两个作用,首先是 XML 映射配置文件和 Mapper 接口的相互跳转,其次是根据 Mapper 接口方法自动生成 statement,如图:

image-20230129194834069

在 File / setting / plugins 中搜索 MyBatisX 插件下载安装即可。

其中蓝色图片代表 Mapper 接口文件,红色图标表示 sql 映射配置文件,使用该插件即可在 Mapper 接口中定义方法,在配置文件中自动生成 statement ,在配置文件可快速跳转到对应的 Mapper 接口。这个插件小编认为在 MyBatis 开发中是十分高效的,小编自从学习 MyBatis 就在使用哦。

3. 查询所有数据

在客户端页面中,我们通常需要展示所有的数据信息,此时在底层代码中就是使用 Java 操作数据库,并查询所有信息并发送到客户端页面。

我们可以通过以下几个步骤来实现查询所有信息:

  • 编写 Mapper 接口
    • 参数:无
    • 结果:List<Student>
  • 编写 sql 映射配置文件
  • 编写并执行测试代码

在分析 Mapper 接口中的方法时,主要是根据操作数据的需求分析是否需要参数和返回值类型。

3.1 编写接口方法

org.chengzi.mapper 包中创建 StudentMapper 接口,并在该接口中定义查询所有学生数据的方法:

public interface StudentMapper {
    
    
    /*
    需求:查询所有学生信息
     */
    List<Student> selectAll();

}

3.2 编写sql语句

在 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>

3.3 编写测试方法

在 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 框架后,这部分代码将变的很简单,这里的重点在于执行方法的一行代码。

运行结果:

image-20230129202650613

这里发现,数据库中部分字段的信息并没有封装到 Java 对象中,问题的原因很简单,由于数据库中表的列名与 Java 代码中实体类的属性名不一致,比如说,学生数学成绩,在 MySQL 中采用 score_math 的方法命名,而在 Java 中命名为 scoreMath,两种命名方式都没有问题。

解决这个问题有两种方法

  1. 给数据表字段名起别名
  2. 使用 resultMap 定义字段名和属性名的映射关系

例如:

<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片段
	-->
    <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 语句中使用<include>标签引用即可。

这个又出现了问题,如果是对数据表中部分字段进行操作,那么又会出现大量的 sql 片段,显然是不可取的,所有 MyBatis 使用 resultMap 的方法解决这个问题。

3.4 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 中,有两个标签可以使用, 用于对一般数据的映射, 用于对主键数据的映射。

运行程序:

image-20230129210333422

数据表中的数据已经全部封装到 Java 对象中。

4. 查询详情

在客户端中,数据往往不会全部显示,而是显示一部分,另一部分通常需要使用查看详情的方式来查看。此时当用户选择指定的学生并查看信息时, 该学生的 id 发送到 Java 代码,通过用户 id 查询该学生的所有信息。

我们可以通过以下几个步骤来实现查询详情功能:

  • 编写 Mapper 接口:
    • 参数:id
    • 返回值:Student
  • 编写sql映射文件
  • 执行测试方法

在查询详情的时候,往往需要传入一个参数,例如 id,根据 id 查询此学生的所有信息,返回结果只有一条记录数据,所以只需要封装到一个对象中。

4.1 编写接口方法

在 StudentMapper 接口中定义根据id查询的数据的方法:

/**
  * 查看详情:根据Id查询
  */
Student selectById(int id);

4.2 编写sql语句

通过 MyBatisX 的快速跳转功能,跳转到对应的 sql 映射配置文件,此时已经自动生成 statement ,并且可以直接使用之前定义的 resultMap 。

<select id="selectById"  resultMap="studentResultMap">
    select *
    from student where id = #{id};
</select>

上面的 #{id} 为参数占位符,类似于之前使用的 ,稍后讲解。

4.3 编写测试方法

在 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 类对象中。

运行结果:

image-20230129212902381

4.4 参数占位符

前面说到,sql 语句中使用 #{xx} 作为参数占位符,查看运行日志可以看到,其实在运行过程中 Java 把参数占位符位置自动替换为 ,这样就解决了拼 sql 字符串带来的 sql 注入问题。如图:

image-20230129213409241

其实 MyBatis 中的占位符除了 #{xx} 还可以使用 ${xx},后者在拼字符串时,容易引发 sql 注入问题,所以其一般用于查询的表名不确定时,用在 sql 语句的 from 后面。

不难发现,两者的不同点在于,#{xx} 占位符其底层使用的是 preparedStatement ,而 ${xx} 则使用的是 statement ,存在 sql 注入的问题。所以在 MyBatis 开发中建议使用前者作为参数占位符。

4.5 parameterType 使用

在 Mapper 接口中的方法如果存在参数,在对应的 sql 映射文件中应该配置 parameterType 来指定参数的数据类型,但是此属性可以省略不写。不难理解,之所以可以省略是因为在 Mapper 接口文件中该参数的数据类型已经被定义。

如下:

<mapper>   
	<select id="selectById" parameterType="int" resultMap="studentResultMap">
select * from student where id=#{id};
    </select>
</mapper>

4.6 特殊字符的处理

MyBatis 在对应的 sql 映射配置文件中写 sql 时,会出现一些特殊的字符,例如使用小于号时,会和标签的 < 部分混淆。如图:

image-20230129233235106

MyBatis 也提供了对应的方法解决这个问题,你可以使用这两种方式:

  1. 转义字符:用于特殊字符较少时
  2. CDATE:大量出现易混淆的特殊字符时使用

例如使用转移字符:

image-20230129234459723

或使用 CDATA 的方法:

image-20230129234642325

使用具有代码自动补全功能的 IDE 时,只需要输入一部分就有自动补全,此方法用于特殊字符较多时。

5. 多条件查询

在客户端实际操作中,我们往往或根据多个条件同时满足来查询数据,例如查询案例中的英语成绩和数学成绩同时大于 60 的所有学生信息。

这里的重点是 sql 语句如何编写。

我们可以通过以下几个步骤来实现查询详情功能:

  • 编写 Mapper 接口
    • 参数:所有的查询条件
    • 结果:List<Student>
  • 编写 sql 映射配置文件
  • 执行测试方法

5.1 编写接口方法

在 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);

5.2 编写sql语句

在 StudentMapper.xml 文件中编写对应的 statement,这里同样使用 resultMap。示例:

<mapper> 
	<select id="selectByCondition" resultMap="studentResultMap">
        select * from student 
                 where score_english > #{scoreEnglish} and score_math > #{scoreMath};
    </select>
</mapper>

5.3 编写测试方法

在多条件查询时,因为定义 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();
    }
}

使用三种方式执行程序,其结果相同,如图:

image-20230129224312015

5.4 动态SQL

上面我们在多条件查询时设置了三个参数,并且执行测试程序时传入了三个参数。但是,现实中的情况是用户可能不会给每个参数输入值,此时上面的 sql 就出现了问题。

当用户输入两个参数时,sql语句如下:

select * from student where score_english > #{scoreEnglish} and score_math > #{scoreMath};

当用户只输入一个条件是,sql语句如下:

select * from student where score_english > #{scoreEnglish} ;

针对这个问题,MyBatis 具有强大的解决办法:

  • if
  • choose(when,otherwise)
  • trim(where,set)
  • foreach

例如使用下面的方法解决:

<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 语句拼接为:

image-20230129231539021

并且如果用户没有传入后者的值,那么程序也是正常执行的,此时的 sql 字符串:

image-20230129231659101

但是如果前者没有输入数据而只有后者给定了数据,则程序会出现错误,因为这样的话在拼接 sql 字符串时,where 后面会出现 and ,此时 sql 语法错误。如下:

select * from student where and scoreMath > ? ;//语法错误

这个问题可以使用 where 标签来解决,使用 where 标签可以替换 where 关键字,并且会动态的去掉第一个条件后面的 and ,如果所有的参数都没有给定值,则不使用 where 关键字。

注意:此时需要给每个条件加上 and 关键字, <where>会动态的去掉第一个条件后面的 and 。

此时程序执行成功,并且满足用户可能不会输入所有参数的值的问题。如图:

image-20230129232606376

6. 动态SQL的单条件查询

在客户端中,有时用户可以选择一个条件查询,而选择哪一个条件 Java 代码并不知道,此时就要使用动态 sql 的单条件查询。

这样的需求可以使用 choose(when,otherwise) 标签来实现,choose 标签的使用类似于Java 中的 Switch 语句。

6.1 编写接口方法

在Mapper接口中写动态 sql 的单条件查询方法:

List<Student> selectByConditionSingle(Map map);

6.2 编写sql语句

在 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>

6.3 编写测试方法

在 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();
    }

运行结果正确,如图:

image-20230130013053973

7. 总结

本文是 MyBatis 开发使用配置文件实现增删改查操作的实战练习篇,由于文章篇幅的原因,本文只涉及到查询操作,查询操作作为操作数据库最常用的方法,一定要不断地练习才能熟练。在写 sql 时,虽然不强调大小写问题,但是建议使用大写,因为大写是更加规范的 sql 方式。

落笔为终,看了下时间,现在是凌晨两点,刚写完这篇文章,外面格外的安静。创作不易,希望能够帮助到你。先赞后看,养成习惯,我们下期见。

image-20230130001121015

猜你喜欢

转载自blog.csdn.net/zhangxia_/article/details/128796222