1.初识MyBatis框架
前言
实际开发中,随着业务的发展,软件系统变得越来越复杂,如果所有的软件都从底层功能开始开发,那将是一个漫长而繁琐的过程。此外,团队协作开发时,由于没有统一的调用规范,系统会出现大量的重复功能的代码,给系统的二次开发和维护带来不便。为解决上述问题,框架应运而生。框架实现了很多基础性的功能,开发人员不需要关心底层功能操作,只需要专心地实现所需要的业务逻辑,大大提高了开发人员的工作效率。当前市场上的Java EE开发主流框架有Spring、SpringMVC和Mybatis等,本章主要对框架的概念以及Mybatis的基础知识进行介绍。
1.1 MyBatis概述
1.1.1 什么是MyBatis?
MyBatis是一个支持普通SQL查询、存储过程以及高级映射的持久层框架,它消除了几乎所有的JDBC代码和参数的手动设置以及对结果集的检索,使用简单的XML或注解进行配置和原始映射,将接口和Java的POJO映射成数据库中的记录,使得Java开发人员可以使用面向对象的编程思想来操作数据库。
MyBatis框架是一个ORM(Object/Relation Mapping,即对象关系映射)框架。所谓的ORM就是一种为了解决面向对象与关系型数据库中数据类型不匹配的技术,它通过描述Java对象与数据库表之间的映射关系,自动将Java应用程序中的对象持久化到关系型数据库的表中。ORM框架的工作原理可以通过一张图来展示。
1.1.2 解决JDBC编程劣势
针对JDBC编程的劣势,MyBatis提供了以下解决方案,具体如下。
问题一:数据库链接创建、释放频繁会造成系统资源浪费,从而影响系统性能。
解决方案:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。
问题二:SQL语句在代码中硬编码,造成代码不易维护。在实际应用的开发中,SQL变化的可能较大。在传统JDBC编程中,SQL变动需要改变Java代码,违反了开闭原则。
解决方案:MyBatis将SQL语句配置在MyBatis的映射文件中,实现了与Java代码的分离。
问题三:使用preparedStatement向占位符传参数存在硬编码,因为SQL语句的where条件不一定,可能多也可能少,修改SQL需要修改代码,造成系统不易维护。
解决方案:MyBatis自动将Java对象映射至SQL语句,通过Statement中的parameterType定义输入参数的类型。
问题四:JDBC对结果集解析存在硬编码(查询列名),SQL变化导致解析代码变化,造成系统不易维护。
解决方案:MyBatis自动将SQL执行结果映射至Java对象,通过Statement中的resultType定义输出结果的类型。
1.2 MyBatis环境搭建
1.2.1 MyBatis环境搭建的步骤
使用MyBatis框架进行数据库开发之前,需要先搭建MyBatis环境,MyBatis环境搭建主要有如下基本步骤。
(1)创建工程;
(2)引入相关依赖;
(3)数据库准备;
(4)编写数据库连接信息配置文件;
(5)编写核心配置文件和映射文件。
1.创建工程:在IntelliJ IDEA中,创建名称为mybatistest的Maven工程
2.引入相关依赖:由于本项目要连接数据库以及对程序进行测试,所以需要在项目的pom.xml文件中导入MySQL驱动包、Junit测试包、MyBatis的核心包等相关依赖。
<!-- 只展示了其中一个依赖-- >
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
...
</dependencies>
注意:IDEA默认集成的Maven,所以在第一次引入依赖时,需要在联网状态下进行,且引入依赖需要较长时间,耐心等待到依赖引入完成即可。
3.创建数据库:在MySQL中创建一个名称为mybatis的数据库,具体SQL语句如下。
create database mybatis;
4.创建数据库连接信息配置文件:在项目的src/main/resources目录下创建数据库连接的配置文件,这里将其命名为db.properties,在该文件中配置数据库连接的参数。
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&
characterEncoding=utf8&useUnicode=true&useSSL=false
mysql.username=root
mysql.password=root
5.创建MyBatis的核心配置文件:在项目的src/main/resources目录下创建MyBatis的核心配置文件,该文件主要用于项目的环境配置,如数据库连接相关配置等。核心配置文件可以随意命名,但通常将其命名为mybatis-config.xml。
<configuration><properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}" />
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</dataSource>
</environment></environments>
</configuration>
1.3 MyBatis入门程序
1.数据准备:在mybatis数据库中创建users表,并在users表中插入几条数据。
use mybatis;
create table users(
uid int primary key auto_increment,
uname varchar(20) not null,
uage int not null
);
insert into users(uid,uname,uage) values(null,'张三',20),(null,'李四',18);
2.创建POJO实体:在项目的src/main/java目录下创建com.itheima.pojo包,在com.itheima.pojo包下创建User类,该类用于封装User对象的属性。
package com.itheima.pojo;
public class User {
private int uid; // 用户id
private String uname; // 用户姓名
private int uage; // 用户年龄
// 省略getter/setter方法
…
}
3.创建映射文件UserMapper.xml:在项目的src/main/resources目录下创建一个文件夹,在mapper文件夹下创建映射文件UserMapper.xml,该文件主要用于实现SQL语句和Java对象之间的映射,使SQL语句查询出来的关系型数据能够被封装成Java对象。
<mapper namespace="com.itheima.pojo.User">
<!--id ="接口中的方法名"parameterType="传入的参数类型" resultType = "返回实体类对象,使用包.类名"-->
<select id="findById" parameterType="int" resultType="com.itheima.pojo.User">
select * from users where uid = #{id}
</select>
</mapper>
4.修改mybatis-config.xml配置文件:在mybatis-config.xml映射文件中添加UserMapper.xml映射文件路径的配置,用于将UserMapper.xml映射文件加载到程序中。
<!– mapping文件路径配置-->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
注意:如果一个项目有多个映射文件,则mybatis-config.xml核心配置文件中需要在<mappers>元素下配置多个<mapper>元素指定映射文件的路径。
5.编写测试类:在项目的src/test/java目录下创建Test包,在Test包下创建UserTest类,该类主要用于程序测试。
public class UserTest {
public void userFindByIdTest() {
String resources = "mybatis-config.xml"; Reader reader=null;
try {
reader= Resources.getResourceAsReader(resources);
}
catch (IOException e)
{
e.printStackTrace();
}
SqlSessionFactory sqlMapper=new SqlSessionFactoryBuilder().build(reader);
SqlSession session=sqlMapper.openSession();
User user=session.selectOne("findById",1);
System.out.println(user.getUname());
session.close();
}
}
1.4 MyBatis工作原理
1.4.1 MyBatis工作原理图
1.4.2 MyBatis工作原理步骤
MyBatis框架在操作数据库时,大体经过了8个步骤。下面结合MyBatis工作原理图对每一步流程进行详细讲解,具体如下。
(1)MyBatis读取核心配置文件mybatis-config.xml:mybatis-config.xml核心配置文件主要配置了MyBatis的运行环境等信息。
(2)加载映射文件Mapper.xml:Mapper.xml文件即SQL映射文件,该文件配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。
(3)构造会话工厂:通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory,用于创建SqlSession。
(4)创建会话对象:由会话工厂SqlSessionFactory创建SqlSession对象,该对象中包含了执行SQL语句的所有方法。
(5)创建执行器:会话对象本身不能直接操作数据库,MyBatis底层定义了一个Executor接口用于操作数据库,执行器会根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存地维护。
(6)封装SQL信息:SqlSession内部通过执行器Executor操作数据库,执行器将待处理的SQL信息封装到MappedStatement对象中。
(7)操作数据库:根据动态生成的SQL操作数据库。
(8)输出结果映射:执行SQL语句之后,通过MappedStatement对象将输出结果映射至Java对象中。
2.MyBatis的核心配置
前言
通过前面的学习,读者对MyBatis框架已经有了一个初步了解,但是要想熟练地使用MyBatis框架进行实际开发,只会简单的配置是不行的,我们还需要对框架中的核心对象、核心配置文件以及映射文件有更深入的了解。本章将针对MyBatis核心对象、核心配置文件和映射文件进行讲解。
2.1 MyBatis的核心对象
2.1.1 SqlSessionFactoryBuilder
1.SqlSessionFactoryBuilder的多个重载build()方法
2.SqlSessionFactoryBuilder构建build()方法的形式
形式一:SqlSessionFactoryBuilder构建build()方法
build(InputStream inputStream,String environment,Properties properties)
上述build()方法中,参数inputStream是字节流,它封装了XML文件形式的配置信息;参数environment和参数properties为可选参数。其中,参数environment决定将要加载的环境,包括数据源和事务管理器;参数properties决定将要加载的properties文件。
形式二:SqlSessionFactoryBuilder构建build()方法
build(Reader reader,String environment,Properties properties)
由上述build()方法可知,第二种形式的build()方法参数作用与第一种形式大体一致,唯一不同的是,第一种形式的build()方法使用InputStream字节流封装了XML文件形式的配置信息,而第二种形式的build()方法使用Reader字符流封装了xml文件形式的配置信息。
形式三:SqlSessionFactoryBuilder构建build()方法
build(Configuration config)
通过以上代码可知,配置信息可以通过InputStream(字节流)、Reader(字符流)、Configuration(类)三种形式提供给SqlSessionFactoryBuilder的build()方法。
3.使用什么模式创建SqlSessionFactory对象
SqlSessionFactory对象是线程安全的,它一旦被创建,在整个应用程序执行期间都会存在。如果我们多次创建同一个数据库的SqlSessionFactory对象,那么该数据库的资源将很容易被耗尽。通常每一个数据库都只创建一个SqlSessionFactory对象,所以在构建SqlSessionFactory对象时,建议使用单例模式。
2.1.2 SqlSessionFactory
1.SqlSessionFactory的openSession()方法
2.openSession(ExecutorType execType)参数值
参数execType有三个可选值:
- ExecutorType.SIMPLE:表示为每条语句创建一条新的预处理语句。
- ExecutorType.REUSE:表示会复用预处理语句。
- ExecutorType.BATCH:表示会批量执行所有更新语句。
3.openSession(ExecutorType execType,Boolean autoCommit)参数值
- 参数execType有三个可选值,同openSession(ExecutorType execType)的参数。
- 参数autoCommit可设置是否开启事务。
4.openSession(ExecutorType execType,Connection connection)参数值
- 参数execType有三个可选值,同openSession(ExecutorType execType)的参数。
- 参数connection可提供自定义连接。
2.1.3 SqlSession
1.SqlSession对象的作用
SqlSession是MyBatis框架中另一个重要的对象,它是应用程序与持久层之间执行交互操作的一个单线程对象,主要作用是执行持久化操作,类似于JDBC中的Connection。SqlSession对象包含了执行SQL操作的方法,由于其底层封装了JDBC连接,所以可以直接使用SqlSession对象来执行已映射的SQL语句。
2.SqlSession对象中常用方法
3. SqlSession对象的使用范围
每一个线程都应该有一个自己的SqlSession对象,并且该对象不能共享。SqlSession对象是线程不安全的,因此其使用范围最好在一次请求或一个方法中,绝不能将其放在类的静态字段、对象字段或任何类型的管理范围(如Servlet的HttpSession)中使用。SqlSession对象使用完之后,要及时的关闭,SqlSession对象通常放在finally块中关闭,代码如下所示。
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 此处执行持久化操作
} finally {
sqlSession.close();
}
2.2 MyBatis核心配置文件
2.2.1 配置文件的主要元素
1.MyBatis核心配置文件中的主要元素
2.<configuration>的子元素的执行顺序
<configuration>元素是整个XML配置文件的根元素,相当于MyBatis各元素的管理员。<configuration>有很多子元素,MyBatis的核心配置就是通过这些子元素完成的。需要注意的是,在核心配置文件中,<configuration>的子元素必须按照上图由上到下的顺序进行配置,否则MyBatis在解析XML配置文件的时候会报错。
2.2.2 <properties>元素
<properties>是一个配置属性的元素,该元素的作用是读取外部文件的配置信息。 假设现在有一个配置文件 db.properties,该文件配置了数据库的连接信息,具体如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
如果想获取数据库的连接信息,可以在 MyBatis 的核心配置文件 mybatis-config.xml 中使用<properties>元素先引入 db.properties 文件,具体代码如下:
<properties resource="db.properties" />
引入 db.properties 文件后,如果希望动态获取 db.properties 文件中的数据库连接信息,可以使用<property>元素配置,示例代码如下:
<dataSource type="POOLED">
<!-- 数据库驱动 -->
<property name="driver" value="${jdbc.driver}" />
<!-- 连接数据库的url -->
<property name="url" value="${jdbc.url}" />
<!-- 连接数据库的用户名 -->
<property name="username" value="${jdbc.username}" />
<!-- 连接数据库的密码 -->
<property name="password" value="${jdbc.password}" />
</dataSource>
db.properties文件实现动态参数配置
完成上述配置后,<dataSource>元素中连接数据库的 4 个属性(driver、url、username 和 password)值将会由db.properties 文件中对应的值来动态替换。这样一来,<properties>元素就可以通过 db.properties 文件实现动态参数配置。
2.2.3 <settings>元素
1.<settings>元素中的常见配置参数
2.<settings>元素中常见配置参数的使用方式
<settings>
<!-- 是否开启缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 是否开启延迟加载,如果开启,所有关联对象都会延迟加载 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 是否开启关联对象属性的延迟加载,如果开启,对任意延迟属性的调用都
会使用带有延迟加载属性的对象向完整加载,否则每种属性都按需加载 -->
<setting name="aggressiveLazyLoading" value="true" />
...
</settings>
2.2.4 <typeAliases>元素
1.核心配置文件若要引用一个POJO实体类,需要输入POJO实体类的全限定类名,而全限定类名比较冗长,如果直接输入,很容易拼写错误。例如,POJO实体类User的全限定类名是com.itheima.pojo.User,未设置别名之前,映射文件的select语句块若要引用POJO类User,必须使用其全限定类名,引用代码如下。
<select id="findById" parameterType="int" resultType="com.itheima.pojo.User">
select * from users where uid = #{id}
</select>
2.多个全限定类设置别名的方式
方式一:在<typeAliases>元素下,使用多个<typeAlias>元素为每一个全限定类逐个配置别名。
<typeAliases> <typeAlias alias=“Usertype="com.itheima.pojo.User"/> <typeAlias alias="Student" type="com.itheima.pojo.Student"/> <typeAlias alias="Employee" type="com.itheima.pojo.Employee"/> <typeAlias alias="Animal" type="com.itheima.pojo.Animal"/> </typeAliases>
方式二:通过自动扫描包的形式自定义别名。
<typeAliases> <package name="com.itheima.pojo"/> </typeAliases>
3.常见Java类型的默认别名问题
除了可以使用<typeAliases>元素为实体类自定义别名外,MyBatis框架还为许多常见的Java类型(如数值、字符串、日期和集合等)提供了相应的默认别名。例如别名_byte映射类型byte、_long映射类型long等,别名可以在MyBatis中直接使用,但由于别名不区分大小写,所以在使用时要注意重复定义的覆盖问题。
2.2.5 <environments>元素
1.<environments>元素配置运行环境
MyBatis可以配置多套运行环境,如开发环境、测试环境、生产环境等,我们可以灵活选择不同的配置,从而将SQL映射到不同运行环境的数据库中。不同的运行环境可以通过<environments>元素来配置,但不管增加几套运行环境,都必须要明确选择出当前要用的唯一的一个运行环境。
2.使用<environments>元素进行配置的示例代码
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" /><!—设置使用JDBC事务管理 -->
<dataSource type="POOLED"> <!-配置数据源 -->
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment> ...
</environments>
3.<transcationManager>元素配置事务管理器
在MyBatis中,<transcationManager>元素可以配置两种类型的事务管理器,分别是JDBC和MANAGED。
- JDBC:此配置直接使用JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务的作用域。
- MANAGED:此配置不提交或回滚一个连接,而是让容器来管理事务的整个生命周期。默认情况下,它会关闭连接,但可以将<transcationManager>元素的closeConnection属性设置为false来阻止它默认的关闭行为。
注意:项目中使用Spring+MyBatis,则没必要在MyBatis中配置事务管理器,实际开发中,项目会使用Spring自带的管理器来实现事务管理。对于数据源的配置,MyBatis提供了UNPOOLED、POOLED和JNDI三种数据源类型。
1). UNPOOLED数据源
UNPOOLED表示数据源为无连接池类型。配置此数据源类型后,程序在每次被请求时会打开和关闭数据库连接。UNPOOLED适用于对性能要求不高的简单应用程。UNPOOLED类型的数据源需要配置5种属性。
UNPOOLED数据源需要配置的属性
2). POOLED数据源
POOLED表示数据源为连接池类型。POOLED数据源利用“池”的概念将JDBC连接对象组织起来,节省了在创建新的连接对象时需要初始化和认证的时间。POOLED数据源使得并发Web应用可以快速的响应请求,是当前比较流行的数据源配置类型。
POOLED数据源可额外配置的属性
3). JNDI数据源
JNDI 表示数据源可以在 EJB 或应用服务器等容器中使用。JNDI数据源需要配置的属性如下所示。
2.2.6 <mappers>元素
1.<mappers>元素作用
在MyBatis的核心配置文件中,<mappers>元素用于引入MyBatis映射文件。映射文件包含了POJO对象和数据表之间的映射信息,MyBatis通过核心配置文件中的<mappers>元素找到映射文件并解析其中的映射信息。通过<mappers>元素引入映射文件的方法有4种。
a.使用类路径引入
<mappers>
<mapper resource="com/itheima/mapper/UserMapper.xml"/>
</mappers>
b.使用本地文件路径引入
使用本地文件路径引入映射文件的示例代码如下所示。
mappers>
<mapper url="file:///D:/com/itheima/mapper/UserMapper.xml"/>
</mappers>
c.使用接口类引入
使用接口类引入映射文件的示例代码如下所示。
<mappers>
<mapper class="com.itheima.mapper.UserMapper"/>
</mappers>
d.使用包名引入
使用包名引入映射文件的示例代码如下所示。
mappers>
<package name="com.itheima.mapper"/>
</mappers>
2.3 MyBatis映射文件
2.3.1 MyBatis映射文件中的常用元素
1.MyBatis映射文件中的常用元素
2.<mapper>元素中namespace属性作用
namespace属性有两个作用:
- 用于区分不同的mapper,全局唯一。
- 绑定DAO接口,即面向接口编程。当namespace绑定某一接口之后,可以不用写该接口的实现类,MyBatis会通过接口的全限定名查找到对应的mapper配置来执行SQL语句,因此namespace的命名必须跟接口同名。
注意: 在不同的映射文件中,<mapper>元素的子元素的id可以相同,MyBatis通过<mapper>元素的namespace属性值和子元素的id联合区分不同的Mapper.xml文件。接口中的方法与映射文件中SQL语句id应一一对应。
2.3.2 <select>元素
1.<select>元素的查询使用
<select>元素用来映射查询语句,它可以从数据库中查询数据并返回。使用<select>元素执行查询操作非常简单,示例代码如下:
<!—查询操作 -->
<select id="findUserById" parameterType="Integer"
resultType="com.itheima.pojo.User">
select * from users where id = #{id}
</select>
2.<select>元素的常用属性
2.3.3 <insert>元素
1.<insert>元素的插入使用
<insert>元素用于映射插入语句,在执行完<insert>元素中定义的SQL语句后,会返回插入记录的数量。使用< insert >元素执行插入操作非常简单,示例代码如下:
<!—插入操作 -->
<insert id="addUser" parameterType="com.itheima.pojo.User">
insert into users(uid,uname,uage)values(#{uid},#{uname},#{uage})
</insert>
2.数据库获取主键值的方式
很多时候,执行插入操作后,需要获取插入成功的数据生成的主键值,不同类型数据库获取主键值的方式不同,下面分别对支持主键自动增长的数据库获取主键值和不支持主键自动增长的数据库获取主键值的方式进行介绍。
a.使用支持主键自动增长的数据库获取主键值
如果使用的数据库支持主键自动增长(如MySQL和SQL Server),那么可以通过keyProperty属性指定POJO类的某个属性接收主键返回值(通常会设置到id属性上),然后将useGeneratedKeys的属性值设置为true。
<insert id="addUser" parameterType="com.itheima.pojo.User" keyProperty="uid" useGeneratedKeys="true" > insert into users(uid,uname,uage)values(#{uid},#{uname},#{uage}) </insert>
b.使用不支持主键自动增长的数据库获取主键值
使用MyBatis提供的<selectKey>元素来自定义主键。
<selectKey keyProperty="id” resultType="Integer" order="BEFORE” statementType="PREPARED">
在上述<selectKey>元素的属性中,order属性可以被设置为BEFORE或AFTER。如果设置为BEFORE,那么它会首先执行<selectKey>元素中的配置来设置主键,然后执行插入语句;如果设置为AFTER,那么它先执行插入语句,然后执行<selectKey>元素中的配置内容。
2.3.4 <update>元素
1.<update>元素的更新使用
<update>元素用于映射更新语句,它可以更新数据库中的数据。在执行完元素中定义的SQL语句后,会返回更新的记录数量。使用<update>元素执行更新操作非常简单,示例代码如下:
<!—更新操作 -->
<update id="updateUser" parameterType="com.itheima.pojo.User">
update users set uname= #{uname},uage = #{uage} where uid = #{uid}
</update>
2.3.5 <delete>元素
1.<delete>元素的删除使用
<delete>元素用于映射删除语句,在执行完<delete>元素中的SQL语句之后,会返回删除的记录数量。使用<delete>元素执行删除操作非常简单,示例代码如下所示:
<!-- 删除操作 -->
<delete id="deleteUser" parameterType="Integer">
delete from users where uid=#{uid}
</delete>
<delete>元素中,除了上述示例代码中的几个属性外,还有其他一些可以配置的属性,如flushCache、timeout等。
2.delete>元素中的属性
2.3.6 <sql>元素
1.<sql>元素的作用
在一个映射文件中,通常需要定义多条SQL语句,这些SQL语句的组成可能有一部分是相同的(如多条select语句中都查询相同的id、username字段),如果每一个SQL语句都重写一遍相同的部分,势必会增加代码量。针对此问题,可以在映射文件中使用MyBatis所提供的<sql>元素,将这些SQL语句中相同的组成部分抽取出来,然后在需要的地方引用。
<sql>元素的作用是定义可重用的SQL代码片段,它可以被包含在其他语句中。<sql>元素可以被静态地(在加载参数时)参数化,<sql>元素不同的属性值通过包含的对象发生变化。
2.实现一个根据客户id查询客户信息的SQL片段
<!--定义要查询的表 -->
<sql id=“someinclude">from <include refid="${include_target}" /></sql>
<!--定义查询列 -->
<sql id=“userColumns"> uid,uname,uage </sql>
<!--根据客户id查询客户信息 -->
<select id="findUserById" parameterType="Integer" resultType="com.itheima.pojo.User">
select
<include refid="userColumns"/>
<include refid="someinclude">
<property name="include_target" value="users" /></include>
where uid = #{uid}
</select>
2.3.7 <resultMap>元素
1.<resultMap>元素的作用
<resultMap>元素表示结果映射集,是MyBatis中最重要也是功能最强大的元素。<resultMap>元素主要作用是定义映射规则、更新级联以及定义类型转化器等。 数据表中的列和需要返回的对象的属性可能不会完全一致,这种情况下MyBatis不会自动赋值,这时就需要使用<resultMap>元素进行结果集映射。
2.接下来通过一个具体的案例演示使用<resultMap>元素进行结果集映射,具体步骤如下。
1). 在名称为mybatis的数据库中,创建一个t_student表,并插入几条测试数据。
USE mybatis;
CREATE TABLE t_student(
sid INT PRIMARY KEY AUTO_INCREMENT,
sname VARCHAR(50),
sage INT
);
INSERT INTO t_student(sname,sage) VALUES('Lucy',25);
INSERT INTO t_student(sname,sage) VALUES('Lili',20);
INSERT INTO t_student(sname,sage) VALUES('Jim',20);
2).创建实体类Student,用于封装学生信息。在类中定义id、name和age属性,以及属性的getter/setter方法和toString()方法。
package com.itheima.pojo;
public class Student {
private Integer id; // 主键id
private String name; // 学生姓名
private Integer age; // 学生年龄
// 省略getter/setter方法
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
3). 创建映射文件StudentMapper.xml,在映射文件中编写映射查询语句。\
<!-- 只显示mapper元素的内容-->
<mapper namespace="com.itheima.mapper.StudentMapper">
<resultMap type="com.itheima.pojo.Student" id="studentMap">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="age" column="sage"/>
</resultMap>
<select id="findAllStudent" resultMap="studentMap">
select * from t_student
</select>
</mapper>
4). 在核心配置文件mybatis-config.xml中,引入StudentMapper.xml,将StudentMapper.xml映射文件加载到程序中。在mybatis-config.xml中的<mapper>元素下添加如下代码。
<mapper resource="com/itheima/mapper/StudentMapper.xml">
<mapper>
5). 创建测试类MyBatisTest,在测试类中,编写测试方法findAllStudentTest(),用于测试<resultMap>元素实现查询结果的映射。
public class MyBatisTest {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;
// init()方法省略
@Test
public void findAllStudentTest() {
List<Student> list = sqlSession.selectList("com.itheima.mapper.StudentMapper.findAllStudent");
for (Student student : list) {
System.out.println(student);
}
}
// destory()方法省略
}
6).运行MyBatisTest测试类,控制台会输出结果。
需要注意的是,在测试类MyBatisTest中,每一个用@Test注解标注的方法称为测试方法, 他们的调用顺序为@Before→@Test→@After。
3.多学一招:使用工具类创建SqlSession对象
在上述案例中,由于每个方法执行时都需要读取配置文件,并根据配置文件的信息构建SqlSessionFactory对象、创建SqlSession对象、释放资源,这导致了大量的重复代码。为了简化开发,我们可以将读取配置文件和释放资源的代码封装到一个工具类中,然后通过工具类创建SqlSession对象。
2.4 案例:员工管理系统
任务:完成一个员工管理系统,能够实现如下功能根据id查询、新增、修改、删除员工信息
2.4.1 员工表详情
现有一张员工表如下。利用本章所学知识完成一个员工管理系统。实现如下功能:根据id查询员工信息、新增员工信息、根据id修改员工信息、根据id删除员工信息。
员工编号(id) | 商品名称(name) | 员工年龄(age) | 员工职位(position) |
1 | 张三 | 20 | 员工 |
2 | 李四 | 18 | 员工 |
3 | 王五 | 35 | 经理 |
2.4.2 案例要求
本案例要求根据员工表在数据库中创建一个employee表,并利用本章所学知识完成一个员工管理系统,该系统需要实现以下几个功能:
根据id查询员工信息;新增员工信息;
根据id修改员工信息;根据id删除员工信息。
1.项目搭建:创建建一个名称为mybatisdemo的项目,并在项目中引入 MySQL 驱动包、 JUnit 测试包、MyBatis 的核心包等相关依赖、创建数据库连接信息配置文件、创建 MyBatis 的核心配置文件。核心配置文件的内容如下。
<configuration><properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}" />
<property name="url" value="${mysql.url}" />
<property name="username" value="${mysql.username}" />
<property name="password" value="${mysql.password}" />
</dataSource>
</environment></environments>
</configuration>
2.数据准备:在mybatis数据库中创建employee表,并在employee表中插入几条数据。
use mybatis;
create table employee(
id int primary key auto_increment,
name varchar(20) not null,
age int not null,
position varchar(20)
);
insert into employee(id,name,age,position) values(null,'张三',20,'员工‘),(null,'李四',18, '员工'),(null,'王五',35,'经理');
3.POJO类准备:创建持久化类Employee,并在类中声明id(编号)、name(姓名)、age(年龄)和position(职位)属性,以及属性对应的getter/setter方法。
public class Employee {
private Integer id;
private String name;
private Integer age;
private String position;
// 省略getter/setter方法
@Override
public String toString() {
return "Employee{" + "id=" + id + ", name=" + name +
", age=" + age + ", position=" + position +'}’;
}
}
4.编写映射文件:创建映射文件EmployeeMapper.xml,该文件主要用于实现SQL语句和Java对象之间的映射,部分文件内容如下。
<mapper namespace="com.itheima.mapper.EmployeeMapper">
<select id="findById"parameterType="Integer”
resultType="com.itheima.pojo.Employee"> select * from employee where id = #{id}
</select>
<insert id="addEmployee" parameterType="com.itheima.pojo.Employee">
insert into employee(id,name,age,position) values (#{id},#{name},#{age},#{position})
</insert>
</mapper>
5.修改mybatis-config.xml核心配置文件:在mybatis-config.xml映射文件的<mappers>元素下添加EmployeeMapper.xml映射文件路径的配置,用于将EmployeeMapper.xml映射文件加载到程序中。
<mapper resource="com/itheima/mapper/EmployeeMapper.xml"></mapper>
6.编写MyBatisUtils工具类 :创建MyBatisUtils工具类,该类用于封装读取配置文件信息的代码。
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
// 使用MyBatis提供的Resources类加载MyBatis的配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 构建SqlSessionFactory工厂
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSession() {
//获取SqlSession对象的静态方法
return sqlSessionFactory.openSession();
}
}
7.编写测试类
(1)在项目src/test/java目录下创建Test包,在Test包下创建MyBatisTest测试类,用于程序测试。在MyBatisTest测试类中添加findByIdTest()方法,用于根据id查询员工信息。
(2)在MyBatisTest测试类中添加insertEmployeeTest()方法,用于插入员工信息。
(3)在MyBatisTest测试类中添加updateEmployeeTest()方法,用于更新员工信息。
(4)在MyBatisTest测试类中添加deleteEmployeeTest()方法,用于删除员工信息。
3.动态SQL
前言
在实际项目的开发中,开发人员在使用JDBC或其他持久层框架进行开发时,经常需要根据不同的条件拼接SQL语句,拼接SQL语句时还要确保不能遗漏必要的空格、标点符号等,这种编程方式给开发人员带来了非常大的不便,而MyBatis提供的SQL语句动态组装功能,恰能很好地解决这一问题。本章将对MyBatis框架的动态SQL进行详细讲解。
3.1 动态SQL中的元素
3.1.1 使用动态SQL的好处
动态SQL是MyBatis的强大特性之一,MyBatis采用了功能强大的基于OGNL(Object Graph Navigation Language)的表达式来完成动态SQL。在MyBatis的映射文件中,开发人员可通过动态SQL元素灵活组装SQL语句,这在很大程度上避免了单一SQL语句的反复堆砌,提高了SQL语句的复用性。
3.1.2 动态SQL常用元素
3.2 条件查询操作
3.2.1 <if>元素
1.<if>元素的应用
在MyBatis中,<if>元素是最常用的判断元素,它类似于Java中的if语句,主要用于实现某些简单的条件判断。在实际应用中,我们可能会通过某个条件查询某个数据。例如,要查找某个客户的信息,可以通过姓名或者年龄来查找客户,也可以不填写年龄直接通过姓名来查找客户,还可以都不填写而查询出所有客户,此时姓名和年龄就是非必须条件。类似于这种情况,在MyBatis中就可以通过<if>元素来实现。
1). 数据库准备:在名称为mybatis的数据库中,创建一个t_customer表,并插入几条测试数据。
USE mybatis;
CREATE TABLE t_customer (
id int(32) PRIMARY KEY AUTO_INCREMENT,
username varchar(50),
jobs varchar(50),
phone varchar(16));
INSERT INTO t_customer VALUES ('1', 'joy', 'teacher', '13733333333');
INSERT INTO t_customer VALUES ('2', 'jack', 'teacher', '13522222222');
INSERT INTO t_customer VALUES ('3', 'tom', 'worker', '15111111111');
2). POJO类准备:创建持久化类Customer,在类中声明id、username、jobs和phone属性,及属性对应的getter/setter方法。
public class Customer {
private Integer id; private String username; // 主键ID、客户名称
private String jobs; private String phone; // 职业、电话
// 省略getter/setter方法
@Override
public String toString() {
return "Customer [id=" + id + ", username=" + username + ", jobs=" + jobs + ", phone=" + phone + "]";
}
}
3). 创建映射文件:创建映射文件CustomerMapper.xml,在映射文件中,根据客户姓名和年龄组合条件查询客户信息,使用<if>元素编写该组合条件的动态SQL。
<!– 该xml文件中只列出了if元素的动态SQL-->
<if test="username != null and username != "">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=‘’“>
and jobs= #{jobs}
</if>
4). 修改核心配置文件:在配置文件mybatis-config.xml中,引入CustomerMapper.xml映射文件,将CustomerMapper.xml映射文件加载到程序中。
<mapper resource="com/itheima/mapper/CustomerMapper.xml"></mapper>
5). 创建获取SqlSession对象的工具类。
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
// 初始化SqlSessionFactory对象
static {
try {
// 使用MyBatis提供的Resources类加载MyBatis的配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// 构建SqlSessionFactory工厂
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取SqlSession对象的静态方法
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}
6). 编写测试类:在测试类MyBatisTest中,编写测试方法findCustomerByNameAndJobsTest(),该方法用于根据客户姓名和职业组合条件查询客户信息列表。
public class MyBatisTest {
@Test
public void findCustomerByNameAndJobsTest(){
SqlSession session = MyBatisUtils.getSession();
Customer customer = new Customer();
customer.setUsername(“jack");
customer.setJobs("teacher");
List<Customer> customers = session.selectList("com.itheima.mapper.CustomerMapper.findCustomerByNameAndJobs",customer);
for (Customer customer2 : customers) {
System.out.println(customer2);
}
session.close();
}
}
3.2.2 <choose>、<when>、<otherwise>元素
1.<choose><when>otherwise>使用场景
在使用<if>元素时,只要test属性中的表达式为true,就会执行元素中的条件语句,但是在实际应用中,有时只需要从多个选项中选择一个去执行。
例如下面的场景:“当客户名称不为空,则只根据客户名称进行客户筛选;当客户名称为空,而客户职业不为空,则只根据客户职业进行客户筛选。当客户名称和客户职业都为空,则要求查询出所有电话不为空的客户信息。”
针对上面情况,使用<if>元素进行处理是不合适的。MyBatis提供了<choose>、<when>、<otherwise>元素进行处理,这三个元素往往组合在一起使用,作用相当于Java语言中的if…else if…else。
1).在映射文件CustomerMapper.xml中,添加使用<choose>、<when>、<otherwise>元素执行上述情况的动态SQL。
<!-- 只展示三个组合元素的部分-->
<choose>
<when test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</when>
<when test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</when>
<otherwise>and phone is not null</otherwise>
</choose>
2).在测试类MyBatisTest中,编写测试方法findCustomerByNameOrJobsTest(),该方法用于根据客户姓名或职业查询客户信息列表。
public void findCustomerByNameOrJobsTest() {
SqlSession session=MyBatisUtils.getSession();
Customer customer=new Customer();
customer.setUsername("tom");customer.setJobs("teacher");
List<Customer> customers = session.selectList("com.itheima.mapper.CustomerMapper.findCustomerByNameOrJobs",customer);
for (Customer customer2 : customers) {
System.out.println(customer2);
}
session.close();
}
3.2.3 <where>、<trim>元素
1.<where><trim>使用场景
在映射文件中,编写的SQL后面加入了“where 1=1”的条件的话,既保证了where后面的条件成立,又避免了where后面第一个词是and或者or之类的关键字。
例如下面这条Mybatis拼接出的SQL语句是不正确的。
select * from t_customer where and username like concat('%',?, '%') and jobs = #{jobs}
上述SQL语句中,where后直接跟的是and,这在运行时会报SQL语法错误,针对这种情况,可以使用MyBatis提供的<where>元素和<trim>元素进行处理。
2.<where>元素
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.pojo.Customer" resultType="com.itheima.pojo.Customer">
select * from t_customer
<where>
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</where>
</select>
上述代码配置中,<where>元素会自动判断由组合条件拼装的SQL语句,只有<where>元素内的某一个或多个条件成立时,才会在拼接SQL中加入where关键字,否则将不会添加;即使where之后的内容有多余的“AND”或“OR”,<where>元素也会自动将他们去除。
3.<trim>元素
<trim>元素用于删除多余的关键字,它可以直接实现<where>元素的功能。<trim>元素包含4个属性。
<select id="findCustomerByNameAndJobs" parameterType="com.itheima.pojo.Customer" resultType="com.itheima.pojo.Customer">
select * from t_customer
<trim prefix="where" prefixOverrides="and" >
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</trim>
</select>
上述配置代码中,<trim>元素的作用是去除一些多余的前缀字符串,它的prefix属性代表的是语句的前缀(where),而prefixOverrides属性代表的是需要去除的前缀字符串(SQL中的“AND”或“OR”)。
3.3 更新操作
3.3.1 <set>元素使用场景
在Hibernate框架中,如果想要更新某一个对象,就需要发送所有的字段给持久化对象,然而在实际应用中,大多数情况下都是更新某一个或几个字段。如果更新的每一条数据都要将其所有的属性都更新一遍,那么执行效率是非常差的。为了解决更新数据的效率问题,MyBatis提供了<set>元素。<set>元素主要用于更新操作,它可以在动态SQL语句前输出一个SET关键字,并将SQL语句中最后一个多余的逗号去除。<set>元素与<if>元素结合可以只更新需要更新的字段。
1). 在映射文件CustomerMapper.xml中,添加使用<set>元素执行更新操作的动态SQL。
<update id="updateCustomerBySet" parameterType="com.itheima.pojo.Customer">
update t_customer
<set>
<if test="username != null and username != ''">
username=#{username},</if>
<if test="jobs !=null and jobs !=''">jobs = #{jobs},</if>
<if test="phone !=null and phone !=''">phone = #{phone},</if>
</set> where id = #{id}
</update>
2). 编写测试方法updateCustomerBySetTest()。
public void updateCustomerBySetTest() {
SqlSession sqlSession = MyBatisUtils.getSession();
Customer customer = new Customer();
customer.setId(3);
customer.setPhone("13311111234");
int rows = sqlSession.update("com.itheima.mapper.CustomerMapper.updateCustomerBySet", customer);
if(rows > 0) {
System.out.println("您成功修改了"+rows+"条数据!");
} else {
System.out.println("执行修改操作失败!!!");
}
sqlSession.commit();
sqlSession.close();
}
注意:
<set>元素字段非空
在映射文件中使用<set>元素和<if>元素组合进行update语句动态SQL组装时,如果<set>元素内包含的内容都为空,则会出现SQL语法错误。因此,在使用<set>元素进行字段信息更新时,要确保传入的更新字段不能都为空。
使用<trim>元素更新
除了使用<set>元素外,还可以通过<trim>元素来实现更新操作。其中, <trim>元素的prefix属性指定要添加的<trim>元素所包含内容的前缀为set,suffixOverrides属性指定去除的<trim>元素所包含内容的后缀为逗号 。
3.4 复杂查询操作
3.4.1 <foreach>元素中的属性
1.<foreach>元素的属性
2.<collection>属性的取值
在遍历参数时,<collection>属性的值是必须指定的。不同情况下,该属性的取值也是不一样的,主要有以下三种情况:List类型、数值类型、Map类型。
3.List类型
若入参为单参数且参数类型是一个List,collection属性值为list。
4.数组类型
若入参为单参数且参数类型是一个数组,collection属性值为array。
5.Map类型
若传入参数为多参数,就需要把参数封装为一个Map进行处理,collection属性值为Map。若传入参数为多参数,就需要把参数封装为一个Map进行处理,collection属性值为Map。
3.4.2 <foreach>元素迭代数组
1.<foeeach>实现入参为数组类型的遍历
例如,要从数据表t_customer中查询出id为1、2、3的客户信息,就可以利用数组作为参数,存储id的属性值1、2、3,并通过<foreach>元素迭代数组完成客户信息的批量查询操作。
1).在映射文件CustomerMapper.xml中,添加使用<foreach>元素迭代数组执行批量查询操作的动态SQL。
<select id="findByArray" parameterType="java.util.Arrays" resultType="com.itheima.pojo.Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
select>
2). 在测试类MyBatisTest中,编写测试方法findByArrayTest()方法,实现客户信息的批量查询。
public void findByArrayTest() {
SqlSession session = MyBatisUtils.getSession(); // 获取SqlSession
Integer[] roleIds = {2,3}; // 创建数组,封装查询id
// 执行SqlSession的查询方法,返回结果集
List<Customer> customers = session.selectList("com.itheima.mapper.CustomerMapper.findByArray", roleIds);
for (Customer customer : customers) {
System.out.println(customer);
}
session.close();
}
3). 执行MyBatisTest测试类的findByArrayTest()方法,控制台会输出结果。
3.4.3 <foreach>元素迭代List
1).在映射文件CustomerMapper.xml中,添加使用<foreach>元素迭代List集合执行批量查询操作的动态SQL。
<select id="findByList" parameterType="java.util.Arrays" resultType="com.itheima.pojo.Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
2).在测试类MyBatisTest中,编写测试方法findByListTest(),用于批量查询客户信息。
public void findByListTest() {
SqlSession session = MyBatisUtils.getSession();
List<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(2);
List<Customer> customers = session.selectList("com.itheima.mapper.CustomerMapper.findByList", ids);
for (Customer customer : customers) {
System.out.println(customer);
}
session.close();
}
3). 执行MyBatisTest测试类的findByListTest()方法,控制台会输出结果。
3.4.4 <foreach>元素迭代Map
1).在映射文件CustomerMapper.xml中,添加使用<foreach>元素迭代Map集合执行批量查询操作的动态SQL。
<select id="findByMap" parameterType="java.util.Map" resultType="com.itheima.pojo.Customer">
select * from t_customer where jobs=#{jobs} and id in
<foreach item="roleMap" index="index" collection="id" open="(" separator="," close=")">
#{roleMap}
</foreach>
</select>
2).在测试类MyBatisTest中,编写测试方法findByMapTest(),用于批量查询客户信息。
public void findByMapTest() {
SqlSession session = MyBatisUtils.getSession();
List<Integer> ids=new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
Map<String,Object> conditionMap = new HashMap<String, Object>();
conditionMap.put(“id",ids);
conditionMap.put("jobs","teacher");
List<Customer> customers = session.selectList("com.itheima.mapper.CustomerMapper.findByMap", conditionMap);
for (Customer customer : customers) {
System.out.println(customer);
}
session.close();
}
3). 执行MyBatisTest测试类的findByMapTest()方法,控制台会输出结果。
3.5 案例:学生信息查询系统任务
1.学生信息查询系统的功能
对MyBatis的动态SQL进行了详细讲解,包括使用动态SQL进行条件查询、更新以及复杂查询操作。本案例要求利用本节所学知识完成一个学生信息查询系统,该系统要求实现2个以下功能。
2.多条件查询
当用户输入的学生姓名不为空,则只根据学生姓名进行学生信息的查询;
当用户输入的学生姓名为空,而学生专业不为空,则只根据学生专业进行学生的查询;
3.单条件查询
查询出所有id值小于5的学生的信息;
4.多条件查询
1). 项目搭建:创建一个名称为mybatis-demo03的项目,项目的具体搭建过程请参考1.3节。
2). 数据准备:在名称为mybatis的数据库中,创建一个dm_student表,并插入几条测试数据。
USE mybatis;
CREATE TABLE dm_student(
id int(32) PRIMARY KEY AUTO_INCREMENT,
name varchar(50),
major varchar(50),
sno varchar(16) );
# 插入7条数据,其他省略
INSERT INTO dm_student VALUES ('1', '张三', '数学', '10001');
3). POJO类准备:创建持久化类Student,在类中声明id、name、major和sno属性,以及属性对应的getter/setter方法。
// 定义变量主键id,姓名name,专业major,学号sno
public class Student {
private Integer id;
private String name;
private String major;
private String sno;
// 省略getter/setter方法
@Override
public String toString() {
return “Student{” + “id=” + id + “, name=‘” + name + “, major=" + major + ", sno=" + sno + '}'
}
}
4). 创建映射文件:创建映射文件StudentMapper.xml,编写根据学生姓名和专业组合成的条件查询学生信息的动态SQL。
<mapper namespace="com.itheima.mapper.StudentMapper">
<select id=“findStudentByNameAndMajor” parameterType=“com.itheima.pojo.Student”resultType="com.itheima.pojo.Student">
select * from dm_student where 1=1 <choose>
<when test="name !=null and name !=''">
and name like concat('%',#{name}, '%')</when>
<when test="major !=null and major !=''"> and major= #{major}</when>
<otherwise> and sno is not null</otherwise> </choose>
</select>
</mapper>
5). 修改mybatis-config.xml核心配置文件:在mybatis-config.xml映射文件的<mappers>元素下添加StudentMapper.xml映射文件路径的配置,用于将StudentMapper.xml映射文件加载到程序中。具体配置代码如下。
<mapper resource="com/itheima/mapper/StudentMapper.xml"/>
6). 编写MyBatisUtils工具类:创建MyBatisUtils工具类,该类用于封装读取配置文件信息的代码。
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
Reader reader = Resources.getResourceAsReader(“mybatis-config.xml”);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();}
}
public static SqlSession getSession() {
return sqlSessionFactory.openSession();}
}
7). 编写测试方法:在测试类MyBatisTest中,编写测试方法findStudentByNameOrMajorTest(),该方法用于根据学生姓名或专业查询学生信息。
public void findStudentByNameOrMajorTest() {
SqlSession session=MyBatisUtils.getSession();
Student student=new Student();
student.setName("张三");
student.setMajor("英语");
List<Student> students = session.selectList("com.itheima.mapper.StudentMapper.findStudentByNameAndMajor",student);
for (Student student2 : students) {
System.out.println(student2);
}
session.close();
}
8.查看运行结果:执行测试类MyBatisTest的findStudentByNameOrMajorTest()方法,控制台会输出结果。
5.多条件查询案例结果分析
由输出结果分析可知,在查询学生信息时,虽然同时传入了姓名和专业两个查询条件,但MyBatis所生成的SQL只是动态组装了学生姓名条件进行查询。如果将案例代码中的student.setName(“张三”)删除或者注释掉,使SQL只按专业进行查询。再次执行findStudentByNameOrMajorTest ()方法,MyBatis生成的SQL会组装学生职业进行条件查询,同样查询出了学生信息。如果学生姓名和专业都为空, MyBatis的SQL便会组装<otherwise>元素中的SQL片段进行条件查询。
6.单条件查询
1). 修改映射文件:在映射文件StudentMapper.xml中的<mapper>元素下,编写查询所有id值小于5的学生信息的动态SQL。
<select id="findByList" parameterType="java.util.List" resultType="com.itheima.pojo.Student">
select * from dm_student where id in
<foreach item="id" index="index" collection="list" open="(" separator="," close=")">
#{id}
</foreach>
</select>
2). 编写测试方法 :在测试类MyBatisTest中,编写测试方法findByListTest()。
public void findByListTest() {
SqlSession session = MyBatisUtils.getSession();
List<Integer> ids=new ArrayList<Integer>();
for(int i =1;i<5;i++){
ids.add(i);}
List<Student> students = session.selectList("com.itheima.mapper"
+ ".StudentMapper.findByList", ids);
for (Student student : students) {
System.out.println(student);
}
session.close();
}
4.MyBatis的关联映射和缓存机制
前言
前面介绍了MyBatis的基本用法、关联映射和动态SQL等重要知识,但这些知识只是针对单表实现进行操作的,在实际开发中,对数据库的操作常常会涉及到多张表,针对多表之间的操作,MyBatis提供了关联映射,通过关联映射可以很好地处理表与表、对象与对象之间的关联关系。此外,在实际开发中经常需要合理地利用MyBatis缓存来加快数据库查询,进而有效地提升数据库性能。本章将对MyBatis的关联映射以及MyBatis缓存机制进行详细讲解。
4.1 关联映射概述
1.关联映射关系
在关系型数据库中,表与表之间存在着三种关联映射关系,分别为一对一关系、一对多关系和多对多关系。
2.一对一关系
一个数据表中的一条记录最多可以和另一个数据表中的一条记录相关。例如,现实生活中学生与校园卡就属于一对一的关系,一个学生只能拥有一张校园卡,一张校园卡只能属于一个学生。
3.一对多关系
主键数据表中的一条记录可以和另外一个数据表的多条记录相关。但另外一个数据表中的记录只能与主键数据表中的某一条记录相关。例如,现实中班级与学生的关系就属于一对多的关系,一个班级可以有很多学生,但一个学生只能属于一个班级。
4.多对多关系
一个数据表中的一条记录可以与另外一个数据表任意数量的记录相关,另外一个数据表中的一条记录也可以与本数据表中任意数量的记录相关。例如,现实中学生与教师属于多对多的关系,一名学生可以由多名教师授课,一名教师可以为多名学生授课。
5.Java对象如何描述事物之间的关系
数据表之间的关系实质上描述的是数据之间的关系,除了数据表,在Java中,还可以通过对象来描述数据之间的关系。通过Java对象描述数据之间的关系,其实就是使对象的属性与另一个对象的属性相互关联。
6.Java对象关联映射关系
Java对象描述数据之间的关联映射关系有三种,分别是一对一、一对多和多对多。
7.一对一
就是在本类中定义与之关联的类的对象作为属性,例如,A类中定义B类对象b作为属性,在B类中定义A类对象a作为属性。
8.一对多
就是一个A类对象对应多个B类对象的情况,例如,定义在A类中,定义一个B类对象的集合作为A类的属性;在B类中,定义A类对象a作为B类的属性。
9.多对多
在两个相互关联的类中,都可以定义多个与之关联的类的对象。例如,在A类中定义B类类型的集合作为属性,在B类中定义A类类型的集合作为属性。
4.2 一对一查询
4.2.1 <association>元素
在现实生活中,一对一关联关系是十分常见的。例如,一个人只能有一个身份证,同时一个身份证也只会对应一个人。人与身份证之间的关联关系如图。
在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。
4.2.2 <association>元素属性
4.2.3 <association>元素的配置方式
<association>元素是<resultMap>元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式,下面对这两种配置方式分别进行介绍。
a.嵌套查询方式
嵌套查询是指通过执行另外一条SQL映射语句来返回预期的复杂类型。
<association property="card" column="card_id" javaType="com.itheima.pojo.IdCard" select="com.itheima.mapper.IdCardMapper.findCodeById" />
b.嵌套结果方式
嵌套结果是使用嵌套结果映射来处理重复的联合结果的子集。
<association property="card" javaType="com.itheima.pojo.IdCard">
<id property="id" column="card_id" />
<result property="code" column="code" />
</association>
接下来就以个人和身份证之间的一对一关联关系为例,对MyBatis中一对一关联关系的处理进行 详细讲解。案例具体实现步骤如下。
1)创建数据表:在mybatis数据库中分别创建名为tb_idcard的身份证数据表和名称为tb_person的个人数据表,同时预先插入几条数据。
USE mybatis;
# 创建一个名称为tb_idcard的表
CREATE TABLE tb_idcard(
id INT PRIMARY KEY AUTO_INCREMENT,
CODE VARCHAR(18) );
# 插入2条数据
INSERT INTO tb_idcard(CODE) VALUES('152221198711020624');
# 创建一个名称为tb_person的表,同理
2)持久化类IDCard类:创建持久化类IdCard,用于封装身份证属性。
public class IdCard {
private Integer id; // 主键id
private String code; // 身份证号码
// 省略getter/setter方法
@Override
public String toString() {
return "IdCard [id=" + id + ", code=" + code + "]";
}
}
3)持久化类Person类:创建持久化类Person,用于封装个人属性。
public class Person {
private Integer id; // 主键id
private String name; // 姓名
private Integer age; // 年龄
private String sex; // 性别
private IdCard card; // 人员关联的证件
// 省略getter/setter方法,重写的toString()方法
}
4)编写IdCardMapper.xml文件:创建身份证映射文件IdCardMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.IdCardMapper">
<!-- 根据id查询证件信息 -->
<select id="findCodeById" parameterType="Integer" resultType="IdCard">
SELECT * from tb_idcard where id=#{id}
</select>
</mapper>
5)编写PersonMapper.xml文件:创建人员映射文件PersonMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。
<select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult">
SELECT * from tb_person where id=#{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult">
<id property="id" column="id" />
<result property="name" column="name" />
<result property="age" column="age" />
<result property="sex" column="sex" />
<association property="card" column="card_id" javaType="IdCard” select="com.itheima.mapper.IdCardMapper.findCodeById" />
</resultMap>
6)引入映射文件:在核心配置文件mybatis-config.xml中,引入IdCardMapper.xml和PersonMapper.xml映射文件,并为com.itheima.pojo包下的所有实体类定义别名。
<!-- 只展示了定义别名和mapping文件中配置新添加的部分-- >
<!--使用扫描包的形式定义别名 -->
<typeAliases>
<package name="com.itheima.pojo" />
</typeAliases>
<mappers>
<mapper resource="com/itheima/mapper/IdCardMapper.xml" />
<mapper resource="com/itheima/mapper/PersonMapper.xml" />
</mappers>
7)编写测试类:在测试类MyBatisTest中,编写测试方法findPersonByIdTest()。
public void findPersonByIdTest() {
// 1、通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.使用MyBatis嵌套查询的方式查询id为1的人的信息
Person person = session.selectOne("com.itheima.mapper."
+ "PersonMapper.findPersonById", 1);
// 3、输出查询结果信息
System.out.println(person);
// 4、关闭SqlSession
session.close();
}
多学一招:MyBatis延迟加载的配置
在使用MyBatis嵌套查询方式进行MyBatis关联映射查询时,使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在mybatis-config.xml中的<settings>元素内进行配置。
<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true" />
<!-- 将积极加载改为消息加载,即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
4.3 一对多查询
4.3.1 用户与订单关联关系图
与一对一的关联关系相比,接触更多的关联关系是一对多(或多对一)。例如一个用户可以有多个订单,多个订单也可以归一个用户所有。用户和订单的关联关系如图。
4.3.2 <collection>元素
在MyBatis中,通过<collection>元素来处理一对多关联关系。<collection>元素的属性大部分与<association>元素相同,但其还包含一个特殊属性一ofType。ofType属性与javaType属性对应,它用于指定实体类对象中集合类属性所包含的元素的类型。
4.3.3 <collection>元素的配置方式
<collection>元素是<resultMap>元素的子元素,<collection >元素有嵌套查询和嵌套结果两种配置方式。
a.嵌套查询方式
<collection property="ordersList" column="id" ofType="com.itheima.pojo.Orders" select="com.itheima.mapper.OrdersMapper.selectOrders"/>
b.嵌套结果方式
<collection property="ordersList"ofType="com.itheima.pojo.Orders">
<id property="id" column="orders_id" />
<result property="number" column="number" />
</collection>
接下来以用户和订单之间的一对多关联关系为例,详细讲解如何在MyBatis中处理一对多关联 关系,具体步骤如下。
1)在名为mybatis的数据库中,创建两个数据表,分别为tb_user(用户数据表)和tb_orders(订单表),同时在表中预先插入几条测试数据。
USE mybatis;
# 创建一个名称为tb_user的表
CREATE TABLE tb_user (
id int(32) PRIMARY KEY AUTO_INCREMENT,
username varchar(32),
address varchar(256) );
# 插入3条数据,其他语句省略
INSERT INTO tb_user VALUES ('1', '小明', '北京');
# 创建一个名称为tb_orders的表,同理
2)创建持久化类Orders,并在类中定义订单id和订单编号等属性。
public class Orders {
private Integer id; //订单id
private String number; //订单编号
// 省略getter/setter方法
@Override
public String toString() {
return "Orders [id=" + id + ", number=" + number + "]";
}
}
3)创建持久化类Users,并在类中定义用户编号、用户姓名、 用户地址以及用户关联的订单等属性。
public class Users {
private Integer id; // 用户编号
private String username; // 用户姓名
private String address; // 用户地址
private List<Orders> ordersList; // 用户关联的订单
// 省略getter/setter方法
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", address="+ address + ", ordersList=" + ordersList + "]";
}
}
4)创建用户实体映射文件UsersMapper.xml,并在文件中编写一对多关联映射查询的配置。
<mapper namespace="com.itheima.mapper.UsersMapper">
<resultMap type="Users" id="UserWithOrdersResult">
<!-- 只展示了部分标签内容,一对多关联映射 -->
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
</collection>
</resultMap>
</mapper>
5)在核心配置文件mybatis-config.xml中,引入UsersMapper.xml,将UsersMapper.xml映射文件加载到程序中。
<mapper resource="com/itheima/mapper/UsersMapper.xml”/>
6)在测试类MyBatisTest中,编写测试方法findUserTest()。
public void findUserTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.查询id为1的用户信息
Users users = session.selectOne("com.itheima.mapper." + "UsersMapper.findUserWithOrders", 1);
// 3.输出查询结果信息
System.out.println(users);
// 4.关闭SqlSession
session.close();
}
4.4 多对多查询
4.4.1 订单和商品多对多关系图
在实际项目开发中,多对多的关联关系非常常见。以订单和商品为例,一个订单可以包含多种商品,而一种商品又可以属于多个订单,订单和商品属于多对多关联关系,订单和商品之间的关联关系如图。
在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表中的订单id作为外键关联订单表的id,中间表中的商品id作为外键关联商品表的id。这三个表之间的关系如图。
下面以订单表与商品表之间的多对多关系为例,讲解如何使用MyBatis处理多对多的关系
1)在名为mybatis的数据库中创建名称为tb_product的商品表和名称为tb_ordersitem 的中间表,同时在表中预先插入几条数据。
CREATE TABLE tb_product (
id INT(32) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32), price DOUBLE );
# 插入1条数据,其他省略
INSERT INTO tb_product VALUES ('1', 'Java基础入门', '44.5’);
<!-- tb_ordersitem表的创建省略-->
2)创建持久化类Product,并在类中定义商品id、商品名称、商品单价等属性,以及与订单关联的属性。
public class Product {
private Integer id; private String name;
private Double price;
private List<Orders> orders; //关联订单属性
// 省略getter/setter方法
@Override
public String toString() {
return "Product [id=" + id + ", name=" + name + ", price=" + price + "]";
}
}
3)在商品持久化类中,除了需要添加订单的集合属性外,还需要在订单持久化类(Orders.java)中增加商品集合的属性及其对应的getter/setter方法,Orders类中添加的代码如下。
// 关联商品集合属性
private List<Product> productList;
// 省略getter/setter方法,以及重写的toString()方法
4)创建订单实体映射文件OrdersMapper.xml,用于编写订单信息的查询SQL语句,并在映射文件中编写多对多关联映射查询的配置信息。
<mapper namespace="com.itheima.mapper.OrdersMapper">
<select id="findOrdersWithPorduct"parameterType="Integer" resultMap="OrdersWithProductResult">
select * from tb_orders WHERE id=#{id} </select>
<resultMap type="Orders" id="OrdersWithProductResult">
<id property="id" column="id" /><result property="number" column="number" />
<collection property="productList" column="id" ofType="Product" select=“com.itheima.mapper.ProductMapper.findProductById" ></collection>
</resultMap>
</mapper>
5)创建商品实体映射文件ProductMapper.xml,用于编写订单与商品信息的关联查询SQL语句。
<mapper namespace="com.itheima.mapper.ProductMapper">
<select id="findProductById" parameterType="Integer" resultType="Product">
SELECT * from tb_product where id IN(SELECT product_id FROM tb_ordersitem WHERE orders_id = #{id} )
</select>
</mapper>
6)将新创建的映射文件OrdersMapper.xml和ProductMapper.xml的文件路径配置到核心配置文件mybatis-config.xml中。
<mapper resource="com/itheima/mapper/OrdersMapper.xml" />
<mapper resource="com/itheima/mapper/ProductMapper.xml" />
7)在测试类MyBatisTest中,编写多对多关联查询的测试方法findOrdersTest()。
public void findOrdersTest() {
// 1.生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.查询id为1的订单中的商品信息
Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithPorduct", 1);
System.out.println(orders);// 3.输出查询结果信息
session.close();// 4.关闭SqlSession
}
查询订单及关联商品的另一方式
除了使用嵌套查询的方式查询订单及其关联的商品信息外,还可以在OrdersMapper.xml中使用嵌套结果的方式进行查询。
4.5 MyBatis缓存机制
4.5.1 一级缓存
1.MyBatis的一级缓存级别
MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时,在第一次执行完成后,MyBatis会将查询结果写入到一级缓存中,此后,如果程序没有执行插入、更新、删除操作,当第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不用再去数据库查询,从而提高了数据库的查询效率。
2.举例说明MyBatis的一级缓存级别
例如,存在数据表tb_book,从表中多次查询id为1的图书信息,当程序第一次查询id为1的图书信息时,程序会将查询结果写入MyBatis一级缓存,当程序第二次查询id为1的图书信息时,MyBatis直接从一级缓存中读取,不再访问数据库进行查询。当程序对数据库执行了插入、更新、删除操作,MyBatis会清空一级缓存中的内容以防止程序误读。
3.案例的查询过程
通过一个案例来对MyBatis一级缓存的应用进行详细讲解,该案例要求根据图书id查询图书信息。案例具体步骤如下。
1)在mybatis数据库中创建名为tb_book的数据表,同时预先插入几条测试数据。
USE mybatis;
# 创建一个名称为tb_book的表,并插入数据,这里只展示一条
CREATE TABLE tb_book(
id INT PRIMARY KEY AUTO_INCREMENT,
bookName VARCHAR(255),
price double,
author VARCHAR(40)
);
INSERT INTO tb_book(bookName,price,author) VALUES('Java基础入门',45.0,' 传智播客高教产品研发部');
2)创建持久化类Book,在Book类中定义图书id、图书名称、图书价格、图书作者属性,以及属性对应的getter/setter方法。
public class Book implements Serializable {
private Integer id; // 主键
private String bookName; // 图书名称
private double price; private String author; // 价格、作者
// 省略getter/setter方法
@Override
public String toString() {
return "Book{" + "id=" + id + ", bookName='" + bookName + ", price=" + price + ", author='" + author + '}';
}
}
3) 创建图书映射文件BookMapper.xml,并在该文件中编写根据图书id查询图书信息的SQL语句。
<mapper namespace="com.itheima.mapper.BookMapper">
<!-- 根据id查询图书信息 -->
<select id="findBookById" parameterType="Integer" resultType="com.itheima.pojo.Book">
SELECT * from tb_book where id=#{id}
</select>
<!-- 根据id更新图书信息 -->
<update id="updateBook" parameterType="com.itheima.pojo.Book">
update tb_book
set bookName=#{bookName},price=#{price}
where id=#{id}
</update>
</mapper>
4)在核心配置文件mybatis-config.xml中的<mappers>标签下,引入BookMapper.xml映射文件。
<mapper resource="com/itheima/mapper/BookMapper.xml" />
5)由于需要通过log4j日志组件查看一级缓存的工作状态,因此需要在pom.xml中引入log4j的相关依赖。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
6) 创建log4j.properties文件,用于配置MyBatis和控制台的。
#全局日志配置
log4j.rootLogger=DEBUG, Console
#控制台输出配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
#日志输出级别,只展示了一个
log4j.logger.java.sql.PreparedStatement=DEBUG
7)在测试类MyBatisTest中,编写测试方法findBookByIdTest1()。
public void findBookByIdTest1() {
// 1.通过工具类生成SqlSession对象
SqlSession session1 = MyBatisUtils.getSession();
// 2.使用session1查询id为1的图书的信息
Book book1 = session.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
// 3.输出查询结果信息
System.out.println(book1.toString());
// 再次使用session1查询id为1的图书的信息,同2、3步
// 4.关闭SqlSession
session1.close();
}
注意:MyBatis如何防止程序误读
当程序对数据库执行了插入、更新、删除操作后,MyBatis会清空一级缓存中的内容,以防止程序误读。MyBatis一级缓存被清空之后,再次使用SQL查询语句访问数据库时,MyBatis会重新访问数据库。例如上面的例子,首先查询id为1的图书信息,然后使用更新语句对数据库中的图书信息进行更改,更改之后,再次对id为1的图书信息进行查询时,MyBatis依然会从数据库中查询。
4.5.2 二级缓存
1.使用二级缓存的好处
由4.5.1节的内容可知,相同的Mapper类,相同的SQL语句,如果SqlSession不同,则两个SqlSession查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBatis的二级缓存。MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession可以共用二级缓存,并且二级缓存可以自定义缓存资源。
2.MyBatis二级缓存的执行过程
在MyBatis中,一个Mapper.xml文件通常称为一个Mapper,MyBatis以namespace区分Mapper,如果多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,此后,如果程序没有执行插入、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。
3.MyBatis二级缓存的执行过程图解
4.二级缓存与一级缓存的不同点
与MyBatis的一级缓存不同的是,MyBatis的二级缓存需要手动开启,开启二级缓存通常要完成以下两个步骤。
a.开启二级缓存的全局配置
与使用二级缓存前,需要在MyBatis的核心配置mybatis-config.xml文件中通过<settings>元素开启二级缓存的全局配置。
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
b.开启当前Mapper的namespace下的二级缓存
开启当前Mapper的namespace下的二级缓存,可以通过MyBatis映射文件中的<cache>元素来完成。
<!-- 开启当前Mapper的namespace下的二级缓存-->
<cache>
</cache>
5.默认状态的二级缓存可实现的功能
(1)映射文件中所有select语句将会被缓存。
(2)映射文件中的所有insert、update和delete语句都会刷新缓存。
(3)缓存会使用LRU算法回收。
(4)没有刷新间隔,缓存不会以任何时间顺序来刷新。
(5)缓存会存储列表集合或对象的1024个引用。
(6)缓存是可读/可写的缓存,这意味着对象检索不是共享的,缓存可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
6.<cache>元素的属性
接下来通过一个案例演示MyBatis二级缓存的应用,该案例仍旧根据id查询图书信息,案例具体步骤如下。
1)修改映射文件BookMapper.xml,在映射文件的<mapper>元素下追加编写<cache>元素开启当前Mapper的namespace的二级缓存。
<!— 开启当前BookMapper的namespace下的二级缓存-->
<cache>
</cache>
2)在测试类MyBatisTest中,编写测试方法findBookByIdTest1()。
public void findBookByIdTest3() {
// 1.通过工具类生成两个SqlSession对象,这里只展示了一个
SqlSession session1 = MyBatisUtils.getSession();
// 2.使用session1查询id为1的图书的信息
Book book1 = session1.selectOne("com.itheima.mapper."BookMapper.findBookById", 1);
// 3.输出查询结果信息
System.out.println(book1.toString());
// 4.关闭SqlSession1
session1.close();
}
3)执行MyBatisTest测试类的findBookByIdTest3()方法,控制台会输出结果。
7.对MyBatis二级缓存的应用案例的运行结果分析
控制台输出了执行SQL语句的日志信息以及查询结果。通过分析SQL语句日志信息可以发现,当第一个SqlSession对象session1执行查询时,Cache Hit Ratio(缓存命中率)为0,程序发送了SQL语句;当第二个SqlSession对象session2执行相同的查询时,Cache Hit Ratio为0.5,程序没有发出SQL语句,这就说明,程序直接从二级缓存中获取了数据。
8.多个SqlSession在同一个Mapper中执行
在实际开发中,经常会遇到多个SqlSession在同一个Mapper中执行操作,例如,SqlSession1执行查询操作,SqlSession2执行插入、更新、删除操作,SqlSession3又执行和SqlSession1相同的查询操作。当SqlSession1执行查询操作时,程序会将查询结果写入MyBatis二级缓存,当SqlSession2对数据库执行了插入、更新、删除操作后,MyBatis会清空二级缓存中的内容,以防止程序误读。当SqlSession3执行和SqlSession1相同的查询操作时,MyBatis会重新访问数据库。
9.多学一招:Cache Hit Ratio(缓存命中率)
终端用户访问缓存时,如果在缓存中查找到了要被访问的数据,就叫做命中。如果缓存中没有查找到要被访问的数据,就是没有命中。当多次执行查询操作时,缓存命中次数与总的查询次数(缓存命中次数+缓存没有命中次数)的比,就叫作缓存命中率,即缓存命中率=缓存命中次数/总的查询次数。当MyBatis开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时,缓存命中率为0。第一次查询过后,MyBatis会将查询到的数据写入缓存中,当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5(1/2)。当第三次查询相同的数据,则缓存命中率为0.66666(2/3),以此类推。
4.6 案例:商品的类别
任务:完成商品的类别案例,能够根据表1和表2在数据库分别创建一个商品表和一个商品类别表, 查询商品类别为白色家电的商品的信息
1.商品表(product)商品类别表(category)详情
现有一个商品表product和一个商品类别表category,商品类别表category和商品表product是一对多的关系。
商品编号id | 商品名称 goodsname | 商品单价price | 商品类别typeid |
1 | 电视机 | 5000 | 1 |
2 | 冰箱 | 4000 | 2 |
3 | 空调 | 3000 | 2 |
4 | 洗衣机 | 2000 | 2 |
商品类别编号 id | 商品类别名称 typename |
1 | 黑色家电 |
2 | 白色家电 |
2.案例具体要求
本案例具体要求:根据表1和表2在数据库分别创建一个商品表product和一个商品类别表category, 并通过MyBatis查询商品类别为白色家电的商品的所有信息。
1)项目搭建:创建一个名称为mybatis-demo04的项目,项目的具体搭建过程请参考1.3节。
2)数据库准备:在名为mybatis的数据库中,创建两个数据表,分别为product和category,同时在表中预先插入几条测试数据。
USE mybatis;
# 创建一个名称为category的表
CREATE TABLE category (
id int(32) PRIMARY KEY AUTO_INCREMENT,
typename varchar(40)
);
# 插入2条数据
INSERT INTO category VALUES (1, '黑色家电');
INSERT INTO category VALUES (2, '白色家电');
# 创建一个名称为product的表,同理
3)POJO类准备:
(1)创建持久化类Category,并在类中定义商品类别的相关属性和方法。
public class Category {
private Integer id; // 主键id
private String typename; // 类别名称
private List<Product> productList; // 商品集合
// 省略getter/etter方法
@Override
public String toString() {
return "Category{" +"id=" + id + ", typename='" + typename +", productList=" + productList + '}';
}
}
(2)创建持久化类Product,并在类中定义相关属性和方法。
public class Product {
private Integer id; // 主键id
private String goodsname; // 商品名称
private double price; // 价格
// 省略getter/setter方法
@Override
public String toString() {
return "Product{" +"id=" + id + ", goodsname=" + goodsname + ", price=" + price + "}";
}
}
4)编写映射文件:创建商品类别实体映射文件CategoryMapper.xml,并在文件中编写一对多关联映射查询的配置。
<!-- 只展示了select标签的内容 -- >
<!-- 一对多:查看某一商品类别及其关联的商品信息
注意:当关联查询出的列名相同,则需要使用别名区分 -->
<select id="findCategoryWithProduct" parameterType="Integer" resultMap="CategoryWithProductResult">
SELECT c.*,p.id as category_id,p.goodsname,p.price
from category c,product p
WHERE c.id=p.category_id and c.id=#{id}
</select>
5)修改mybatis-config.xml核心配置文件:在核心配置文件mybatis-config.xml中,引入CategoryMapper.xml,将CategoryMapper.xml映射文件加载到程序中。
<mapper resource="com/itheima/mapper/CategoryMapper.xml" ></mapper>
6)编写测试方法:在测试类MyBatisTest中,编写测试方法findCategoryTest()。
public void findCategoryTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
// 2.查询id为2的商品类别信息
Category category = session.selectOne("com.itheima.mapper.CategoryMapper.findCategoryWithProduct", 2);
// 3.输出查询结果信息
System.out.println(category);
// 4.关闭SqlSession
session.close();
}
7)查看运行结果:执行MyBatisTest测试类的findCategoryTest()方法,控制台会输出结果。
5.MyBatis的注解开发
前言
前面介绍了MyBatis的基本用法、关联映射、动态SQL和缓存机制等知识,所有的配置都是基于XML文件完成的,但在实际开发中,大量的XML配置文件的编写是非常繁琐的,为此,MyBatis提供了更加简便的基于注解的配置方式。本章将对MyBatis的注解开发进行详细讲解。
5.1 基于注解的单表增删改查
5.1.1 @Select注解
下面通过一个案例演示@Select注解的使用,该案例要求根据员工的id查找员工信息,案例具体 实现步骤如下。
1)建表:在mybatis数据库中创建名为tb_worker的数据表,同时预先插入几条测试数据。
CREATE TABLE tb_worker(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32),
age INT,
sex VARCHAR(8),
worker_id INT UNIQUE
);
INSERT INTO tb_worker(name,age,sex,worker_id)VALUES('张三',32,'女',1001);
...
2)创建类:创建持久化类Worker,在Worker类中定义id、员工姓名、年龄、性别、工号等属性以及属性对应的getter/setter方法。
public class Worker {
private Integer id;
private String name;
private Integer age;
private String sex;
private String worker_id;
// 省略getter/setter方法
@Override
public String toString() {
return "Worker{" + "id=" + id + ", name=" + name + ", age=" + age + ", sex=" + sex + ", worker_id=" + worker_id + '}';
}
}
3)编写查询方法:创建WorkerMapper接口,用于编写@Select注解映射的select查询方法。
package com.itheima.dao;
import com.itheima.pojo.Worker;
import org.apache.ibatis.annotations.Select;
public interface WorkerMapper {
@Select("select * from tb_worker where id = #{id}")
Worker selectWorker(int id);
}
4)加载配置文件:在核心配置文件mybatis-config.xml中的<mappers>元素下引入WorkerMapper接口,将WorkerMapper.java接口加载到核心配置文件中。
<mapper class="com.itheima.dao.WorkerMapper"/>
5)编写测试方法:在测试类MyBatisTest中,编写测试方法findWorkerByIdTest()。
public void findWorkerByIdTest() {
// 1.获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2.查询id为1的员工信息
Worker worker = mapper.selectWorker(1);
System.out.println(worker.toString());
// 3.关闭SqlSession
session.close();
}
5.1.2 @Insert注解
下面通过一个案例演示@Insert注解的使用,要求实现员工信息的插入,案例具体实现步骤如下。
1)添加注解:在WorkerMapper接口中添加向tb_worker数据表插入数据的方法insertWorker(),并在方法上添加@Insert注解。
@Insert("insert into tb_worker(name,age,sex,worker_id) values(#{name},#{age},#{sex},#{worker_id})")
int insertWorker(Worker worker);
2)编写测试类:在测试类MyBatisTest中,编写测试方法insertWorkerTest()。
public void insertWorkerTest() {
// 1.生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
Worker worker = new Worker();
worker.setId(4);
worker.setName("赵六");
worker.setAge(36);
worker.setSex("女");
worker.setWorker_id("1004");
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2.插入员工信息
int result = mapper.insertWorker(worker);
// 输出语句省略...
session.commit();
// 3.关闭SqlSession
session.close();
}
5.1.3 @Update注解
下面通过一个案例演示@Update注解的使用,该案例要求实现员工信息的修改,案例具体实现步骤如下。
1)添加注解:在WorkerMapper接口中添加更新tb_worker表中数据的方法,并在方法上添加@Update注解。
@Update("update tb_worker set name = #{name},age = #{age} " +"where id = #{id}")
int updateWorker(Worker worker);
2)编写测试类:测试类MyBatisTest中,编写测试方法updateWorkerTest()。
public void updateWorkerTest() {
// 1.生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
Worker worker = new Worker();
worker.setId(4);
worker.setName("李华");
worker.setAge(28);
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2.更新员工信息
int result = mapper.updateWorker(worker);
// 输出语句省略...
session.commit();
// 3.关闭SqlSession
session.close();
}
5.1.4 @Delete注解
下面通过一个案例演示@Delete注解的使用,该案例要求实现员工信息的删除,案例具体实现步骤如下。
1)添加注解:在WorkerMapper接口中添加删除数据库中数据的方法,并在方法上添加@Delete注解。
@Delete("delete from tb_worker where id = #{id}")
int deleteWorker(int id);
2)编写测试类:在测试类MyBatisTest中,编写测试方法deleteWorkerTest()。
public void deleteWorkerTest() {
SqlSession session = MyBatisUtils.getSession(); // 1.生成SqlSession对象
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2.删除员工信息
int result = mapper.deleteWorker(4);
if(result>0){
System.out.println("成功删除"+result+"条数据");
}else {
System.out.println("删除数据失败");
}
session.commit();
// 3.关闭SqlSession
session.close();
}
5.1.5 @Param注解
下面通过一个案例演示@Param注解的使用,该案例要求根据员工的id和姓名查询员工信息, 案例具体实现步骤如下。
1)添加注解:在WorkerMapper接口中添加多条件查询的方法。
@Select("select * from tb_worker where id = #{param01} and name = #{param02}")
Worker selectWorkerByIdAndName(@Param("param01") int id, @Param("param02") String name);
2)编写测试类:在测试类MyBatisTest中,编写测试方法selectWorkerByIdAndNameTest()。
public void selectWorkerByIdAndNameTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2.查询id为3姓名为王五的员工的信息
Worker worker = mapper.selectWorkerByIdAndName(3,"王五");
System.out.println(worker.toString());
session.close();
}
5.2 基于注解的关联查询
5.2.1 一对一查询
接下来,以4.2节中使用的tb_idcard和tb_person数据表为例,详细讲解基于注解@One实现 tb_idcard和tb_person数据表之间的一对一关联查询,具体步骤如下。
1)创建持久化类:本案例使用4.2节中的IdCard类和Person类作为持久类。
2)编写接口方法:
(1)创建IdCardMapper接口,在该接口中编写selectIdCardById()方法,通过id查询人员对应的身份证信息。
package com.itheima.dao;
import com.itheima.pojo.IdCard;
import org.apache.ibatis.annotations.Select;
public interface IdCardMapper {
@Select("select * from tb_idcard where id=#{id}")
IdCard selectIdCardById(int id);
}
(2)在项目的com.itheima.dao包下创建PersonMapper接口,在该接口中编写selectPersonById(),通过id查询人员信息。
package com.itheima.dao;
public interface PersonMapper {
@Select("select * from tb_person where id=#{id}")
@Results({@Result(column = "card_id",property = "card",one = @One(select = "com.itheima.dao.IdCardMapper.selectIdCardById"))})
Person selectPersonById(int id);
}
@Result注解的三个属性及含义
(1)property属性用来指定关联属性,这里为card。
(2)column属性用来指定关联的数据库表中的字段,这里为card_id。
(3)one属性用来指定数据表之间属于哪种关联关系,通过@One注解表明数据表tb_idcard和tb_person之间是一对一关联关系。
3)引入接口:在核心配置文件mybatis-config.xml中的<mappers>元素下引入IdCardMapper和PersonMapper接口。
<mapper class="com.itheima.dao.IdCardMapper"/>
<mapper class="com.itheima.dao.PersonMapper"/>
<mappers>元素引入XML文件顺序
由于mybatis-config.xml文件中的扫描方式是从上往下扫描,所以<mappers>元素下引入 IdCardMapper和PersonMapper接口的位置,必须在引入IdCardMapper.xml和PersonMapper.xml文件位置前面,否则程序将会首先读取到引入的IdCardMapper.xml和PersonMapper.xml文件,程序将会报错。
4)编写测试类:在测试类MyBatisTest中,编写测试方法selectPersonByIdTest()。
public void selectPersonByIdTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
PersonMapper mapper = session.getMapper(PersonMapper.class);
// 2.查询id为1的人员的信息
Person person = mapper.selectPersonById(2);
System.out.println(person.toString());
// 3.关闭SqlSession
session.close();
}
5.2.2 一对多查询
接下来,以4.3节中的tb_user和tb_orders数据表为例,详细讲解基于@Many注解配置实现 tb_user和tb_orders数据表之间的一对多关联查询,具体步骤如下。
1)创建持久化类:本案例使用4.3节中的Users类和Orders类作为持久类。
2)编写接口方法:
(1)创建OrdersMapper接口,在该接口中编写selectOrdersByUserId()方法,通过user_id查询用户对应的订单信息。
public interface OrdersMapper {
@Select("select * from tb_orders where user_id=#{id} ")
@Results({@Result(id = true,column = "id",property = "id"),@Result(column = "number",property = "number") })
List<Orders> selectOrdersByUserId(int user_id);
}
(2)创建UsersMapper接口,在该接口中编写selectUserById()方法,通过id查询用户信息。
public interface UsersMapper {
@Select("select * from tb_user where id=#{id} ")
@Results({@Result(id = true,column = "id",property = "id"),
@Result(column = "username",property = "username"),
@Result(column = "address",property = "address"),
@Result(column = "id",property = "ordersList",
many = @Many(select = "com.itheima.dao.OrdersMapper.selectOrdersByUserId"))})
Users selectUserById(int id);
}
3)引入接口:在核心配置文件mybatis-config.xml中的<mappers>元素下引入UsersMapper和OrdersMapper接口。
<mapper class="com.itheima.dao.UsersMapper"/>
<mapper class="com.itheima.dao.OrdersMapper"/>
<mappers>元素引入XML文件顺序
由于mybatis-config.xml文件中的扫描方式是从上往下扫描,所以<mappers>元素下引入UsersMapper和OrdersMapper接口的位置,必须在引入UsersMapper.xml和OrdersMapper.xml文件位置前面,否则程序将会首先读取到引入的UsersMapper.xml和OrdersMapper.xml文件,程序将会报错。
4)编写测试类:在测试类MyBatisTest中,编写测试方法selectUserByIdTest()。
public void selectUserByIdTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
UsersMapper mapper = session.getMapper(UsersMapper.class);
// 2.查询id为1的人的信息
Users users = mapper.selectUserById(1);
System.out.println(users.toString());
session.close();
}
5.2.3 多对多查询
1.多对多关联使用中间表
在数据库中,表与表之间的多对多关联关系通常使用一个中间表来维护,以4.4节中使用的订单表tb_orders和商品表tb_product为例,这两个表之间的关联关系使用了一个中间表tb_ordersitem来维护,订单表tb_orders和商品表tb_product,都与中间表tb_ordersitem形成了一对多关联关系,即中间表tb_ordersitem将订单表tb_orders和商品表tb_product拆分成了两个一对多的关联关系。
接下来,以4.4节中使用的订单表tb_orders、商品表tb_product和中间表tb_ordersitem为例, 详细讲解多对多关联查询,具体步骤如下。
1)在订单持久化类(Orders.java)中增加商品集合的属性及其对应的getter/setter方法,并修改Orders类和Product类中的toString()方法。
@Override
public String toString() {
return "Orders{" + "id=" + id + ", number=" + number + ", productList=" + productList + '}’;
}
@Override
public String toString() {
return "Product{" + "id=" + id + ", name=" + name +", price=" + price + '}';
}
2) (1)创建ProductMapper接口,在该接口中编写selectProductByOrdersId()方法,通过user_id查询用户对应的订单信息。
public interface ProductMapper {
@Select("select * from tb_product where id in (select product_id from tb_ordersitem where orders_id = #{id} )")
List<Product> selectProductByOrdersId(int orders_id);
}
(2)在OrdersMapper接口中添加selectOrdersById()方法,该方法用于通过id查询订单信息。
@Select("select * from tb_orders where id=#{id} ")
@Results({@Result(id = true,column = "id",property = "id"),
@Result(column = "number",property = "number"),
@Result(column = "id",property = "productList",many = @Many(select = "com.itheima.dao.ProductMapper.selectProductByOrdersId"))})
Orders selectOrdersById(int id);
3) 在核心配置文件mybatis-config.xml中的<mappers>元素下引入ProductMapper和OrdersMapper接口,将这两个接口加载到核心配置文件中。
<mapper class="com.itheima.dao.ProductMapper"/>
<mapper class="com.itheima.dao.OrdersMapper "/>
<mappers>元素引入XML文件的顺序
注意:由于mybatis-config.xml文件中的扫描方式是从上往下扫描,所以<mappers>元素下引入ProductMapper和OrdersMapper接口的位置,必须在引入ProductMapper.xml和OrdersMapper.xml文件位置前面,否则程序将会首先读取到引入的ProductMapper.xml和OrdersMapper.xml文件,程序将会报错。
4) 在测试类MyBatisTest中,编写测试方法selectOrdersByIdTest(),查询id为3的订单的信息。
public void selectOrdersByIdTest() {
// 1.通过工具类生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
OrdersMapper mapper = session.getMapper(OrdersMapper.class);
// 2.查询id为3的订单的信息
Orders orders = mapper.selectOrdersById(3);
System.out.println(orders.toString());
session.close();
}
5.3 案例:基于MyBatis注解的学生管理程序
任务: 完成基于MyBatis注解的学生管理程序,能够用MyBatis注解实现查询操作、实现修改操作、实现一对多查询
1.学生表(s_student)与班级表(c_class)详情
现有一个学生表s_student和一个班级表c_class,其中,班级表c_class和学生表s_student是一对多的关系。
学生id | 学生姓名 name | 学生年龄age | 所属班级cid |
1 | 张三 | 18 | 1 |
2 | 李四 | 18 | 2 |
3 | 王五 | 19 | 2 |
4 | 赵六 | 20 | 1 |
班级id | 班级名称 classname |
1 | 一班 |
2 | 二班 |
2.使用MyBatis注解实现下列要求
(1)MyBatis注解实现查询操作。根据表1和表2在数据库分别创建一个学生表s_student和一个班级表c_class, 并查询id为2的学生的信息。
(2)MyBatis注解实现修改操作。修改id为4的学生的姓名修改为李雷,年龄修改为21。 (3)MyBatis注解实现一对多查询。查询出二班所有学生的信息。
3.MyBatis注解实现查询操作
1)项目搭建:创建一个名称为mybatis-demo05的项目,项目的具体搭建过程请参考1.3节。
2)数据库准备:在名为mybatis的数据库中,创建两个数据表,分别为学生表s_student和班级表c_class,同时在表中预先插入几条测试数据。
USE mybatis;
# 创建一个名称为c_class的表
CREATE TABLE c_class (
id int(32) PRIMARY KEY AUTO_INCREMENT,
classname varchar(40)
);
INSERT INTO c_class VALUES (1, '一班');
INSERT INTO c_class VALUES (2, '二班’);
# 创建s_student表同理
3)POJO类准备:创建持久化类IClass,并在类中定义相关属性和方法,该类用于封装IClass对象的id、班级名称以及关联的学生集合等属性。
public class IClass {
private Integer id;
private String classname; // 主键id,班级名称
private List<IStudent> studentList; // 学生集合
// 省略getter/setter方法
@Override
public String toString() {return "IClass{" +"id=" + id + ", classname='" + classname +", studentList=" + studentList + '}’;
}}
4)创建Mapper接口:创建IStudentMapper接口,用于编写@Select注解映射的select查询语句。
5)修改mybatis-config.xml核心配置文件:在核心配置文件mybatis-config.xml中的<mappers>元素下引入IStudentMapper接口,将IStudentMapper接口加载到核心配置文件中。
<mapper class="com.itheima.dao.IStudentMapper"/>
6)编写MyBatisUtils工具类:创建MyBatisUtils工具类,该类用于封装读取配置文件信息的代码。
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}
7)编写测试方法:创建测试类MyBatisTest,在测试类MyBatisTest中编写测试方法findIStudentByIdTest()。
public void findIStudentByIdTest() {
// 1.通过工具类获取SqlSession对象
SqlSession session = MyBatisUtils.getSession();
IStudentMapper mapper = session.getMapper(IStudentMapper.class);
// 2.使用IStudentMapper对象查询id为1的学生的信息
IStudent student = mapper.selectStudent(2);
System.out.println(student.toString());
// 3.关闭SqlSession
session.close();
}
4. MyBatis注解实现修改操作
1)修改Mapper接口:在IStudentMapper接口中添加更新s_student表中数据的方法,并在方法上添加@Update注解。
@Update("update s_student set name = #{name},age = #{age} where id = #{id}")
int updateStudent(IStudent student);
2)编写测试方法:在测试类MyBatisTest中,编写测试方法updateIStudentTest()。
public void updateIStudentTest() {
SqlSession session = MyBatisUtils.getSession();
IStudent student = new IStudent();
student.setId(4);
student.setName("李雷");
student.setAge(21);
IStudentMapper mapper = session.getMapper(IStudentMapper.class);
int result = mapper.updateStudent(student); // 更新学生信息
if(result>0){
System.out.println("成功更新"+result+"条数据");
} else {
System.out.println("更新数据失败");、
}
System.out.println(student.toString());
session.commit();
// 关闭SqlSession}
session.close();
3.MyBatis注解实现一对多操作
1)修改Mapper接口:
(1)在IStudentMapper接口中编写selectStudentByCid()方法,通过cid查询对应班级中的学生信息。
@Select("select * from s_student where cid=#{id} ")
@Results({@Result(id = true,column = "id",property = "id"),
@Result(column = "classname",property = "classname")
})
List<IStudent> selectStudentByCid(int cid);
(2)创建IClassMapper接口,在该接口中编写selectClassById ()方法,通过id查询班级信息。
public interface IClassMapper {
@Select("select * from c_class where id=#{id} ")
@Results({@Result(id = true,column = "id",property = "id"),
@Result(column = "classname",property = "classname"),
@Result(column = "id",property = "studentList",
many = @Many(select"com.itheima.dao.IStudentMapper.selectStudentByCid"))})
IClass selectClassById(int id); }
2)修改mybatis-config.xml核心配置文件:在核心配置文件mybatis-config.xml中的<mappers>元素下引入IClassMapper接口,将IClassMapper接口加载到核心配置文件中。
<mapper class="com.itheima.dao.IClassMapper"/>
3)编写测试方法:在测试类MyBatisTest中,编写测试方法selectClassByIdTest()。
public void selectClassByIdTest() {
// 1.生成SqlSession对象
SqlSession session = MyBatisUtils.getSession();
IClassMapper mapper = session.getMapper(IClassMapper.class);
// 2.查询id为2的班级中学生的信息
IClass icalss = mapper.selectClassById(2);
System.out.println(icalss.toString());
session.close();
}