控制类和dao层接口以及mapper中的xml是怎样的关联的?
在Mybatis中,控制类和dao层接口是通过mapper的xml文件进行连接的。
- 控制类调用dao层接口中的方法,通过接口实现进行访问数据库操作。
- dao层接口定义数据库操作的方法,提供给控制类调用。
- mapper的xml文件中定义了dao层接口中方法的具体实现,包括SQL语句的编写和参数的映射关系。
- 接口实现是通过Mybatis的动态代理机制实现的。Mybatis会根据接口和对应的mapper的xml文件生成接口的实现类,并在需要访问数据库时调用mapper中的SQL语句进行操作。
MyBatis动态SQL
动态 SQL 只有几个基本元素,与 JSTL 或 XML 文本处理器相似,十分简单明了,大量的判断都可以在 MyBatis 的映射 XML 文件里配置,以达到许多需要大量代码才能实现的功能。
动态 SQL 大大减少了编写代码的工作量,更体现了 MyBatis 的灵活性、高度可配置性和可维护性。
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose(when、otherwise) | 相当于Java中的switch case语句 | 多条件分支判断 |
trim、where | 辅助元素 | 用于处理一些SQL拼装问题 |
foreach | 循环语句 | 在in语句等列举条件常用 |
bind | 辅助元素 | 拼接参数 |
where 标签
where 标签主要用来简化 SQL 语句中的条件判断,可以自动处理 AND/OR 条件
if 标签
MyBatis if 类似于 Java 中的 if 语句,是 MyBatis 中最常用的判断语句。使用 if 标签可以节省许多拼接 SQL 的工作,把精力集中在 XML 的维护上。
bind 标签
bind 标签可以通过 OGNL [对象导航图语言(Object Graph Navigation Language)]表达式自定义一个上下文变量。
<!-- resultType查出来的结果中的每一行都要映射成该类型的对象,写全称 -->
<select id="getStaff" resultType="com.easy.bean.Staff">
select * from staff
<!-- 根据参数不同组合出不同的SQL语句 动态SQL语句 标签 -->
<where>
<!-- 编写条件语句 如果where标签中有内容 会自动添加where关键字 -->
<if test="checktext !=null and checktest !=''">
<!-- 重新定义参数内容 -->
<bind name="liketext" value="'%'+checktext+'%'"/>
name like #{liketext}
</if>
</where>
</select>
foreach 标签
foreach 标签用于循环语句,它很好的支持了数组和 List、set 接口的集合,并对此提供遍历的功能。
foreach 标签主要有以下属性
- item:表示集合中每一个元素进行迭代时的别名。
- index:指定一个名字,表示在迭代过程中每次迭代到的位置。
- open:表示该语句以什么开始(既然是 in 条件语句,所以必然以(开始)。
- separator:表示在每次进行迭代之间以什么符号作为分隔符(既然是 in 条件语句,所以必然以,作为分隔符)
- close:表示该语句以什么结束(既然是 in 条件语句,所以必然以)结束)。
<insert id="addList">
insert into staff(code,name,salary,username,userpass)
values
<foreach collection="list" item="it" separator=",">
(#{it.code},#{it.name},#{it.salary},#{it.username},#{it.userpass})
</foreach>
</insert>
注意:
使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
- 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
- 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
- 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
choose、when和otherwise 标签
MyBatis 中动态语句 choose-when-otherwise 类似于 Java 中的 switch-case-default 语句。由于 MyBatis 并没有为 if 提供对应的 else 标签,如果想要达到<if>...<else>...</else> </if> 的效果,可以借助 <choose>、<when>、<otherwise> 来实现。
当判断条件中有字符串时,比较用双等号,里面的字符串用双引号,外面用单引号。
在xml文件中,尖括号<>有特殊含义,小于号用 < 替换,大于号用 > 替换
<select id="getStaffBySalary" resultType="com.easy.bean.Staff">
select * from staff
<where>
<!-- 参数名 salarytext -->
<choose>
<when test='salarytext == "低"'>
<!-- 用<表示小于号,用>表示大于号 -->
salary <= 5000
</when>
<when test='salarytext == "中"'>
salary >5000 and salary <=8000
</when>
<otherwise>
salary >8000
</otherwise>
</choose>
</where>
</select>
set 标签
在 Mybatis 中,update 语句可以使用 set 标签动态更新列。set 标签可以为 SQL 语句动态的添加 set 关键字,剔除追加到条件末尾多余的逗号。
<update id="editStaffItem">
update staff
<set>
<!-- set标签会去除多余的逗号 -->
<if test='name!=null and name!=""'>
name=#{name},
</if>
<if test="salary!=null">
salary=#{salary},
</if>
</set>
<where>
<!-- where标签会去除多余的and,or -->
id=#{id}
</where>
</update>
trim 标签
trim 一般用于去除 SQL 语句中多余的 AND 关键字、逗号,或者给 SQL 语句前拼接 where、set 等后缀,可用于选择性插入、更新、删除或者条件查询等操作。
这种效果和where相同,where本身已经可以做到去除多余的and,or的功能。
resultMap元素
resultMap 是 MyBatis 中最复杂的元素,主要用于解决实体类属性名与数据库表中字段名不一致的情况,可以将查询结果映射成实体对象。
为了使数据库的查询结果和返回值类型中的属性能够自动匹配,通常会对 MySQL 数据库和 JavaBean 采用同一套命名规则,即 Java 命名驼峰规则,这样就不需要再做映射了(数据库表字段名和属性名不一致时需要手动映射)。
一对一关联查询
通过 <resultMap> 元素的子元素 <association> 处理一对一级联关系
<association> 元素中通常使用以下属性。
- property:指定映射到实体类的对象属性。
- column:指定表中对应的字段(即查询返回的列名)
- javaType:指定映射到实体对象属性的类型。
- select:指定引入嵌套查询的子 SQL 语句,该属性用于关联映射中的嵌套查询。
查询员工及其部门,根据id将部门id子查询的结果映射到员工类Staff的部门属性dep上:
<resultMap id="staffAndDep" type="com.easy.bean.Staff">
<association column="dep_id" select="getStaffDep" property="dep"></association>
</resultMap>
<select id="getStaffDep" resultType="com.easy.bean.Department">
select * from department where id=#{dep_id}
</select>
<!-- 一对一查询或一对多查询需要指定映射方式 resultMap -->
<select id="getStaffAndDep" resultMap="staffAndDep">
select * from staff
</select>
控制类的方法:
@GetMapping("staff/dep")
public CommonResult getStaffAndDep() {
List<Staff> lists = dao.getStaffAndDep();
return CommonResult.success(lists);
}
数据访问层接口:
List<Staff> getStaffAndDep();
一对多关联查询
通过 <resultMap> 元素的子元素 <collection> 处理一对多级联关系,collection 可以将关联查询的多条记录映射到一个 list 集合属性中。
查询部门及其下的员工信息,根据id将查询结果映射到部门类的员工列表里:
<resultMap id="departmentAndStaff" type="com.easy.bean.Department">
<!-- <id column="id" property="depid"></id>
<result column="name" property="depname"></result> -->
<result column="id" property="id"></result>
<collection fetchType="lazy" column="id" select="getDepStaff" property="staffList"></collection>
</resultMap>
<select id="getDepStaff" resultType="com.easy.bean.Staff">
select * from staff where dep_id=#{id}
</select>
result 标签中的内容确保了id列被映射到Department对象的id属性上,如果result不指定id,系统会将id用于子查询的映射而不进行封装,导致最后id属性为默认值0
控制类方法:
@GetMapping("dep")
//@Transactional
public CommonResult getDep() {
List<Department> list=departmentDao.getDep();
return CommonResult.success(list);
}
数据访问层接口:
List<Department> getDep();
resultType和resultMap的区别:
MyBatis 的每一个查询映射的返回类型都是 resultMap,只是当我们提供的返回类型是 resultType 时,MyBatis 会自动把对应的值赋给 resultType 所指定对象的属性,而当我们提供的返回类型是 resultMap 时,MyBatis 会将数据库中的列数据复制到对象的相应属性上,可用于复制查询。
MyBatis缓存
MyBatis 提供了一级缓存和二级缓存的支持。默认情况下,MyBatis 只开启一级缓存
一级缓存
- 一级缓存是基于 PerpetualCache(MyBatis自带)的 HashMap 本地缓存,作用范围为 SQLession 域内。当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。
- 在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 mapper 的方法,往往只执行一次 SQL。因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,再次查询时,如果没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
- 在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用同一个 mapper 的方法,往往只执行一次 SQL。因为使用 SqlSession 第一次查询后,MyBatis 会将其放在缓存中,再次查询时,如果没有刷新,并且缓存没有超时的情况下,SqlSession 会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
@Transactional 注解是用于标记在方法或类上的注解,可以声明一个事务的边界。当方法或类被@Transactional注解标记时,Spring会自动为其创建一个事务,并在方法执行前开启事务、执行方法体、执行成功后提交事务,或在遇到异常时进行回滚。
@GetMapping("dep")
@Transactional
public CommonResult getDep() {
List<Department> list=departmentDao.getDep();
System.out.println("-------------");
list=departmentDao.getDep();
return CommonResult.success();
}
在启动类上使用 @EnableTransactionManagement 注解用于开启Spring的声明式事务管理。
@SpringBootApplication
@EnableTransactionManagement
public class EasySpringApplication {
public static void main(String[] args) {
SpringApplication.run(EasySpringApplication.class, args);
}
}
通过两个事务相关的注解确保两次方法调用在同一个SqlSession中,验证一级缓存:
通过日志可以看到,两次调用方法只执行了一次SQL语句。
一级缓存失效的四种情况:
1.sqlSession不同。
2.sqlSession相同,查询条件不同。因为缓存条件不同,缓存中还没有数据。
3.sqlSession相同,在两次相同查询条件中间执行过增删改操作。(因为中间的增删改可能对缓存中数据进行修改,所以不能用)
4.sqlSession相同,手动清空了一级缓存。
二级缓存
二级缓存是全局缓存,作用域超出 SQLsession 范围之外,可以被所有 SqlSession 共享。手动开启
在mapper的xml文件中使用以下语句开启二级缓存:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
二级缓存要求参与的对象都是可序列化的,实现Serializable接口。
测试代码:
@GetMapping("dep")
public CommonResult getDep() {
List<Department> list=departmentDao.getDep();
System.out.println("-------------");
list=departmentDao.getDep();
return CommonResult.success();
}
从日志可见:二级缓存已生效。
懒加载
MyBatis的懒加载是一种延迟加载机制,允许在需要时才加载关联对象的属性。默认情况下,MyBatis使用的是立即加载,即在执行查询语句时,同时加载关联对象的属性。但在某些情况下,如果查询结果中包含了大量的关联对象,且这些关联对象可能在业务逻辑中并不总是需要的,这时懒加载就能提高查询性能和减少内存消耗。
懒加载可以在以下两种场景中使用:
-
延迟加载关联对象:当你在查询对象时,并不立即加载关联对象的属性,只有在真正访问关联对象的属性时,才会执行额外的查询语句来加载关联对象的数据。
-
延迟加载集合属性:当你查询对象并包含有关联的集合属性时,仅在访问这些集合属性时才会执行额外的查询语句来加载集合属性中的数据。
懒加载在 collection 标签中设置属性 fetchType="lazy"
fetchType用于控制数据库查询时关联对象的加载时机和方式。它决定了是在查询主对象时立即加载关联对象(迫切加载),还是在访问关联对象时才从数据库中加载(懒加载)。
<resultMap id="departmentAndStaff" type="com.easy.bean.Department">
<result column="id" property="id"></result>
<collection fetchType="lazy" column="id" select="getDepStaff" property="staffList"></collection>
</resultMap>
<select id="getDepStaff" resultType="com.easy.bean.Staff">
select * from staff where dep_id=#{id}
</select>
<select id="getDep" resultMap="departmentAndStaff">
select * from department
</select>
测试代码:
@GetMapping("dep")
public CommonResult getDep() {
List<Department> list=departmentDao.getDep();
System.out.println("-------------");
System.out.println(list.get(0));
System.out.println(list.get(1));
return CommonResult.success();
}
从日志中可见:在方法刚调用时仅执行了 select * from department 并没有立即进行子查询,
当方法访问到集合元素 list.get(0) 和 list.get(1) 时,子查询语句才分别执行。
另外,如果Dao中接收两个以上的参数,就要使用 @Param 注解给参数起别名
//如果Dao中接收两个以上的参数,就要使用@Param注解给参数起别名
int edit(@Param("id")int id,@Param("staff")Staff staff);
@Insert("insert into staff value()")
int save(Staff staff);
也可以在接口中声明方法时直接使用注解引入SQL语句