昨天我们深入学习了Mybatis的动态SQL,掌握MyBatis的动态SQL所有元素的使用,也掌握了更加复杂的查询操作。
元素:相当于 Java 中的单条件判断语句。它允许您在 SQL 查询语句中嵌入条件判断,只有当条件满足时,相应的 SQL 语句片段才会包含在最终的查询中。这使我们能够根据传入的参数动态构建 SQL 查询语句。
,
,
元素:这是 MyBatis 中实现类似于 Java 中的 switch
语句的机制。
是选择块的开头,其中可以包含多个
子元素和一个
子元素。MyBatis 会检查
子元素中的条件,如果某个条件满足,则执行对应的 SQL 语句块。如果没有
子元素的条件满足,则会执行
中的 SQL 语句块(类似于 Java 中的 default
语句)。
元素:用于动态生成 UPDATE 语句中的 SET 部分,允许我们根据传入的参数动态构建需要更新的字段。这对于仅更新部分字段而不影响其他字段的情况非常有用。
元素:用于在 SQL 查询语句中进行循环操作,特别适用于在 IN
子句中传递一组值。可以循环遍历集合或数组,并将集合或数组中的元素按照一定格式插入到 SQL 查询语句中。
元素是 MyBatis 中用于处理字符串拼接和修整的动态 SQL 元素。它通常用于构建动态的 SQL 片段,可以去除开头或结尾的特定字符,以及在特定位置添加分隔符。这在构建复杂的 SQL 查询语句时非常有用。
元素有以下属性:
prefix
:在 SQL 片段前添加的内容。prefixOverrides
:要删除的前缀内容。suffix
:在 SQL 片段后添加的内容。suffixOverrides
:要删除的后缀内容。 关联映射(Association Mapping)是指在关系型数据库中存在关联关系的不同表之间,通过对象关系映射(ORM)工具,将这些关联关系映射为编程语言中的对象关联关系,从而方便在程序中处理复杂的数据结构和关联查询。
在关系数据库中,关联通常通过外键建立。ORM工具(如MyBatis)通过关联映射,可以将这些关系以对象的形式表示出来,让程序员可以更自然地操作这些关联关系,而不需要关心底层数据库的细节。
在关系型数据库中,表与表之间存在着三种关联映射关系,分别为:
POJO类就是普通Java对象,那么一个表就是一个Java对象,数据表之间的关系实质上就是数据之间的关系。通过Java对象描述数据之间的关系,其实就是使对象的属性与另一个对象的属性相互关联。如图:
我们先讲Mybatis对象关联映射中的一对一、一对多吧,这俩个比较常见,也比较重要。
元素属性 在MyBatis中,通过
元素来处理一对一关联关系。
元素提供了一系列属性用于维护数据表之间的关系。
属性 | 说明 |
---|---|
property | 用于指定映射到的实体类对象的属性,与表字段一一对应 |
column | 用于指定表中对应的字段 |
javaType | 用于指定映射到实体对象的属性的类型 |
jdbcType | 用于指定数据表中对应字段的类型 |
fetchType | 用于指定在关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy |
select | 用于指定引入嵌套查询的子SQL语句 |
autoMapping | 用于指定是否自动映射 |
typeHandler | 用于指定一个类型处理器 |
元素是
元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式,下面对这两种配置方式分别进行介绍。
是指通过执行另外一条SQL映射语句来返回预期的复杂类型。
是使用嵌套结果映射来处理重复的联合结果自己。
在一对一关联映射中,一个主实体类关联一个从实体类,这种关系通常通过外键来实现。例如,一个学生和一个身份证之间的关系,一个人和一个地址之间的关系等。
1.先创建数据表:idcard的身份证数据表和名为person的个人数据表,同时先插入几条数据作为测试使用。
idcard表的创建SQL语句:
#idcard表
CREATE TABLE `idcard` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`CODE` varchar(18) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
#插入数据
insert into `idcard`(`id`,`CODE`) values (1,'445222222222221148'),(2,'152201199008150317');
person表的创建SQL语句:
#person表
CREATE TABLE `person` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` varchar(8) DEFAULT NULL,
`card_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `card_id` (`card_id`),
CONSTRAINT `person_ibfk_1` FOREIGN KEY (`card_id`) REFERENCES `idcard` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
#插入数据
insert into `person`(`id`,`name`,`age`,`sex`,`card_id`) values (1,'stevedash',22,'女',1),(2,'Steve',23,'男',2);
2.创建俩个表的pojo类,使用IDEA链接数据库自动生成POJO类(前面发过文章了,可以去查看了解一下),这样能快速编写,帮助我们节省大量时间。
生成后记得自己检验一下,是否生成正确,确保无误。
可以看到如果我们想用一对一关联映射查询,那么就要有主表和从表,可以看到IdCard表和Person表的关联字段在于id与cardId
就是外键嘛,所以我们对POJO类要进行修改,记得还要重新生成getter和setter方法
3.编写IdCardMapper.xml和PersonMapper.xml文件(A.嵌套查询方式):
IdCardMapper.xml映射配置:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.IdCardMapper">
<select id="selectCodeById" parameterType="Integer" resultType="IdCard">
select * from idcard where id=#{id}
select>
mapper>
PersonMapper.xml映射配置:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.PersonMapper">
<select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult">
select * from person where id=#{id}
select>
<resultMap type="Person" id="IdCardWithPersonResult">
<id property="id" column="id"/>
<association property="cardId" column="id" javaType="IdCard" select="mapper.IdCardMapper.selectCodeById"/>
resultMap>
mapper>
代码说明:
resultMap是Mybatis最强大的元素,它可以将查询到的复杂数据(比如查询到几个表中数据)映射到一个结果集当中。
<resultMap id
=“唯一的标识” type=“映射的pojo对象('‘主表’'对应的pojo类)”>
<id column
=“表的主键字段,或者可以为查询语句中的别名字段” property=“映射pojo对象的主键属性” />
<association property
=“pojo的一个对象属性” javaType=“pojo关联的pojo对象(从表对应的pojo类)”>
这个配置的意思是,将当前实体类中的 cardId
属性与数据库从表中的 id
列进行关联,当需要获取关联对象 IdCard
的数据时,会调用 IdCardMapper
中的 selectCodeById
方法。这样,MyBatis 会根据关联关系自动去调用指定的映射语句,从而查询并映射出一对一关联的对象数据。
**PersonMapper.xml:**新增下面语句
<select id="findPersonByIdWithResult" parameterType="Integer" resultMap="IdCardWithPersonResult2">
select * from person p, idcard i where p.card_Id=i.id
and p.id=#{id}
select>
<resultMap type="Person" id="IdCardWithPersonResult2">
<association property="cardId" javaType="IdCard"/>
resultMap>
4.在mybatis-config.xml中引入映射文件:IdCardMapper.xml和PersonMapper.xml
<mappers>
<mapper resource="mapper/PasswordMSMapper.xml"/>
<mapper resource="mapper/IdCardMapper.xml"/>
<mapper resource="mapper/PersonMapper.xml"/>
mappers>
5.编写出测试类进行测试:
class PersonTest {
private Logger logger= Logger.getLogger(PersonTest.class);
//嵌套查询方式
@Test
void findCodeById() {
SqlSession session= MyBatisUtil.createSqlSession();
Person person=session.selectOne("findPersonById",1);
logger.info(person.toString());//记得在pojo实体类中重写toString(),不然只会输出一串地址
//关闭session
session.close();
}
//嵌套结果方式
@Test
void findCodeById2() {
SqlSession session= MyBatisUtil.createSqlSession();
List<Person> list=session.selectList("findPersonByIdWithResult",1);
for(Person p:list){
logger.info(p.toString());//记得在pojo实体类中重写toString(),不然只会输出一串地址
}
//关闭session
session.close();
}
}
在一对多关联映射中,一个主实体类关联多个从实体类,这种关系通常通过主表的主键和从表的外键来实现。例如,一个人和多个订单之间的关系,一个课程和多个学生之间的关系等。
use database passwordms;
CREATE TABLE `order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`productname` varchar(30) DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`number` int(11) DEFAULT NULL,
`userid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
insert into `order`(`id`,`productname`,`price`,`number`,`userid`) values (1,'橄榄油','20',20,1),(2,'洗洁精','15',10,1),(3,'塑料杯','2',100,1),(4,'泰国香米','60',2,2),(5,'可口可乐','3',10,2),(6,'美国大杏仁','10',5,2),(7,'葡萄酒','6',10,3),(8,'豆瓣酱','4',2,3);
CREATE TABLE `users` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`uname` varchar(20) NOT NULL,
`uage` int(11) NOT NULL,
PRIMARY KEY (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
insert into `users`(`uid`,`uname`,`uage`) values (1,'steve',20),(2,'王五',18),(3,'王国',22),(4,'Stevedash',23),(5,'Kdash',22);
生成俩个表pojo类:
定义List的字段限制为
对象,用于记录多条订单记录
UserMapper.xml映射配置:
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.UserMapper">
<select id="findUserWithOrder" resultMap="UserWithOrder" parameterType="int">
select * from users u join tb_order o on u.uid=o.userid where uid=#{uid}
select>
<resultMap id="UserWithOrder" type="Users">
<id property="uid" column="uid"/>
<collection property="orderList" ofType="TbOrder">
collection>
resultMap>
mapper>
编写测试类:
package Test;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.jupiter.api.Test;
import pojo.TbOrder;
import pojo.Users;
import utils.MyBatisUtil;
class UsersTest {
//创建一个当前类的日志对象
private Logger logger=Logger.getLogger(UsersTest.class);
@Test
public void getOrdersList() {
//创建SqlSession实例
SqlSession session = MyBatisUtil.createSqlSession();
//传入参数查询,返回结果
Users users =session.selectOne("findUserWithOrder",2);
//输出结果
if(users!=null){
if(users.getOrderList().size() > 0){
for(TbOrder orders: users.getOrderList()){
logger.debug("姓名 " +users.getUname()+ ", 年龄: " +users.getUage()
+ ", 产品名称: " + orders.getProductname() + ", 价格: " + orders.getPrice()
+ ", 数量: " + orders.getNumber());
}
}else{
logger.debug("该用户下无订单!");
}
}else{
logger.debug("查无此用户!");
}
//关闭session
session.close();
}
}
association
标签用于一对一关联映射,而 collection
标签用于一对多关联映射。 在MyBatis的关联映射中,嵌套条件查询和嵌套结果方式是两种常见的处理多表关联查询的方法。它们各有优缺点,适用于不同的场景。上面已经将这两种方式进行详细的对比,下面小结一下。
这种方式在需要使用关联对象时,动态发起一次查询来获取关联对象的信息。适用于关联数据量较大,查询效率要求较高的情况。
这种方式通过在结果映射中定义关联对象,将关联对象的数据嵌套在主对象的属性中。适用于关联数据量不大,且查询效率要求不高的情况。
在掌握上述的嵌套查询方式和嵌套结果方式的情况下,可以尝试看看,加入以下设置能不能更加快速的反应。
<settings>
<setting name="autoMappingBehavior" value="FULL" />
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
settings>
MyBatis提供了两级缓存(一级缓存和二级缓存)来提升数据库访问性能,减少重复查询的开销。下面我将解释MyBatis的缓存机制、并且提供一个简单的示例,并解释其工作过程。
MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时,在第一次执行完成后,MyBatis会将查询结果写入到一级缓存中,此后,如果程序没有执行插入、更新、删除操作,当第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不用再去数据库查询,从而提高了数据库的查询效率。
在MyBatis中,一个Mapper.xml文件通常称为一个Mapper,MyBatis以namespace区分Mapper,如果多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,此后,如果程序没有执行插入、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。
其实,当程序对数据库执行了插入、更新、删除操作后,MyBatis会清空一级缓存中的内容,以防止程序误读。MyBatis一级缓存被清空之后,再次使用SQL查询语句访问数据库时,MyBatis会重新访问数据库。
当调用查询方法时,MyBatis会首先查看一级缓存,如果缓存中存在结果,直接返回结果。
如果一级缓存中没有,MyBatis会查看二级缓存。如果启用了二级缓存且缓存中存在相应数据,则返回结果。
如果二级缓存也没有,MyBatis会去数据库中查询数据,并将查询结果放入一级缓存和(如果启用)二级缓存。
如果开启了二级缓存,当其他SqlSession也需要查询相同的数据时,可以直接从二级缓存中获取,而不必再次查询数据库。
#一级缓存测试
# 创建一个名称为book的表,并插入数据,三条测试数据
CREATE TABLE book(
id INT PRIMARY KEY AUTO_INCREMENT,
bookName VARCHAR(255),
price double,
author VARCHAR(40) );
INSERT INTO book(bookName,price,author)
VALUES('Java基础',15.0,'Stevedash出品'),('Java高级编程',25.0,'Kdash出品'),('JavaEE企业开发',30.0,'Steve联合出品');
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.BookMapper">
<select id="findBookById" parameterType="Integer"
resultType="Book">
SELECT * from book where id=#{id} select>
<update id="updateBook"
parameterType="Book">
update book set bookName=#{bookName},price=#{price}
where id=#{id} update>
mapper>
我们进行俩次相同的查询:区分出一级缓存和链接数据库查询
那么我们继续尝试,在俩次相同的查询中间,新增一个更新的操作
这样子就能清楚的认知到,一级缓存的功能了。
二级缓存一般是默认关闭的,需要手动设置打开。在MyBatis配置文件中,可以配置
元素来启用和配置二级缓存。
<settings>
<setting name="cacheEnabled" value="true"/>
settings>
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.BookMapper">
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
<select id="findBookById" parameterType="Integer"
resultType="Book">
SELECT * from book where id=#{id} select>
<update id="updateBook"
parameterType="Book">
update book set bookName=#{bookName},price=#{price}
where id=#{id} update>
mapper>
记得添加进去,在mapper标签对里面。
@Test
void SecondTest() {
//理解二级缓存
//创建SqlSession
SqlSession session = MyBatisUtil.createSqlSession();
SqlSession session1=MyBatisUtil.createSqlSession();
// 第一次查询,会从数据库获取数据并放入一级缓存
Book book1 = session.selectOne("findBookById", 1);
logger.info("User 1: " + book1);
session.close();
Book book2 = session1.selectOne("findBookById", 1);
logger.info("User 2: " + book2);
session1.close();
}
使用缓存时需要考虑缓存的刷新策略和缓存失效机制,以确保数据的一致性和准确性。
终端用户访问缓存时,如果在缓存中查找到了要被访问的数据,就叫做命中。如果缓存中没有查找到要被访问的数据,就是没有命中。当多次执行查询操作时,缓存命中次数与总的查询次数**(缓存命中次数+缓存没有命中次数)的比,就叫作缓存命中率**,即缓存命中率=缓存命中次数/总的查询次数。当MyBatis开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时,缓存命中率为0。第一次查询过后,MyBatis会将查询到的数据写入缓存中,当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5(1/2)。当第三次查询相同的数据,则缓存命中率为0.66666(2/3),以此类推。
(1)映射文件中所有select语句将会被缓存。
(2)映射文件中的所有insert、update和delete语句都会刷新缓存。
(3)缓存会使用LRU算法回收。
(4)没有刷新间隔,缓存不会以任何时间顺序来刷新。
(5)缓存会存储列表集合或对象的1024个引用。
(6)缓存是可读/可写的缓存,这意味着对象检索不是共享的,缓存可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
总之,MyBatis的缓存机制可以大大提高数据库访问性能,但在使用时需要根据业务需求和数据特点来合理配置和管理缓存。
MyBatis缓存机制:
一级缓存(本地缓存): 也被称为会话级缓存。在同一个SqlSession中,查询结果会被缓存在这一级缓存中。默认情况下,一级缓存是开启的,且无法关闭。当SqlSession执行commit、close、clearCache等操作时,一级缓存会被清空。
二级缓存(全局缓存): 二级缓存是跨SqlSession的缓存,可供多个SqlSession共享。默认情况下,二级缓存是关闭的,需要手动配置启用。二级缓存的生命周期更长,可以在不同SqlSession间共享查询结果。
这是第四天对SSM框架的学习,今天深化了解了Mybatis的关联映射关系,一对一,一对多等。还掌握学习了Mybatis的缓存机制,一二级缓存机制的不同,如何开启,能够做到什么?想要跟着学习的可以去我的资源里面找对应的文件下载,我的md文件也会发上去,项目文件会上传可以自己跟着学习一下。
作者:Stevedash
发表于:2023年8月25日 21点40分