MyBatis框架学习笔记(四):动态SQL语句、映射关系和缓存

动态 SQL 语句-更复杂的查询业务需求

1.1 动态 SQL-官方文档

(1)文档地址: mybatis – MyBatis 3 | 动态 SQL

(2)为什么需要动态 SQL

  • 动态 SQL MyBatis 的强大特性之一
  • 使用 JDBC 或其它类似的框架,根据不同条件拼接 SQL 语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等
  • SQL 映射语句中的强大的动态 SQL 语言, 可以很好的解决这个问题

1.2 动态 SQL-基本介绍 

基本介绍
  • 在一个实际的项目中,sql 语句往往是比较复杂的
  • 为了满足更加复杂的业务需求,MyBatis 的设计者,提供了动态生成 SQL 的功能。

动态 SQL 必要性

  • 比如我们查询Monster 时,如果程序员输入的age 不大于0, 我们的sql语句就不带age 。
  • 更新 Monster 对象时,没有设置的新的属性值,就保持原来的值,设置了新的值,才更新

解决方案分析

  • 从上面的需求我们可以看出,有时我们在生成 sql 语句时,在同一个方法中,还要根据 不同的情况生成不同的 sql 语句
  • 解决方案: MyBatis 提供的动态 SQL 机制

动态 SQL 常用标签

动态 SQL 提供了如下几种常用的标签,类似我们 Java 的控制语句:

(1)if [判断]

(2)where [拼接 where 子句]

(3)choose/when/otherwise [类似 java 的 switch 语句, 注意是单分支]

(4)foreach [类似 in ]

(5)trim [替换关键字/定制元素的功能]

(6)set [在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值]

1.3 动态 SQL-案例演示

1.3.1 新建 Module dynamic-sql 

(1)在原来的项目中,新建 dynamic-sql 模块演示动态 SQL 的使用

(2)新建 Module 后,先创建需要的包,再将需要的文件/资源拷贝过来(这里我们拷贝 Monster.java、resources/jdbc.properties 和 mybatis-config.xml)

(3)创建 MonsterMapper.java MonsterMapper.xml 和 MonsterMapperTest.java

1.3.2 if 标签应用实例 

需求:请查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0, 则输出所有 的妖怪 

代码实现

(1)修改 MonsterMapper.java ,增加接口方法

//根据age查询结果
public List<Monster> findMonsterByAge(@Param(value = "age") Integer age);

 (2)修改 MonsterMapper.xml,sql实现

<!--
1. 配置方法public List<Monster> findMonsterByAge(@Param(value="age") Integer age);
2. 请查询age 大于 10 的所有妖怪,如果程序员输入的age 不大于 0, 则输出所有的妖怪
3. 如果使用原来的#{age} 在test表达式是取不出入参值
4. 解决方案是使用@Param
-->
<select id="findMonsterByAge" parameterType="int" resultType="monster">select * from `monster` where 1 = 1<if test="age >= 0">and `age` > ${age}</if>
</select>

(3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByAge() {List<Monster> monsters =monsterMapper.findMonsterByAge(-1);for (Monster monster : monsters) {System.out.println("monster--" + monster);}if (sqlSession != null) {sqlSession.close();}System.out.println("操作成功~");
}

 测试效果

1.3.3 where 标签应用实例 

需求:查询 id 大于 20 的,并且名字包含 "狐狸精" 的所有妖怪, 如果名字为空 , 就不带名字条件, 如果输入的 id 小于 0, 就不带 id 的条件

(1)修改 MonsterMapper.java ,增加接口方法

//根据id和名字来查询结果
public List<Monster> findMonsterByIdAndName(Monster monster);

 (2)修改 MonsterMapper.xml,sql实现

<!--1、配置public List<Monster> findMonsterByIdAndName(Monster monster);2. 如果我们入参是对象,test表达式中, 直接使用对象的属性名即可3. where标签,会在组织动态sql时,加上where4. mybatis底层自动的去掉多余的AND
-->
<select id="findMonsterByIdAndName" parameterType="monster" resultType="monster">select * from `monster`<where><if test="age >= 0">`age` > #{age}</if><if test="name != null and name != ''">and `name` like '%${name}%'</if></where>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByIdAndName(){Monster monster = new Monster();monster.setAge(20);monster.setName("狐狸精");List<Monster> monsters =monsterMapper.findMonsterByIdAndName(monster);for (Monster o : monsters) {System.out.println("monster--" + o);}if (sqlSession != null){sqlSession.close();}System.out.println("操作成功~");
}

  测试效果

1.3.4 choose/when/otherwise 应用实例

需求:如果给的 name 不为空,就按名字查询妖怪,name 为空并且 id>0,就按 id 来查询妖怪, 如果name 为空 并且 id<0,则以 id > 10 为查询条件。要求使用 choose/when/otherwise 标签实现, 传入参数要求使用 Map 

(1)修改 MonsterMapper.java ,增加接口方法

//测试choose标签的使用
public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map);

(2)修改 MonsterMapper.xml,sql实现

<!--1、配置/实现public List<Monster> findMonsterByIdOrName_choose(Map<String, Object> map)2、  1) 如果给的name不为空,就按名字查询妖怪,2) 如果指定的id>0,就按id来查询妖怪3) 如果前面两个条件都不满足, 就默认查询 `age` > 10的4) 使用mybatis 提供choose-when-otherwise-->
<select id="findMonsterByIdOrName_choose" parameterType="map" resultType="monster">select * from `monster`<where><choose><when test="name != null and name != ''">`name` like '%${name}%'</when><when test="age >= 0">`age` > #{age}</when><otherwise>`age` > 10</otherwise></choose></where>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByIdOrName_choose(){HashMap<String, Object> map = new HashMap<>();map.put("age",-1);map.put("name","");List<Monster> monsters =monsterMapper.findMonsterByIdOrName_choose(map);for (Monster o : monsters) {System.out.println("monster--" + o);}if (sqlSession != null){sqlSession.close();}System.out.println("操作成功~");
}

 测试效果

1.3.5 forEach 标签应用实例 

forEach 标签说明:可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

需求:查询 monster_id 为 20, 22, 34 的妖怪 

(1)修改 MonsterMapper.java ,增加接口方法

//测试foreach的标签使用
public List<Monster> findMonsterById_forEach(Map<String, Object> map)

 (2)修改 MonsterMapper.xml,sql实现

<!--1、配置/实现public List<Monster> findMonsterById_forEach(Map<String, Object> map);2、查询id 为 5, 6, 7 的妖怪3、是foreach标签4. 入参map 中 会如何传入id值 k-v ids - [集合,比如List 10,12,14], 即map 入参中应当有 ids-[10,12,14]-->
<select id="findMonsterById_forEach" parameterType="map" resultType="monster">select * from `monster`<!-- 解读3. 如果ids不为空,则使用foreach标签进行遍历4. collection="ids" 对应入参map的 key 为 ids5. item="id" 在遍历ids集合时,每次取出的值,对应的变量id6. open="where id in (" 表示开头的字符串7. separator="," 遍历出来的多个值之间的 分隔符号8. close=")" 表示结尾的字符串9. #{id} 对应的就是 item="id",即遍历出来的值--><if test="ids != null and ids != ''"><foreach collection="ids" index="" item="id" open="where id in ("separator="," close=")">#{id}</foreach></if>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterById_forEach() {Map<String, Object> map = new HashMap<>();map.put("ids", Arrays.asList(5, 6, 7));List<Monster> monsters =monsterMapper.findMonsterById_forEach(map);for (Monster monster : monsters) {System.out.println("monster--" + monster);}if (sqlSession != null) {sqlSession.close();}System.out.println("操作成功~");}

 测试效果

1.3.6 trim 标签应用实例

trim 可以替换一些关键字

要求:按名字查询妖怪,在动态添加where,并且如果where 子句以开头 and | or 就去除。即使用trim 标签 实现 等价于 where标签的效果

(1)修改 MonsterMapper.java ,增加接口方法

//trim标签的使用
public List<Monster> findMonsterByName_Trim(Map<String, Object> map);

 (2)修改 MonsterMapper.xml,sql实现

suffixOverrides 表示行末尾,prefixOverrides 表示行开头

<!--
1. 配置/实现 public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
2. 按名字和年龄 查询妖怪,如果sql语句开头有 and | or  就替换成 where
3. 如果要实现这个功能,其实使用where标签是一样的效果 [加入where 同时会去掉多余的and]
4. trim prefix="WHERE" prefixOverrides="and|or|wwj" 若子句的开头为 “AND” 或 “OR  或"wwj"
, 就去除
-->
<select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">SELECT * FROM `monster`<trim prefix="WHERE" prefixOverrides="and|or|wwj"><if test="name != null and name != ''">AND `name` = #{name}</if><if test="age != null and age != ''">AND `age` > #{age}</if></trim>
</select>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void findMonsterByName_Trim() {Map<String, Object> map = new HashMap<>();map.put("name", "狐狸精-100");map.put("age", 10);List<Monster> monsters =monsterMapper.findMonsterByName_Trim(map);for (Monster monster : monsters) {System.out.println("monster--" + monster);}if (sqlSession != null) {sqlSession.close();}System.out.println("操作成功~");}

测试效果

1.3.7 set 标签应用实例 

set标签说明:

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号

需求: 请对指定 id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值 

(1)修改 MonsterMapper.java ,增加接口方法

//测试Set标签
public void updateMonster_set(Map<String, Object> map);

(2)修改 MonsterMapper.xml,sql实现

<!--
1. 配置/实现 public void updateMonster_set(Map<String, Object> map);
2. 请对指定id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值
3. 入参要根据sql语句来配合 map [age-10, email-'xx@sohu.com'...]
4. set标签会处理多余的 ,
-->
<update id="updateMonster_set" parameterType="map">UPDATE `monster`<set><if test="age != null and age != ''">`age` = #{age} ,</if><if test="email != null and email != ''">`email` = #{email} ,</if><if test="name != null and name != ''">`name` = #{name} ,</if><if test="birthday != null and birthday != ''">`birthday` = #{birthday} ,</if><if test="salary != null and salary != ''">`salary` = #{salary} ,</if><if test="gender != null and gender != ''">`gender` = #{gender} ,</if></set>WHERE id = #{id}
</update>

 (3)修改 MonsterMapperTest.java 增加测试方法

@Test
public void updateMonster_set() {Map<String, Object> map = new HashMap<>();map.put("id", 1);map.put("name", "牛魔王5-100");map.put("age", 76);map.put("email", "wwj@qq.com");monsterMapper.updateMonster_set(map);//修改需要有commitif (sqlSession != null) {sqlSession.commit();sqlSession.close();}System.out.println("操作成功~");
}

 测试效果

2 映射关系一对一

2.1 映射关系-官方文档

文档地址: mybatis – MyBatis 3 | XML 映射器

2.2 映射关系 1 1-基本介绍

基本介绍:项目中 1 对 1 的关系是一个基本的映射关系,比如:Person(人) --- IDCard(身份证) 

2.3 映射关系 1 1-映射方式 

2.3.1 映射方式

(1)通过配置 XxxMapper.xml 实现 1 对 1 [配置方式]

(2)通过注解的方式实现 1 对 1 [注解方式]

2.3.2 配置 Mapper.xml 的方式-应用实例

2.3.2.1 方式1

说明:通过配置 XxxMapper.xml 的方式来实现下面的 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到对应的 idencard 信息 

完成功能示意(如下) 

person--Person{id=1, name='张三', card=IdenCard{id=1,card_sn='111111111111110'}}

 代码实现

(1)创建 person 表和 idencard

CREATE TABLE idencard(
id INT PRIMARY KEY AUTO_INCREMENT,
card_sn VARCHAR(32) NOT NULL DEFAULT '' 
);CREATE TABLE person(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL DEFAULT '',
card_id INT , --对应idencard的主键id
FOREIGN KEY (card_id) REFERENCES idencard(id) --外键
);INSERT INTO idencard VALUES(1,'111111111111110');
INSERT INTO person VALUES(1,'张三',1);
SELECT * FROM person;
SELECT * FROM idencard;

(2)创建新的 module(mybatis-mapping), 相关配置文件可以从上一个 module 拷贝 (这里我们拷贝resources/jdbc.properties 和 mybatis-config.xml)。嫌麻烦这一步可以省略,这里这是为了直观,所以一个章节使用一个module

(3)创 建 entity: src\main\java\com\entity\IdenCard.java 和 src\main\java\com\entity\Person.java

package com.entity;public class IdenCard {private Integer id;private String card_sn;//通过查询IdenCard 可以级联查询得到personprivate Person person;public Person getPerson() {return person;}public void setPerson(Person person) {this.person = person;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getCard_sn() {return card_sn;}public void setCard_sn(String card_sn) {this.card_sn = card_sn;}@Overridepublic String toString() {return "IdenCard{" +"id=" + id +", card_sn='" + card_sn + '\'' +", person=" + person +'}';}
}
package com.entity;public class Person {private Integer id;private String name;//因为我们的需要实现一个级联操作, 一个人需要对应一个身份证//这里需要直接定义IdenCard对象属性private IdenCard card;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public IdenCard getCard() {return card;}public void setCard(IdenCard card) {this.card = card;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +", card=" + card +'}';}
}

(4)创建 com\mapper\IdenCardMapper.java接口 和 com\mapper\IdenCardMapper.xml映射文件

package com.hspedu.mapper;import com.hspedu.entity.IdenCard;public interface IdenCardMapper {//根据id获取到身份证序列号public IdenCard getIdenCardById(Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mapper.IdenCardMapper"><select id="getIdenCardById" parameterType="integer" resultType="idenCard">select * from `idencard` where `id`=#{id}</select>
</mapper>

  (5)创建 PersonMapper.java PersonMapper.xml

package com.mapper;import com.entity.Person;public interface PersonMapper {//通过Person的id获取到Person,包括这个Person关联的IdenCard对象[级联查询]public Person getPersonById(Integer id);}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mapper.PersonMapper"><!--1、配置/实现public Person getPersonById(Integer id);2、完成通过Person的id获取到Person,包括这个Person关联的IdenCard对象[级联查询]3. 自定义resultMap 搞定 映射返回的结果--><resultMap id="PersonResultMap" type="Person"><!--id标签 – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能1.property="id" 表示person 属性 id ,通常是主键2.column="id" 表示对应表的字段--><id property="id" column="id"/><result property="name" column="name"/><!--association标签 – 一个复杂类型的关联1. property="card" 表示 Person对象的 card 属性2. javaType="IdenCard" 表示card 属性 的类型3. column="id" 是从我们的 下面这个语句查询后返回的字段SELECT *  FROM `person`,`idencard` WHERE `person`.id=1AND `person`.card_id = `idencard`.id--><association property="card" javaType="IdenCard"><result property="id" column="id"/><result property="card_sn" column="card_sn"/></association></resultMap><select id="getPersonById" parameterType="Integer"resultMap="PersonResultMap">SELECT *  FROM `person`,`idencard` WHERE `person`.id = #{id}AND `person`.card_id = `idencard`.id</select></mapper>

 (6)创建 src\test\java\com\mapper\IdenCardMapperTest.java 和src\test\java\com\map per\PersonMapperTest.java , 进行测试 

package com.mapper;import com.entity.IdenCard;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;public class IdenCardMapperTest {SqlSession sqlSession;IdenCardMapper idenCardMapper;@Beforepublic void init(){sqlSession = MyBatisUtils.getSqlSession();idenCardMapper = sqlSession.getMapper(IdenCardMapper.class);}@Testpublic void getIdenCardById(){int id = 1;IdenCard idenCard = idenCardMapper.getIdenCardById(id);System.out.println("idenCard=" + idenCard);}
}
package com.mapper;import com.entity.Person;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;public class PersonMapperTest {SqlSession sqlSession;PersonMapper personMapper;@Beforepublic void init(){sqlSession = MyBatisUtils.getSqlSession();personMapper = sqlSession.getMapper(PersonMapper.class);}@Testpublic void getPersonById(){Person person = personMapper.getPersonById(1);System.out.println("person--" + person);if (sqlSession != null){sqlSession.close();}}}

测试效果

2.3.2.2 方式2

(1)修改PersonMapper.java 接口增加方法 PersonMapper.xml 配置相应sql映射

//通过Person的id获取到Person,包括这个Person关联的IdenCard对象,方式2
public Person getPersonById2(Integer id);
<!--1、方式21) 先通过 SELECT * FROM `person` WHERE `id` = #{id} 返回 person信息2) 再通过 返回的card_id 值,再执行操作,得到IdenCard 数据
-->
<resultMap id="PersonResultMap2" type="Person"><id property="id" column="id"/><result property="name" column="name"/><!--1. mybatis第二种方式核心思想: 将多表联查,分解成单表操作 , 这样简洁,而且易于维护 ,推荐2. property="card": 表示 Person对象的 card 属性3. column="card_id" 这个是SELECT * FROM `person` WHERE `id` = #{id}  返回的 字段 card_id 信息/数据4. 返回的 字段 card_id 信息/数据 作为getIdenCardById入参, 来执行--><association property="card" column="card_id"select="com.hspedu.mapper.IdenCardMapper.getIdenCardById" />
</resultMap>
<select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">SELECT * FROM `person` WHERE `id` = #{id}
</select>

(2)修改 PersonMapperTest.java 进行测试

@Test
public void getPersonById2(){Person person = personMapper.getPersonById2(1);System.out.println("person--" + person);if (sqlSession != null){sqlSession.close();}
}

测试效果

2.3.3 注解的方式实现-应用实例

说明:通过注解的方式来实现下面的 1 对 1 的映射关系,实现级联查询,通过 person 可以获取到 对应的 identcard 信息 (在实际开发中还是推荐使用配置方式)

(1)创建 com\mapper\IdenCardMapperAnnotaion.java

package com.mapper;import com.entity.IdenCard;
import org.apache.ibatis.annotations.Select;/*** IdenCardMapperAnnotation: 使用注解方式实现1对1的映射*/
public interface IdenCardMapperAnnotation {//根据id获取到身份证//这个方法不需要返回任何级联对象@Select("SELECT * FROM `idencard` WHERE `id` = #{id}")public IdenCard getIdenCardById(Integer id);
}

 (2)创建 com\mapper\PersonMapperAnnotation.java

package com.mapper;import com.entity.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;public interface PersonMapperAnnotation {//这里注解实现方法//注解的形式就是对前面xml配置方式的体现@Select("SELECT * FROM `person` WHERE `id` = #{id}")@Results({@Result(id = true, property = "id", column = "id"),@Result(property = "name", column = "name"),@Result(property = "card", column = "card_id",one = @One(select = "com.mapper.IdenCardMapper.getIdenCardById"))})public Person getPersonById(Integer id);
}

 (3)创建 com\mapper\PersonMapperAnnotationTest.java 完成测试

package com.mapper;import com.entity.Person;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;public class PersonMapperAnnotationTest {//属性private SqlSession sqlSession;private PersonMapperAnnotation personMapperAnnotation;//初始化@Beforepublic void init() {//获取到sqlSessionsqlSession = MyBatisUtils.getSqlSession();personMapperAnnotation = sqlSession.getMapper(PersonMapperAnnotation.class);}@Testpublic void getPersonById() {Person person = personMapperAnnotation.getPersonById(1);System.out.println("person----" + person);if(sqlSession != null) {sqlSession.close();}}
}

测试效果

3 映射关系多对一 

.1 映射关系-官方文档

 文档地址: mybatis – MyBatis 3 | XML 映射器 

3.2 映射关系多对 1-基本介绍 

基本介绍

(1)项目中多对 1 的关系是一个基本的映射关系, 多对 1, 也可以理解成是 1 对多

  • User --- Pet: 一个用户可以养多只宠物
  • Dep ---Emp : 一个部门可以有多个员工

特别说明

(1)在实际的项目开发中, 要求会使用双向的多对一的映射关系

(2)说明:什么是双向的多对一的关系 : 比如通过 User 可以查询到对应的 Pet, 反过来,通过 Pet 也可以级联查询到对应的 User 信息

(3)多对多的关系,是在多对 1 的基础上扩展即可. 

3.3 映射关系多对 1-映射方式 

3.3.1 映射方式

(1)方式 1:通过配置 XxxMapper.xml 实现多对 1

(2)方式 2:通过注解的方式实现 多对 1 

3.3.2 配置 Mapper.xml 方式-应用实例

需求说明: 实现级联查询,通过 user 的 id 可以查询到用户信息,并可以查询到关联的 pet 信息,反过来,通过 Pet 的 id 可以查询到 Pet 的信息,并且可以级联查询到它的主人 User 对象信息

(1)创建 mybatis_user 和 mybatis_pet 表

CREATE TABLE mybatis_user(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32) NOT NULL DEFAULT '' 
);
CREATE TABLE mybatis_pet(
id INT PRIMARY KEY AUTO_INCREMENT,
nickname VARCHAR(32) NOT NULL DEFAULT '',
user_id INT ,
FOREIGN KEY (user_id) REFERENCES mybatis_user(id)
);
INSERT INTO mybatis_user
VALUES(NULL,'宋江'),(NULL,'张飞');
INSERT INTO mybatis_pet
VALUES(1,'黑背',1),(2,'小哈',1);
INSERT INTO mybatis_pet
VALUES(3,'波斯猫',2),(4,'贵妃猫',2);
SELECT * FROM mybatis_user;
SELECT * FROM mybatis_pet;

(2)创 建 entity : entity\Pet.java 和 entity\User.java 

package com.entity;import java.util.List;public class User {private Integer id;private String name;//因为一个user可以养多个宠物,mybatis 使用集合List<Pet>体现这个关系private List<Pet> pets;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public List<Pet> getPets() {return pets;}public void setPets(List<Pet> pets) {this.pets = pets;}//这toString会带来麻烦?=>会造成StackOverFlow//@Override//public String toString() {//    return "User{" +//            "id=" + id +//            ", name='" + name + '\'' +//            ", pets=" + pets +//            '}';//}
}
package com.entity;public class Pet {private Integer id;private String nickname;//一个pet对应一个主人 User对象private User user;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}}

(3)创建 PetMapper.java 和 UserMapper.java 

package com.mapper;import com.entity.Pet;import java.util.List;public interface PetMapper {//通过User的id来获取pet对象,可能有多个,因此使用List接收public List<Pet> getPetByUserId(Integer userId);//通过pet的id获取Pet对象, 同时会查询到pet对象关联的user对象public Pet getPetById(Integer id);}
package com.mapper;import com.entity.User;public interface UserMapper {//通过id获取User对象public User getUserById(Integer id);
}

(4)创建 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mapper.UserMapper"><!--1、配置/实现 public User getUserById(Integer id);2、思路(1) 先通过user-id 查询得到user信息 (2) 再根据user-id查询对应的pet信息并映射到User-List<Pet> pets--><resultMap id="userMap" type="com.entity.User"><id property="id" column="id"/><result property="name" column="name"/><!--因为pets属性是集合,因此这里需要是collection标签来处理1. ofType="Pet" 指定返回的集合中存放的数据类型Pet2. collection 表示 pets 是一个集合3. property="pets" 是返回的user对象的属性 pets4. column="id" SELECT * FROM `mybatis_user` WHERE `id` = #{id} 返回的id字段对应的值--><collection property="pets" column="id" ofType="pet"select="com.mapper.PetMapper.getPetByUserId"/></resultMap><select id="getUserById" parameterType="integer" resultMap="userMap">select * from `mybatis_user` where id=#{id}</select></mapper>

(5) 创建 PetMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.mapper.PetMapper"><!--1、通过User的id来获取pet对象,可能有多个,因此使用List接收2、public List<Pet> getPetByUserId(Integer userId);--><resultMap id="PetResultMap" type="Pet"><id property="id" column="id"/><result property="nickname" column="nickname"/><association property="user" column="user_id"select="com.mapper.UserMapper.getUserById" /></resultMap><select id="getPetByUserId" parameterType="integer" resultMap="PetResultMap">select * from `mybatis_pet` where user_id=#{userId}</select><!--1. 这里直接复用PetResultMap2. 实现/配置public Pet getPetById(Integer id);3. 通过pet的id获取Pet对象--><select id="getPetById"parameterType="Integer"resultMap="PetResultMap">SELECT * FROM `mybatis_pet` WHERE `id` = #{id}</select>
</mapper>

 (6)创建 PetMapperTest.java 完成测试

package com.mapper;import com.entity.Pet;
import com.entity.User;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;import java.util.List;public class PetMapperTest {//属性private SqlSession sqlSession;private PetMapper petMapper;//初始化@Beforepublic void init() {//获取到sqlSessionsqlSession = MyBatisUtils.getSqlSession();petMapper = sqlSession.getMapper(PetMapper.class);}@Testpublic void getPetByUserId() {List<Pet> pets = petMapper.getPetByUserId(2);for (Pet pet : pets) {System.out.println("pet信息-" + pet.getId() + "-" + pet.getNickname());User user = pet.getUser();System.out.println("user信息 name-" + user.getName());}if(sqlSession != null) {sqlSession.close();}}@Testpublic void getPetById() {Pet pet = petMapper.getPetById(2);System.out.println("pet信息-" + pet.getId() + "-" + pet.getNickname());User user = pet.getUser();System.out.println("user信息-" + user.getId() + "-" + user.getName());if(sqlSession != null) {sqlSession.close();}}}

 测试效果

3.3.3 注解实现多对 1 映射-应用实例

需求说明: 通过注解的方式来实现下面的多对 1 的映射关系,实现级联查询,完成前面完成 的任务,通过 User-->Pet 也可 Pet->User , 在实际开发中推荐使用配置方式来做

(1)创建 UserMapperAnnotation.java

package com.mapper;import com.entity.User;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;/*** UserMapperAnnotation:以注解的方式来配置多对一*/
public interface UserMapperAnnotation {//通过id获取User对象@Select("SELECT * FROM `mybatis_user` WHERE `id` = #{id}")@Results({@Result(id = true, property = "id", column = "id"),@Result(property = "name", column = "name"),//这里请小伙伴注意,pets属性对应的是集合@Result(property = "pets",column = "id",many = @Many(select = "com.mapper.PetMapperAnnotation.getPetByUserId"))})public User getUserById(Integer id);
}

 (2)创建 PetMapperAnnotation.java

package com.mapper;import com.entity.Pet;
import org.apache.ibatis.annotations.*;import java.util.List;public interface PetMapperAnnotation {//通过User的id来获取pet对象,可能有多个,因此使用List接收//id = "PetResultMap" 就是给我们的Results[Result Map] 指定一个名字//,目的是为了后面复用@Select("SELECT * FROM `mybatis_pet` WHERE `user_id` = #{userId}")@Results(id = "PetResultMap", value = {@Result(id = true, property = "id", column = "id"),@Result(property = "nickname", column = "nickname"),@Result(property = "user",column = "user_id",one = @One(select = "com.mapper.UserMapperAnnotation.getUserById"))})public List<Pet> getPetByUserId(Integer userId);//通过pet的id获取Pet对象, 同时会查询到pet对象关联的user对象/*** @ResultMap("PetResultMap") 使用/引用我们上面定义的 Results[ResultMap]*/@Select("SELECT * FROM `mybatis_pet` WHERE `id` = #{id}")@ResultMap("PetResultMap")public Pet getPetById(Integer id);
}

(3)创建 UserMapperAnnotationTest.java 完成测试

package com.mapper;import com.entity.Pet;
import com.entity.User;
import com.util.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;import java.util.List;public class UserMapperAnnotationTest {//属性private SqlSession sqlSession;private UserMapperAnnotation userMapperAnnotation;//初始化@Beforepublic void init() {//获取到sqlSessionsqlSession = MyBatisUtils.getSqlSession();userMapperAnnotation = sqlSession.getMapper(UserMapperAnnotation.class);}@Testpublic void getUserById() {User user = userMapperAnnotation.getUserById(2);System.out.println("user信息-" + user.getId() + "-" + user.getName());List<Pet> pets = user.getPets();for (Pet pet : pets) {System.out.println("宠物信息-" + pet.getId() + "-" + pet.getNickname());}if(sqlSession != null) {sqlSession.close();}}
}

 测试效果

4 缓存-提高检索效率的利器

4.1 缓存-官方文档

文档地址:mybatis – MyBatis 3 | XML 映射器 

4.2 一级缓存

4.2.1 基本介绍

(1)默认情况下,mybatis 是启用一级缓存的/本地缓存/local Cache,它是 SqlSession 级别的

(2)同一个 SqlSession 接口对象调用了相同的 select 语句,会直接从缓存里面获取,而不是再去查询数据库 

(3)一级缓存原理图

4.2.2 一级缓存快速入门 

需求: 当我们第 1 次查询 id=1 的 Monster 后,再次查询 id=1 的 monster 对象,就会直接 从一级缓存获取,不会再次发出 sql

(1)创建新module: mybatis_cache , 必要的文件和配置直接从mybatis_quickstart module 拷贝即可,需要拷贝的文件和配置如图

(2)使用 MonsterMapperTest.java , 运行 getMonsterById() 通过查看日志输出, 结论我们多次运行,总是会发出 SQL

(3)修改MonsterMapperTest.java, 增加测试方法, 测试一级缓存的基本使用(无需配置,因为默认的就是一级缓存)

//测试一级缓存
@Test
public void level1CacheTest() {//查询id=1的monsterMonster monster = monsterMapper.getMonsterById(1);System.out.println("monster=" + monster);//再次查询id=1的monster//当我们再次查询 id=1的Monster时,直接从一级缓存获取,不会再次发出sqlSystem.out.println("--一级缓存默认是打开的,当你再次查询相同的id时, 不会再发出sql----");Monster monster2 = monsterMapper.getMonsterById(1);System.out.println("monster2=" + monster2);if (sqlSession != null) {sqlSession.close();}
}

4.2.3 一级缓存失效分析 

(1)关闭 sqlSession 会话后, 再次查询,会到数据库查询

(2)当执行 sqlSession.clearCache() 会使一级缓存失效

(3)当对同一个 monster 修改(update),该对象在一级缓存会失效

4.3 二级缓存

4.3.1 基本介绍

(1)二级缓存和一级缓存都是为了提高检索效率的技术

(2)最大的区别就是作用域的范围不一样,一级缓存的作用域是 sqlSession 会话级别,在一次 会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效

(3)二级缓存原理图 

4.3.2 二级缓存快速入门

(1)mybatis-config.xml 配置中开启二级缓存 

<configuration><properties resource="jdbc.properties"/><settings><!--配置MyBatis自带的日志输出-查看原生的sql--><setting name="logImpl" value="STDOUT_LOGGING"/><!--1、全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 可以理解这是一个总开关2、默认就是: true--><setting name="cacheEnabled" value="true"/></settings>
。。。。。
</configuration>
(2)使用二级缓存时 entity 类实现序列化接口 (serializable),因为二级缓存可能使用到序 列化技术(大部分情况用不到,一些第三方缓存库可能用得到,所以可以不配置,等需要的时候再配置也可以)
public class Monster implements Serializable {

(3)在对应的 XxxMapper.xml 中设置二级缓存的策略 

<mapper namespace="com.mapper.MonsterMapper"><!--1、配置二级缓存: 是mybatis自带2、eviction="FIFO" – 先进先出:按对象进入缓存的顺序来移除它们。默认LRU,最近最少使用。可选属性还有"SOFT"软引用,"WEAK"弱引用3. flushInterval 刷新间隔 是毫秒单位 60000 表示 60s4. size="512": 引用数目, 属性可以被设置为任意正整数, 默认10245. readOnly="true": (只读)属性可以被设置为 true 或 false: 如果我们只是用于读操作,建议设置成 true, 这样可以提示效率, 如果有修改操作,设置成 false, 默认就是false--><cache eviction="FIFO" flushInterval="60000"size="512" readOnly="true"/>。。。。。。。
</mapper>

(4)修改 MonsterMapperTest.java , 完成测试

//测试二级缓存的使用
@Test
public void level2CacheTest() {//查询id=1的monsterMonster monster = monsterMapper.getMonsterById(1);System.out.println("monster=" + monster);//这里关闭sqlSessionif (sqlSession != null) {sqlSession.close();}//重新获取sqlSessionsqlSession = MyBatisUtils.getSqlSession();//重新获取了monsterMappermonsterMapper = sqlSession.getMapper(MonsterMapper.class);//再次查询id=3的monsterSystem.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存, " +"当你再次查询相同的id时, 依然不会再发出sql, 而是从二级缓存获取数据----");Monster monster2 = monsterMapper.getMonsterById(1);System.out.println("monster2=" + monster2);if (sqlSession != null) {sqlSession.close();}
}

测试效果 

4.3.3 注意事项和使用陷阱

(1)理解二级缓存策略的参数

<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/

上面的配置意思如下:

创建了 FIFO 的策略,每隔 30 秒刷新一次,最多存放 360 个对象而且返回的对象被认为是 只读的。

  • eviction:缓存的回收策略
  • flushInterval:时间间隔,单位是毫秒
  • size:引用数目,内存大就多配置点,要记住你缓存的对象数目和你运行环境的可用内存 资源数目。默认值是 1024
  • readOnly:true,只读

(2)四大策略

  • LRU – 最近最少使用的:移除最长时间不被使用的对象,它是默认
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

(3)如何禁用二级缓存

1) 在 mybatis-config.xml

<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 默认就是 true-->
<setting name="cacheEnabled" value="false"/>
</settings>

2)在 mapper\MonsterMapper.xml 中把下面这一行注释掉

<!--<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/>-->

3) 或者更加细粒度的, 在配置方法上指定

设置 useCache=false 可以禁用当前 select 语句的二级缓存,即每次查询都会发出 sql 去查询, 默认情况是 true,即该 sql 使用二级缓存。 注意:一般我们不需要去修改,使用默认的即可

(4)mybatis 刷新二级缓存的设置

<update id="updateMonster" parameterType="Monster" flushCache="true">UPDATE mybatis_monster SET NAME=#{name},age=#{age} WHERE id=#{id}
</update>

insert、update、delete 操作数据后需要刷新缓存,如果不执行刷新缓存会出现脏读 默认为 true,默认情况下为 true 即刷新缓存,一般不用修改。

(5)不会出现一级缓存和二级缓存中有同一个数据。因为二级缓存(数据)是在一级缓存关闭 之后才有的 

4.4 Mybatis 的一级缓存和二级缓存执行顺序

缓存执行顺序是:二级缓存-->一级缓存-->数据库 

说明:先看二级缓存有没有数据,如果没有再看一级缓存,一级缓存也没有数据,才会连接数据库。当我们关闭一级缓存的时候,如果配置了二级缓存,那么一级缓存的数据,会放入到二级缓存 

小实验,测试缓存执行顺序

(1)修改 MonsterMapperTest.java,增加如下方法

//演示二级缓存->一级缓存->DB执行的顺序
@Test
public void cacheSeqTest() {System.out.println("查询第1次");//DB, 会发出SQL, 分析cache hit ratio 0.0Monster monster1 = monsterMapper.getMonsterById(1);System.out.println(monster1);//这里关闭sqlSession, 一级缓存数据没有//当我们关闭一级缓存的时候,如果你配置二级缓存,那么一级缓存的数据,会放入到二级缓存sqlSession.close();sqlSession = MyBatisUtils.getSqlSession();monsterMapper = sqlSession.getMapper(MonsterMapper.class);System.out.println("查询第2次");//从二级缓存获取id=1 monster , 就不会发出SQL, 分析cache hit ratio 0.5Monster monster2 = monsterMapper.getMonsterById(1);System.out.println(monster2);System.out.println("查询第3次");//从二级缓存获取id=1 monster, 不会发出SQL, 分析cache hit ratio 0.6666Monster monster3 = monsterMapper.getMonsterById(1);System.out.println(monster3);if (sqlSession != null) {sqlSession.close();}System.out.println("操作成功");}

 (2)运行结果:

4.5 EhCache 缓存

4.5.1 基本介绍

(1)配置文档(官方解释): https://www.cnblogs.com/zqyanywn/p/10861103.html

(2)文档说明(通俗解释): https://www.taobye.com/f/view-11-23.html 

(3)EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点

(4)MyBatis 有自己默认的二级缓存(前面我们已经使用过了),但是在实际项目中,往往使用的是更加专业的第三方缓存产品 作为 MyBatis 的二级缓存,EhCache 就是非常优秀的缓存产品

4.5.2 配置和使用 EhCache

(1)加入相关依赖, 修改 mybatis_cache\pom.xml

<dependencies><!--引入ehcache核心库/jar--><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>2.6.11</version></dependency><!--引入需要使用的slf4j--><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.25</version></dependency><!--引入mybatis整合ehcache库/jar--><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version></dependency>
</dependencies>

 (2)mybatis-config.xml 仍然打开二级缓存 

<settings><!--1、全局性地开启或关闭所有映射器配置文件中已配置的任何缓存, 可以理解这是一个总开关2、默认就是: true--><setting name="cacheEnabled" value="true"/>
</settings>

 (3)加入 mybatis_cache\src\main\resources\ehcache.xml 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache><!--diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:user.home – 用户主目录user.dir  – 用户当前工作目录java.io.tmpdir – 默认临时文件路径--><diskStore path="java.io.tmpdir/Tmp_EhCache"/><!--defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。--><!--name:缓存名称。maxElementsInMemory:缓存最大数目maxElementsOnDisk:硬盘最大缓存个数。eternal:对象是否永久有效,一但设置了,timeout将不起作用。overflowToDisk:是否保存到磁盘,当系统宕机时timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。可理解为: TTI用于设置对象在cache中的最大闲置时间,就是 在一直不访问这个对象的前提下,这个对象可以在cache中的存活时间。timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。可理解为: TTL用于设置对象在cache中的最大存活时间,就是 无论对象访问或是不访问(闲置),这个对象在cache中的存活时间diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。clearOnFlush:内存数量最大时是否清除。memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。FIFO,first in first out,这个是大家最熟的,先进先出。LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。--><defaultCacheeternal="false"maxElementsInMemory="10000"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="1800"timeToLiveSeconds="259200"memoryStoreEvictionPolicy="LRU"/><cachename="cloud_user"eternal="false"maxElementsInMemory="5000"overflowToDisk="false"diskPersistent="false"timeToIdleSeconds="1800"timeToLiveSeconds="1800"memoryStoreEvictionPolicy="LRU"/></ehcache>

(4)在 XxxMapper.xml 中启用 EhCache , 记得把原来 MyBatis 自带的缓存配置注销

<!--配置/启用ehcache-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

(5)修改 MonsterMapperTest.java , 增加测试方法, 完成测试

//测试ehCache级缓存
@Test
public void ehCacheTest() {//查询id=1的monsterMonster monster = monsterMapper.getMonsterById(1);//会发出SQL, 到db查询System.out.println("monster=" + monster);//这里关闭sqlSession, 一级缓存[数据]失效.=> 将数据放入到二级缓存 (ehcache)if (sqlSession != null) {sqlSession.close();}//重新获取sqlSessionsqlSession = MyBatisUtils.getSqlSession();//重新获取了monsterMappermonsterMapper = sqlSession.getMapper(MonsterMapper.class);//再次查询id=1的monsterSystem.out.println("--虽然前面关闭了sqlSession,因为配置二级缓存(ehcache), " +"当你再次查询相同的id时, 不会再发出sql, 而是从二级缓存(ehcache)获取数据----");Monster monster2 = monsterMapper.getMonsterById(1);System.out.println("monster2=" + monster2);//再次查询id=3的monster, 仍然到二级缓存(ehcache), 获取数据, 不会发出sqlMonster monster3 = monsterMapper.getMonsterById(1);System.out.println("monster3=" + monster3);if (sqlSession != null) {sqlSession.close();}}

测试效果

4.5.3 EhCache 缓存-细节说明

 如何理解 EhCache 和 MyBatis 缓存的关系

(1)MyBatis 提供了一个接口 Cache【如右图,找到 org.apache.ibatis.cache.Cache ,关联源 码包就可以看到 Cache 接口】

(2)只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用,Ehcache 就是实现了该接口 

(3)MyBatis 默认情况(即一级缓存)是使用的 PerpetualCache 类实现 Cache 接口的

(4)当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的.

(5)我们看一下源码,发现缓存的本质就是 Map<Object,Object> 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/380526.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【瑞吉外卖 | day07】移动端菜品展示、购物车、下单

文章目录 瑞吉外卖 — day71. 导入用户地址簿相关功能代码1.1 需求分析1.2 数据模型1.3 代码开发 2. 菜品展示2.1 需求分析2.2 代码开发 3. 购物车3.1 需求分析3.2 数据模型3.3 代码开发 4. 下单4.1 需求分析4.2 数据模型4.3 代码开发 瑞吉外卖 — day7 移动端相关业务功能 —…

华为USG6000V防火墙NAT智能选举

目录 一、拓扑图 二、要求 三、配置思路及方法 要求1&#xff1a;通过多对多的NAT实现上网功能 思路&#xff1a;基础IP地址配置按照之前的进行配置&#xff0c;接着在策略里配置多对多的NAT 要求2&#xff1a;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的…

uniapp中给data中的变量赋值报错

排查了一上午&#xff0c;原本以为是赋值的这个变量有一个键名是空字符串的问题&#xff0c;后来发现是因为在data中定义变量是写的是{}&#xff0c;如果写成null就不会报错了&#xff0c;具体原因不清楚为什么

Spark中的JOIN机制

Spark中的JOIN机制 1、Hash Join概述2、影响JOIN的因素3、Spark中的JOIN机制3.1、Shuffle Hash Join3.2、Broadcast Hash Join3.3、Sort Merge Join3.4、Cartesian Product Join3.5、Broadcast Nested Loop Join4、Spark中的JOIN策略5、Spark JOIN机制与策略总结5.1、Spark中的…

【BUG】已解决:WslRegisterDistribution failed with error: 0x800701bc

已解决&#xff1a;WslRegisterDistribution failed with error: 0x800701bc 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武…

C++初学者指南-5.标准库(第一部分)--标准库查找算法

C初学者指南-5.标准库(第一部分)–标准库查找算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库查找算法查找/定位一个元素findfind_iffind_if_notfind_last / find_last_if / find_last_if_notfind_first_of 查找范围内的子范围 search find_endstarts_withends_with 找到…

1个Xpath定位可以在Web页面查找到多个元素Selenium

1个Xpath定位可以在Web页面查找到多个元素Selenium//input[id\"transactionId\"] 打开Web页面&#xff0c; 点击F12可以看到压面 点击Ctrl F 可以点图如下图的输入框&#xff0c;输入xpath&#xff0c;看右侧可以找到3个对应的元素 点击Ctrl F 点击Ctrl F 点…

uniapp开发钉钉小程序流程

下载开发工具 1、小程序开发工具 登录钉钉开发平台&#xff0c;根据自己的需求下载合适的版本&#xff0c;我这里下载的是Windows &#xff08;64位&#xff09;版本 小程序开发工具 - 钉钉开放平台 2、HBuilder X HBuilderX-高效极客技巧 新建项目及相关配置 新建项目 …

最新!CSSCI(2023-2024)期刊目录公布!

【SciencePub学术】据鲁迅美术学院7月16日消息&#xff0c;近日&#xff0c;南京大学中国社会科学研究评价中心公布了中文社会科学引文索引&#xff08;CSSCI&#xff09;&#xff08;2023—2024&#xff09;数据库最新入选目录。 C刊一般指CSSCI来源期刊&#xff0c;即南大核心…

通过splunk web服务将服务器上文件下载到本地

1. 需求说明 工作中经常遇到需要将服务器上的文件下载到本地&#xff0c;但是由于各种网络环境限制&#xff0c;没办法使用winscp或者xftp工具&#xff0c;那么如何将服务器上的文件下载下来呢&#xff1f; 这里提供一种思路: 如果服务器上安装有web服务&#xff0c;可将待下…

大语言模型-检索测评指标

1. MRR &#xff08;Mean Reciprocal Rank&#xff09;平均倒数排名&#xff1a; 衡量检索结果排序质量的指标。 计算方式&#xff1a; 对于每个查询&#xff0c;计算被正确检索的文档的最高排名的倒数的平均值&#xff0c;再对所有查询的平均值取均值。 意义&#xff1a; 衡量…

Elastic 线下 Meetup 将于 2024 年 7 月 27 号在深圳举办

2024 Elastic Meetup 深圳站活动&#xff0c;由 Elastic、腾讯、新智锦绣联合举办&#xff0c;现诚邀广大技术爱好者及开发者参加。 时间地点 2024年 7 月 27 日 13:30-18:00 活动地点 中国深圳 南山区海天二路 33 号腾讯滨海大厦 北塔 3 楼多功能厅 ​ 活动流程 14:00-15…

【你也能从零基础学会网站开发】 SQL Server 2000数据库的创建、移除、备份还原操作以及索引、视图、存储过程、触发器基本介绍!

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 认识数据库中都…

GD32F303想控制PA13~15、PB3和PB4不成功?

有没有小伙伴遇到这种情况&#xff1a;在使用GD32F303的时候&#xff0c;想要将PA13~15以及PB3和PB4作为IO口来使用&#xff0c;却怎么也不成功呢&#xff1f;下面小编就来告诉大家原因。 我们先来看下GD32F303 datasheet中这几个引脚的定义&#xff1a; 原来&#xff0c;这几…

钡铼Profinet、EtherCAT、Modbus、MQTT、Ethernet/IP、OPC UA分布式IO系统BL20X系列耦合器

BL20X系列耦合器是钡铼技术开发的一款用于分布式I/O系统的设备&#xff0c;专为工业环境下的高速数据传输和远程设备控制而设计&#xff0c;支持多种工业以太网协议&#xff0c;包括Profinet、EtherCAT、Modbus、MQTT、Ethernet/IP和OPC UA等。如果您正在考虑部署BL20X系列耦合…

AWS服务器购买:如何选择合适的AWS云服务器

在当今数字化时代,云计算已成为企业IT基础设施的重要组成部分。作为全球领先的云服务提供商之一,亚马逊网络服务(AWS)提供了丰富多样的云服务器选项。然而,面对众多选择,如何为您的业务需求挑选最合适的AWS云服务器呢?我们结合九河云的分析来给你解答。 1. 明确业务需求 首先…

excel系列(三) - 利用 easyexcel 快速实现 excel 文件导入导出

一、介绍 在上篇文章中&#xff0c;我们介绍了 easypoi 工具实现 excel 文件的导入导出。 本篇我们继续深入介绍另一款更优秀的 excel 工具库&#xff1a;easyexcel 。 二、easyexcel easyexcel 是阿里巴巴开源的一款 excel 解析工具&#xff0c;底层逻辑也是基于 apache p…

阿里云盾占用资源的问题AliYunDun,AliYunDunUpdate

目录 1.关闭AliYunDunUpdate&#xff0c;AliYunDun&#xff0c;AliYunDunMonitor。 2.发现报错如下 3.打开阿里云安全中心控制台 4.成功解决 2.开启云盾命令 “如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经验分享对您有所帮助。如果您有任何疑问或者想分享您…

深度学习落地实战:大模型生成图片

前言 大家好&#xff0c;我是机长 本专栏将持续收集整理市场上深度学习的相关项目&#xff0c;旨在为准备从事深度学习工作或相关科研活动的伙伴&#xff0c;储备、提升更多的实际开发经验&#xff0c;每个项目实例都可作为实际开发项目写入简历&#xff0c;且都附带完整的代…

Django F()函数

F()函数的作用 F()函数在Django中是一个非常强大的工具&#xff0c;主要用于在查询表达式中引用模型的字段。它允许你在数据库层面执行各种操作&#xff0c;而无需将数据加载到Python内存中。这不仅提高了性能&#xff0c;还允许你利用数据库的优化功能。 字段引用 在查询表达…