MyBatis全篇

文章目录

  • MyBatis
    • 特性
    • 下载
    • 持久化层技术对比
  • 搭建MyBatis
    • 创建maven工程
    • 创建MyBatis的核心配置文件
    • 创建mapper接口
    • 创建MyBatis的映射文件
    • 测试功能
    • 加入log4j日志功能
      • 加入log4j的配置文件
    • 核心配置文件的完善与详解
    • MyBatis的增删改查
      • 测试功能
  • MyBatis获取参数值
    • 在IDEA中设置中配置文件的模板
      • 操作步骤
      • 操作步骤
    • MyBatis获取参数值的两种方式(重点)
      • 获取单个字面量类型的参数
      • 获取多个字面量类型的参数
      • 获取map集合类型的参数
      • 获取实体类类型的参数
      • 使用@Param注解标识参数(常用)
  • MyBatis的各种查询功能
    • 查询单个实体类对象
    • 查询一个list集合
    • 查询单个数据
    • 查询一条数据为Map集合
    • 查询多条数据为Map集合
      • 用Map的list集合接收
      • 用@MapKey注解设置键(Key)
  • 特殊SQL的执行
    • 模糊查询
    • 批量删除
    • 动态设置表名
    • 添加功能获取自增的主键
  • 自定义映射resultMap
    • 多对一映射处理
      • 级联方式处理映射关系
      • 使用association处理映射关系
      • 分步查询
    • 一对多映射处理
      • 通过collection解决
      • 分步查询
  • 动态SQl
    • if标签
    • where标签
    • trim标签
    • choose、when、otherwise标签
    • foreach标签
      • 通过数组实现批量删除
      • 通过集合实现批量添加
    • sql标签
  • MyBatis的缓存
    • 一级缓存
      • 缓存失效
    • 二级缓存
      • 开启条件
      • 缓存失效
      • 相关设置
    • 缓存查询的顺序
    • 第三方缓存EHCache
      • 添加依赖

MyBatis

MyBatis最初是Apache的一个开源项目iBatis,2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下,iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

iBatis一词来源于"internet"和"abatis"的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQLMaps和Data Access Obiects(DAO)。

特性

  1. MyBatis是(支持定制化SQL、存储过程以及高级映射的优秀的)持久层框架(本质)
  2. MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
  4. MyBatis是一个半自动的ORM(Object Relation Mapping)框架

下载

MyBatis官网下载地址:https://github.com/mybatis/mybatis-3

打开链接之后往下拉,找到这个地方

image-20230330185202259

点进去之后,下载jar包(主体)

image-20230330185348085

下载完之后,其中有mybatis的jar包已经官方文档pdf(方便学习,后续会使用到)。

或者,如果嫌上面下载的方式太麻烦,则可以采用maven依赖项式的下载,将下面代码导入依赖配置中即可。

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version>
</dependency>

持久化层技术对比

JDBC

  • SQL夹杂在Java代码中耦合度高,导致硬编码(将代码写死)内伤
  • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
  • 代码冗长,开发效率低

Hibernate和JPA

  • 操作简便,开发效率高
  • 程序中的长难复杂 SQL 需要绕过框架
  • 内部自动生产的 SQL,不容易做特殊优化
  • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
  • 反射操作太多,导致数据库性能下降

MyBatis

  • 轻量级,性能出色
  • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
  • 开发效率稍逊于HIbernate

搭建MyBatis

首先创建一个空项目,然后再下面创建一个maven工程

创建maven工程

注意,这里的打包方式要是jar方式。

下面将引入依赖(pom.xml)

<dependencies><!-- Mybatis核心 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency><!-- junit测试 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope></dependency><!-- MySQL驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.32</version></dependency>
</dependencies>

依赖引入完成后,则需要配置一下MyBatis的核心配置文件

创建MyBatis的核心配置文件

在main包下的resources路径下创建一个mybatisConfig.xml文件,然后进行配置。

这时就需要用到mybatis中的官方文档了,将其中的的配置文件直接复制过来即可。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--设置连接数据库的环境--><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.jdbc.Driver"/><property name="url"value="jdbc:mysql://localhost:3306/MyBatis"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--引入映射文件--><mappers><mapper resource="mappers/UserMapper.xml"/></mappers>
</configuration>

这里用到了mysql中的账号和密码需要改成自己mysql上对应的账号和密码。

创建mapper接口

该接口相当于以前的DAO,但是mapper仅仅是一个接口,不需要提供实现类。

当然,在此之前,需要在mysql中创建上述配置中的数据库,并且创建一张user表,创建代码如下。

CREATE DATABASE MyBatis;USE Mybatis;CREATE TABLE `t_user`(`id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(20), `password` VARCHAR(20), `age` INT, `sex` CHAR, `email` VARCHAR(20), PRIMARY KEY (`id`) 
)ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;

接下来,创建User的实体类(POJO)

package code;/*** @创建人 HeXin* @所属包 User* @所属项目 MyBatis* @创建时间 2023/3/30 20:38* @描述 User实体类*/
public class User {private Integer id;private String username;private String password;private Integer age;private String sex;private String email;public User () {}public User (Integer id, String username, String password, Integer age, String sex, String email) {this.id = id;this.username = username;this.password = password;this.age = age;this.sex = sex;this.email = email;}public Integer getId () {return id;}public void setId (Integer id) {this.id = id;}public String getUsername () {return username;}public void setUsername (String username) {this.username = username;}public String getPassword () {return password;}public void setPassword (String password) {this.password = password;}public Integer getAge () {return age;}public void setAge (Integer age) {this.age = age;}public String getSex () {return sex;}public void setSex (String sex) {this.sex = sex;}public String getEmail () {return email;}public void setEmail (String email) {this.email = email;}@Overridepublic String toString () {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", age=" + age +", sex='" + sex + '\'' +", email='" + email + '\'' +'}';}
}

创建User的mapper接口,在里面定义了一个添加用户信息的方法

package code.mapper;/*** @创建人 HeXin* @所属包 UserMapper* @所属项目 MyBatis* @创建时间 2023/3/30 20:40* @描述 User类的Mapper接口*/
public interface UserMapper {/*** @Description: 添加用户名信息* @CreateTime: 2023/3/30 21:41* @Author: HeXin*/int addUser();
}

创建MyBatis的映射文件

这里要引入一个概念,对象关系映射(ORMObject Relationship Mapping)

其中的对象是指的实体类对象,关系是关系型数据库,映射为二者之间的对应关系。

Java概念数据库概念
属性列/字段
对象记录/行

在main中的resources中创建一个mappers软件包,并在此创建一个UserMapper.xml配置文件。

查询官方文档,找到关于映射文件的配置部分,将其拷贝过来,并在其中实现插入一条用户信息的sql。

<?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="code.mapper.UserMapper"><!--对应方法:int addUser();--><insert id="addUser">insert into t_user values(null,"xiaoming","123456",19,'男',"xiaoming@qq.com");</insert>
</mapper>

创建映射文件的注意事项(重要):

  1. 映射文件的命名规则:表所对应的实体类的类名+Mapper.xml(如:表为t_user,映射的实体类为User,因此所对应的映射文件为UserMapper.xml)。因此一个映射文件对应一个实体类,对应一张表的操作

MyBatis映射文件用于编写SQL,访问以及操作表中的数据。MyBatis映射文件存放的位置为src/main/resources/mappers目录下。

  1. MyBatis中可以面向接口操作数据,要保证两个一致:
    1. mapper接口的全类名和映射文件的命名空间(namespace)保持一致。
    2. mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致。

测试功能

在test包下创建TestMyBatis测试类,并实现测试方法

@Test
public void testMyBatis(){InputStream stream = null;try {//加载核心配置文件stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}//获取SqlSessionFactoryBuilderSqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//获取sqlSessionFactorySqlSessionFactory build = builder.build(stream);//获取SqlSessionSqlSession sqlSession = build.openSession();//获取mapper接口对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);//测试功能int i = mapper.addUser();//提交事务sqlSession.commit();System.out.println("受影响行数:"+i);
}

将代码中的一些参数进行解读

  • SqlSession:代表Java程序和数据库之间的会话。(就如同HttpSession是Java程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”。

其中存在着工厂模式(如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象)。

注意:当测试功能实现后一定要进行事务提交,否则数据库那边会没有数据显示,因为SqlSessionFactory的openSession方法中有一个autoCommit参数,默认为false,也就是默认不自动提交事务。

若需要自动提交事务,可将其参数值改为true(此时自己写的提交事务就可以注释掉了),当需要手动进行提交事务的时候,直接使用默认情况即可。

//获取SqlSession
SqlSession sqlSession = build.openSession(true);

加入log4j日志功能

首先要加入相对应依赖

<!-- log4j日志 -->
<dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version>
</dependency>

加入log4j的配置文件

创建一个名为log4j.xml的配置文件,其存放在main下的resources目录下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8" /><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /></layout></appender><logger name="java.sql"><level value="debug" /></logger><logger name="org.apache.ibatis"><level value="info" /></logger><root><level value="debug" /><appender-ref ref="STDOUT" /></root>
</log4j:configuration>

日记级别

FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

从左到右打印的内容越来越详细

核心配置文件的完善与详解

这里了解一下即可,现在使用时将其拷贝过去即可,以后在ssm整合的环境中是可以没有核心配置文件的,因为核心配置文件中的所有内容都可以交给spring处理。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//MyBatis.org//DTD Config 3.0//EN""http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration><!--引入properties文件,此时就可以${属性名}的方式访问属性值--><properties resource="jdbc.properties"></properties><settings><!--将表中字段的下划线自动转换为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/><!--开启延迟加载--><setting name="lazyLoadingEnabled" value="true"/></settings><typeAliases><!--typeAlias:设置某个具体的类型的别名属性:type:需要设置别名的类型的全类名alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小写若设置此属性,此时该类型的别名只能使用alias所设置的值每个实体类就需要设置一个typeAlias,比较麻烦,所以使用频率较低-->
<!--        <typeAlias type="code.User" alias="User"></typeAlias>--><!--以包为单位,设置该包下所有的类型都拥有默认的别名,即类名且不区分大小写--><package name="code"/></typeAliases><!--environments:设置多个连接数据库的环境属性:default:设置默认使用的环境的id--><environments default="mysql_test"><!--environment:设置具体的连接数据库的环境信息属性:id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境--><environment id="mysql_test"><!--transactionManager:设置事务管理方式属性:type:设置事务管理方式,type="JDBC|MANAGED"type="JDBC":设置当前环境的事务管理都必须手动处理type="MANAGED":设置事务被管理,例如spring中的AOP--><transactionManager type="JDBC"/><!--dataSource:设置数据源属性:type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建type="JNDI":调用上下文中的数据源--><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><!--引入映射文件--><mappers>
<!--        <mapper resource="mappers/UserMapper.xml"/>--><!--以包为单位,将包下所有的映射文件引入核心配置文件注意:此方式必须保证mapper接口和mapper映射文件必须在相同的包下--><package name="code.mappers"/></mappers>
</configuration>

到此,目前需要用到的核心配置文件已经配置完成,下面将开始MyBatis的CRUD功能(增删改查)

MyBatis的增删改查

添加

<!--int addUser();-->
<insert id="addUser">insert into t_user values(null,"xiaohong","123456",19,'女',"xiaohong@qq.com");
</insert>

删除

<!--void deleteUser()-->
<delete id="deleteUser">delete from t_user where id=4;
</delete>

修改

<!--void updateUser()-->
<update id="updateUser">update t_user set username = "lihua",sex = '男' where id=6;
</update>

查询

<!--查询功能标签必须设置resultType(设置默认映射关系)或resultMap(设置自定义映射关系)-->
<!--User getUserById()-->
<select id="getUserById" resultType="User">select * from t_user where id=5;
</select>
<!--List<User> getUsers()-->
<select id="getUsers" resultType="User">select * from t_user;
</select>

测试功能

@Test
public void testAdd(){InputStream stream = null;try {//加载核心配置文件stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}//获取SqlSessionFactoryBuilderSqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();//获取sqlSessionFactorySqlSessionFactory build = builder.build(stream);//获取SqlSessionSqlSession sqlSession = build.openSession(true);//获取mapper接口对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);//测试功能int i = mapper.addUser();System.out.println("受影响行数:"+i);
}
@Test
public void testUpdate(){InputStream stream = null;try {stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.updateUser();
}
@Test
public void testDelete(){InputStream stream = null;try {stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);mapper.deleteUser();
}
@Test
public void testSelect(){InputStream stream = null;try {stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);SqlSession sqlSession = build.openSession(true);UserMapper mapper = sqlSession.getMapper(UserMapper.class);//根据id查询用户信息System.out.println(mapper.getUserById());System.out.println("--------------------------------");//查询所有用户信息List<User> users = mapper.getUsers();users.forEach(user -> System.out.println(user));
}

到此,MyBatis最基础的框架与功能已经实现~


MyBatis获取参数值

MyBatis获取参数值有两种方式,一种是${},一种是#{}

${}的本质是使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,则需要手动加单引号。

#{}的本质是占位符赋值,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。

此时,新建一个模板,重新搭建一个MyBatis框架。因为其核心配置文件每次都去官方文档拷贝实在太麻烦,所以这里使用IDEA中的代码模板功能,后续需要核心配置文件和映射文件时直接可以一键生成。

在IDEA中设置中配置文件的模板

在idea中配置核心配置文件模板

操作步骤

第一步

image-20230331195839105

第二步

image-20230331200802373

将简单的模板代码拷贝进模板文件主体中

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//MyBatis.org//DTD Config 3.0//EN""http://MyBatis.org/dtd/MyBatis-3-config.dtd">
<configuration><properties resource="jdbc.properties"></properties><settings><setting name="mapUnderscoreToCamelCase" value="true"/><setting name="lazyLoadingEnabled" value="true"/></settings><typeAliases><package name=""/></typeAliases><environments default="mysql_test"><environment id="mysql_test"><transactionManager type="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><mappers><package name=""/></mappers>
</configuration>

在idea中配置映射文件模版

操作步骤

第一步与配置核心文件时一样,区别在于第二步

第二步

image-20230401095700184

MyBatis获取参数值的两种方式(重点)

获取单个字面量类型的参数

若mapper接口中的方法参数为单个的字面量类型 此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加单引号。

首先,因为每次测试时都需要创建的sqlsession,存在大量一样的代码,所以将其提取出来形成一个工具类,叫做SqlSessionUtils

package utils;import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;
import java.io.InputStream;/*** @创建人 HeXin* @所属包 SqlSessionUtils* @所属项目 MyBatis* @创建时间 2023/4/1 10:08* @描述 获取SqlSession的工具类*/
public class SqlSessionUtils {public static SqlSession getSqlSession(){InputStream stream = null;try {stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);SqlSession sqlSession = build.openSession(true);return sqlSession;}
}

下面将用代码实现这两种方式获取参数

#{}

查询代码

<!--User getUserByUsername(String username)-->
<select id="getUserByUsername" resultType="User">select * from t_user where username = #{username};
</select>

测试代码

@Test
public void testSingle(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserByUsername("xiaohong");System.out.println(user);
}

测试的结果:

image-20230401103750867

从测试的结果可以看出,#{}的确使用的?占位符赋值获取参数

${}

查询代码

<!--User getUserByUsername(String username)-->
<select id="getUserByUsername" resultType="User">select * from t_user where username = '${username}';
</select>

测试代码

@Test
public void testSingle(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserByUsername("xiaohong");System.out.println(user);
}

测试的结果:

image-20230401104034679

从测试结果可以看出,${}使用的是字符串拼接的方式进行赋值

值得注意的时,因为${}不会自动添加单引号,所以当使用${}时,一定要记得用单引号将其进行包围,否则就会报出一下异常

image-20230401104431103

获取多个字面量类型的参数

若mapper接口中的方法参数为多个时 此时MyBatis会自动将这些参数放在一个map集合中,以arg0,arg1…为键,以参数为值;以 param1,param2…为键,以参数为值;因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值。由于上面获取单个参数已经演示过${}和#{}的使用和区别,下面就演示其中一种。

代码演示

查询代码(错误方式)

<!--User login(String username, String password)-->
<select id="login" resultType="User">select * from t_user where username = #{username} and password = #{password};
</select>

测试代码

@Test
public void testLogin(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.login("xiaohong", "123456");System.out.println(user);
}

当有多个参数时,不能像单个参数一样直接使用形参名获取,否则会报异常

image-20230401105736615

上面的错误信息中不仅告诉了哪里出错,还给出了解决方案。

查询代码(正确方式)

<!--User login(String username, String password)-->
<select id="login" resultType="User">select * from t_user where username = #{arg0} and password = #{arg1};
</select>

测试结果

image-20230401110057384

获取map集合类型的参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在map中 只需要通过${}或#{}访问map集合的键就可以获取相对应的值。此方式由获取多参数方式演变而来,相当于自己设置键来访问数据。

代码实现

查询代码

<!--User login(Map<String,Object> map)-->
<select id="login" resultType="User">select * from t_user where username = #{username} and password = #{password};
</select>

测试代码

@Test
public void testLogin(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);Map<String,Object> map = new HashMap<>();map.put("username","xiaohong");map.put("password","123456");User user = mapper.login(map);System.out.println(user);
}

测试结果与获取多参数的结果一样

获取实体类类型的参数

若mapper接口中的方法参数为实体类对象时

此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值。

代码演示

向表中插入数据

<!--int addUser(User user)-->
<insert id="addUser">insert intot_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>

测试代码

@Test
public void testAdd(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);int i = mapper.addUser(new User(null, "xiaoming", "123456", 19, "男", "xiaoming@qq.com"));System.out.println(i);
}

测试结果

image-20230401112542454

使用@Param注解标识参数(常用)

此方式可以将第一种(获取单个参数)、第二种(获取多个参数)、第三种(获取map类型)方式整合成一种方式,都可以使用@Param进行实现获取参数。

此时,会将这些参数放在map集合中,以@Param注解的value属性值为键,以参数为值;以param1,param2…为键,以参数为值;只需要通过${}和#{}访问map集合的键就可以获取相对应的值。

代码演示

查询代码

<!--User login(@Param("username")String username, @Param("password")String password)-->
<select id="login" resultType="User">select * from t_user where username = #{username} and password = #{password};
</select>

测试代码

@Test
public void testLogin(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.login("xiaohong", "123456");System.out.println(user);
}

测试结果

image-20230401110057384

MyBatis的各种查询功能

共分为五种查询情况:查询单个实体类对象、查询一个list集合、查询单个数据、查询一条数据为map集合,查询多条数据为map集合。下面将逐个进行学习

查询单个实体类对象

这种查询情况已经实现过很多次了,这里就不多赘述,直接上代码

/*** @Description:根据id查询用户信息* @CreateTime: 2023/4/1 12:00* @Author: HeXin*/
User getUserById(@Param("id") Integer id);

查询语句

<!--User getUserById(Integer id);-->
<select id="getUserById" resultType="User">select * from t_user where id = #{id};
</select>

测试代码

@Test
public void testGetUser(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);User user = mapper.getUserById(5);System.out.println(user);
}

测试结果

image-20230401120856900

查询一个list集合

这种情况可以包含第一种情况,因为list集合中可以不包含任何数据,也可以只包含一条数据。

/*** @Description: 查询所有用户信息* @CreateTime: 2023/4/1 12:10* @Author: HeXin*/
List<User> getUsers();

查询语句

<!--List<User> getUsers();-->
<select id="getUsers" resultType="User">select * from t_user;
</select>

测试代码

@Test
public void testGetUsers(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);List<User> users = mapper.getUsers();users.forEach(user -> System.out.println(user));
}

测试结果

image-20230401121410042

查询单个数据

单个数据,一般是指单行单列的数据,如一张表上的总记录数、统计总共的金额等等。

下面将以查询用户信息的总记录数为例进行学习

代码演示

/*** @Description: 查询用户的总记录数* @CreateTime: 2023/4/1 14:48* @Author: HeXin*/
Integer getCount();

查询语句(MyBatis设置的有默认的类型别名,所以这里的resultType设置为全类名或别名都行。)

<!--Integer getCount();-->
<select id="getCount" resultType="Integer">select count(*) from t_user;
</select>

测试代码

@Test
public void testGetCount(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Integer count = mapper.getCount();System.out.println("总记录数:"+count);
}

测试结果为

image-20230401145315389

通过查找官方文档,我们可以得知,MyBatis设置的默认全类名有以下这些

image-20230401150258511

image-20230401150347067

查询一条数据为Map集合

就是将查询到的一条数据存储到Map集合中。

代码演示

/*** @Description: 根据id查询用户信息为一个map集合* @CreateTime: 2023/4/1 15:07* @Author: HeXin*/
Map<String,Object> getUserByIdToMap(@Param("id") Integer id);

查询语句

<!--Map<String,Object> getUserByIdToMap(@Param("id") Integer id);-->
<select id="getUserByIdToMap" resultType="map">select * from t_user where id = #{id};
</select>

测试代码

@Test
public void testGetUserToMap(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Map<String, Object> userMap = mapper.getUserByIdToMap(6);System.out.println(userMap);
}

测试结果

image-20230401151546549

查询多条数据为Map集合

将查询到的多个数据都存储在Map集合中。这种方式以后的使用频率是很高的,因为map可以转换成json对象然后将数据传递给前端。注意,这里的多条数据不能只用一个Map来接收,应该用一个Map的LIst集合来接收(或者用@MapKey设置一个键(Key),此时就可以将每条数据转换的Map集合作为值,以某个字段的值作为主键,放在同一个Map集合中),否则就会报异常(此异常与用一个参数接收多条数据时的异常一样)

image-20230401152903896

代码演示

用Map的list集合接收

/*** @Description: 查询所有用户信息为Map集合* @CreateTime: 2023/4/1 15:18* @Author: HeXin*/
List<Map<String,Object>> getUsersToMap();

查询语句

<!--Map<String,Object> getUsersToMap();-->
<select id="getUsersToMap" resultType="map">select * from t_user;
</select>

测试代码

@Test
public void testGetUsersToMap() {SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);List<Map<String, Object>> users = mapper.getUsersToMap();users.forEach(user-> System.out.println(user));
}

测试结果

image-20230401152803203

用@MapKey注解设置键(Key)

/*** @Description: 查询所有用户信息为Map集合* @CreateTime: 2023/4/1 15:18* @Author: HeXin*/
@MapKey("id")
Map<Integer,Object> getUsersToMap();

查询语句

<!--Map<String,Object> getUsersToMap();-->
<select id="getUsersToMap" resultType="map">select * from t_user;
</select>

测试代码

@Test
public void testGetUsersToMap() {SqlSession sqlSession = SqlSessionUtils.getSqlSession();SelectMapper mapper = sqlSession.getMapper(SelectMapper.class);Map<Integer, Object> users = mapper.getUsersToMap();users.forEach((id,user)-> System.out.println("key="+id+" "+user));
}

测试结果

image-20230401154926263

到此,MyBatis中的各种查询功能已经学习完毕。

特殊SQL的执行

模糊查询

这里就存在一个问题,因为#{}是占位符赋值,所以会将sql语句中的单引号部分内容替换成问号,并且会自动加上单引号,导致查询失败,所以这个时候有三种解决办法,下面进行学习

以根据用户名模糊查询为例

/*** @Description: 根据用户名进行模糊查询* @CreateTime: 2023/4/1 15:52* @Author: HeXin*/
List<User> getUserByLike(@Param("username")String username);
  1. 使用${}获取参数值
<!--List<User> getUserByLike(@Param("username")String username)-->
<select id="getUserByLike" resultType="User">select * from t_user where username like '%${username}%'
</select>
  1. 使用#{}与SQL中concat字符串拼接函数结合使用它
<!--List<User> getUserByLike(@Param("username")String username)-->
<select id="getUserByLike" resultType="User">select * from t_user where username like concat('%',#{username},'%')
</select>
  1. 使用"%“#{}”%"去拼接其中的内容(最常用的方式)
<!--List<User> getUserByLike(@Param("username")String username)-->
<select id="getUserByLike" resultType="User">select * from t_user where username like "%"#{username}"%"
</select>

测试代码

@Test
public void testGetUserByLike(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);List<User> user = mapper.getUserByLike("xiao");user.forEach(u-> System.out.println(u));
}

测试结果

image-20230401161001574

批量删除

/*** @Description: 批量删除* @CreateTime: 2023/4/1 16:11* @Author: HeXin*/
int deleteMore(@Param("ids") String ids);

SQL语句

<!--int deleteMore(@Param("ids"),String ids);-->
<delete id="deleteMore">delete from t_user where id in(${ids})
</delete>

测试代码

@Test
public void testDeleteMore(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);int i = mapper.deleteMore("5,6,7");System.out.println("受影响行数:"+i);
}

测试结果

image-20230401162816509

动态设置表名

根据表名来查询数据,当然,这里也应该使用${}来获取参数

/*** @Description: 查询指定表中的数据* @CreateTime: 2023/4/1 16:29* @Author: HeXin*/
List<User> getUserByTableName(@Param("tableName") String tableName);

查询语句

<!--List<User> getUserByTableName(@Param("tableName") String tableName);-->
<select id="getUserByTableName" resultType="User">select * from ${tableName};
</select>

测试代码

@Test
public void testGetUserByTableName(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);List<User> users = mapper.getUserByTableName("t_user");users.forEach(user-> System.out.println(user));
}

测试结果

image-20230401170922257

添加功能获取自增的主键

这是没有添加获取自增主键的添加用户信息打印结果,很显然,其id值为null

image-20230401172603506

因为增删改有统一的返回值是受影响的行数,
因此只能将获取的自增的主键放在传输的参数user对象的某个属性中。

/*** @Description: 添加用户信息* @CreateTime: 2023/4/1 17:14* @Author: HeXin*/
void addUser(User user);

添加语句

<!--void addUser();-->
<!--useGeneratedKeys:设置使用自增的主键keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数user对象的某个属性中-->
<insert id="addUser" useGeneratedKeys="true" keyProperty="id">insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email});
</insert>

测试代码

@Test
public void testAddUser(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();SQLMapper mapper = sqlSession.getMapper(SQLMapper.class);User user = new User(null,"WLM","1122334",18,"女","wlm@sina.com");mapper.addUser(user);System.out.println(user);
}

测试结果

image-20230401173110597

这次添加,其id值就不是null了。

自定义映射resultMap

resultMap:设置自定义映射

这里将会用到多对一或一对多的关系,所以需要再创建两张表。

CREATE TABLE `t_emp`( `eid` INT NOT NULL AUTO_INCREMENT, `emp_name` VARCHAR(20), `age` INT, `sex` CHAR, `email` VARCHAR(20),`did` INT,PRIMARY KEY (`eid`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;
CREATE TABLE `t_dept`( `did` INT NOT NULL AUTO_INCREMENT, `dept_name` VARCHAR(20), PRIMARY KEY (`did`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;

向表中插入数据

t_emp

INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`) 
VALUES ('1', 'xiaoming', '18', '男', 'xiaoming@qq.com', '1'); 
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`) 
VALUES ('2', 'xiaohong', '19', '女', 'xiaohong@qq.com', '1'); 
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`) 
VALUES ('3', 'lihua', '22', '男', 'lihua@sina.com', '2'); 
INSERT INTO `mybatis`.`t_emp` (`eid`, `emp_name`, `age`, `sex`, `email`, `did`) 
VALUES ('4', 'wangqiang', '21', '男', 'liuqiang@163.com', '3');

t_dept

INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('1', 'A'); 
INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('2', 'B'); 
INSERT INTO `mybatis`.`t_dept` (`did`, `dept_name`) VALUES ('3', 'C'); 

对应的,创建两个表的实体类

package POJO;/*** @创建人 HeXin* @所属包 Emp* @所属项目 MyBatis* @创建时间 2023/4/1 18:00* @描述*/
public class Emp {private Integer eid;private String empName;private Integer age;private String sex;private String email;private Dept dept;public Dept getDept () {return dept;}public void setDept (Dept dept) {this.dept = dept;}public Emp (Integer eid, String empName, Integer age, String sex, String email) {this.eid = eid;this.empName = empName;this.age = age;this.sex = sex;this.email = email;}public Emp () {}public Integer getEid () {return eid;}public void setEid (Integer eid) {this.eid = eid;}public String getEmpName () {return empName;}public void setEmpName (String empName) {this.empName = empName;}public Integer getAge () {return age;}public void setAge (Integer age) {this.age = age;}public String getSex () {return sex;}public void setSex (String sex) {this.sex = sex;}public String getEmail () {return email;}public void setEmail (String email) {this.email = email;}@Overridepublic String  toString () {return "Emp{" +"eid=" + eid +", empName='" + empName + '\'' +", age=" + age +", sex='" + sex + '\'' +", email='" + email + '\'' +", dept=" + dept +'}';}
}
package POJO;import java.util.List;/*** @创建人 HeXin* @所属包 Dept* @所属项目 MyBatis* @创建时间 2023/4/1 18:02* @描述*/
public class Dept {private Integer did;private String deptName;private List<Emp> emps;public List<Emp> getEmps () {return emps;}public void setEmps (List<Emp> emps) {this.emps = emps;}public Dept (Integer did, String deptName) {this.did = did;this.deptName = deptName;}public Dept () {}public Integer getDid () {return did;}public void setDid (Integer did) {this.did = did;}public String getDeptName () {return deptName;}public void setDeptName (String deptName) {this.deptName = deptName;}@Overridepublic String toString () {return "Dept{" +"did=" + did +", deptName='" + deptName + '\'' +", emps=" + emps +'}';}
}

若字段名与实体类中的属性名不一致,则通过查询语句查询数据的时候,是无法查到的,这是因为字段名有自己的风格要求(下划线),属性名也有风格要求(驼峰)。解决此问题共有三个办法:

  1. 给查询的时候给字段名起别名,将别名设置为驼峰风格的。(实现起来比较麻烦,尤其是当字段名很多的时候,非常消耗时间)。

  2. 通过设置MyBatis核心配置文件,设置全局变量,将下划线自动映射为驼峰。该配置已经在完善核心配置文件时已经涉及到了。

    <!--设置全局变量-->
    <settings><!--将表中字段的下划线自动转换为驼峰--><setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    
  3. 通过resultMap设置自定义的映射关系

    <!--resultMap:设置自定义映射属性:id:表示自定义映射的唯一标识type:查询的数据要映射的实体类的类型子标签:id:设置主键的映射关系result:设置普通字段的映射关系association:设置多对一的映射关系collection:设置一对多的映射关系属性:property:设置映射关系中实体类中的属性名column:设置映射关系中表中的字段名-->
    <resultMap id="userMap" type="User"><id property="id" column="id"></id><result property="userName" column="user_name"></result><result property="password" column="password"></result><result property="age" column="age"></result><result property="sex" column="sex"></result>
    </resultMap>
    <!--List<Emp> getEmps-->
    <select id="getEmps" resultMap="userMap">select * from t_emp;
    </select>
    

多对一映射处理

一共有三种处理方式。通过查询员工信息及其对应部门信息为例。

/*** @Description: 查询员工及其对应部门信息* @CreateTime: 2023/4/2 10:33* @Author: HeXin*/
Emp getEmpAndDept(@Param("eid") Integer eid);

级联方式处理映射关系

查询语句

<resultMap id="empAndDeptResultMap" type="Emp"><id property="eid" column="eid"></id><result property="empName" column="emp_name"></result><result property="age" column="age"></result><result property="sex" column="sex"></result><result property="email" column="email"></result><result property="dept.did" column="did"></result><result property="dept.deptName" column="dept_name"></result>
</resultMap>
<!--Emp getEmpAndDept(@Param("eid") Integer eid);-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>

使用association处理映射关系

查询语句

<resultMap id="empAndDeptResultMap" type="Emp"><id property="eid" column="eid"></id><result property="empName" column="emp_name"/><result property="age" column="age"></result><result property="sex" column="sex"></result><result property="email" column="email"></result><!--association:处理多对一的映射关系property:需要处理多对的映射关系的属性名javaType:该属性的类型--><association property="dept" javaType="Dept"><id property="did" column="did"></id><result property="deptName" column="dept_name"/></association>
</resultMap>
<!--Emp getEmpAndDept(@Param("eid") Integer eid);-->
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid};
</select>

分步查询

先查询员工的信息

/*** @Description: 查询员工信息* @CreateTime: 2023/4/2 11:32* @Author: HeXin*/
Emp getEmpByStep(@Param("eid") int eid);

查询语句

<resultMap id="empDeptStepMap" type="Emp"><id column="eid" property="eid"></id><result column="empName" property="emp_name"></result><result column="age" property="age"></result><result column="sex" property="sex"></result><result column="email" property="email"></result><!--select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId或mapper接口的全类名.方法名)column:将sql以及查询结果中的某个字段设置为分步查询的条件--><association property="dept"select="mappers.DeptMapper.getEmpDeptByStep" 					 					 column="did"></association>
</resultMap>
<!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">select * from t_emp where eid = #{eid}
</select>

再根据员工所对应的部门id查询部门信息

/*** @Description: 查询员工信息* @CreateTime: 2023/4/2 11:32* @Author: HeXin*/
Dept getEmpDeptByStep(@Param("did") int did);

查询语句

<!--Dept getEmpDeptByStep(@Param("did") int did);-->
<select id="getEmpDeptByStep" resultType="Dept">select * from t_dept where did = #{did}
</select>

测试代码

@Test
public void testGetEmpAndDept(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);Emp emp = mapper.getEmpAndDept(1);System.out.println(emp);
}

测试结果

image-20230402120839913

分布查询的好处是延迟加载(懒加载,对某种信息推迟加载,这样的技术也就帮助我们实现了 “按需查询” 的机制),而延迟加载默认在MyBatis中不开启的。

如果需要开启延迟加载,则需要在核心配置文件的全局设置中设置这两个属性

  • lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载(默认是关闭的:false)
  • aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(默认是开启的:true)
<!--设置MyBatis全局配置-->
<settings><!--开启延迟加载--><setting name="lazyLoadingEnabled" value="true"/>
</settings>

当开启延迟加载之后,可以通过修改association中的fetchType属性的值来手动控制延迟加载的效果

  • fetchType=“lazy”:延迟加载
  • fetchType=“eager”:立即加载

一对多映射处理

通过查询部门及其员工信息为例。

/*** @Description:获取部门及其员工信息* @CreateTime: 2023/4/2 11:54* @Author: HeXin*/
Dept getDeptAndEmp(@Param("did") Integer did);

通过collection解决

<resultMap id="deptAndEmpResultMap" type="Dept"><id property="did" column="did"/><result property="deptName" column="dept_name"/><!--
collection:处理一对多的映射关系
ofType:表示该属性所对应的集合中存储数据的类型
--><collection property="emps" ofType="Emp"><id property="eid" column="eid"/><result property="empName" column="emp_name"/><result property="age" column="age"/><result property="sex" column="sex"/><result property="email" column="email"/></collection>
</resultMap>
<!--Dept getDeptAndEmp(@Param("id") Integer id);-->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did};
</select>

分步查询

第一步

先根据did查询部门信息

/*** @Description: 分布查询第一步:查询部门信息* @CreateTime: 2023/4/2 14:52* @Author: HeXin*/
Dept getDeptAndEmpByStepOne(@Param("did") Integer did);

查询语句

<resultMap id="deptAndEmpByStepOneResultMap" type="Dept"><id property="did" column="did"/><result property="deptName" column="dept_name"/><collection property="emps"select="mappers.EmpMapper.getDeptAndEmpByStepTwo"column="did"fetchType="lazy"></collection>
</resultMap>
<!--Dept getDeptAndEmpByStepOne(@Param("did") Integer did);-->
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepOneResultMap">select * from t_dept where did = #{did};
</select>

第二步

根据did查询员工信息

/*** @Description: 根据did查询员工信息* @CreateTime: 2023/4/2 11:54* @Author: HeXin*/
List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);

查询语句

<!--List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">select * from t_emp where did = #{did};
</select>

测试代码

@Test
public void testGetDeptAndEmpByStep(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);Dept dept = mapper.getDeptAndEmpByStepOne(1);System.out.println("部门名称:"+dept.getDeptName());System.out.println("部门id:"+dept.getDid());List<Emp> emps = dept.getEmps();System.out.println("部门员工:");emps.forEach(emp -> System.out.println(emp));
}

测试结果

image-20230402151651389

从运行的日志中可以发现,当代码只查询部门的id和名称的时候,只执行了第一句sql语句。而当查询员工信息的时候,两条sql语句才同时执行。

动态SQl

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

下面以根据多条件查询用户信息为例进行演示学习。

/*** @Description: 多条件查询* @CreateTime: 2023/4/2 15:23* @Author: HeXin*/
List<Emp> getEmpByCondition(Emp emp);

测试代码

@Test
public void testGetEmpByCondition(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);List<Emp> emps = mapper.getEmpByCondition(new Emp(null,"",19,"女","xiaohong@qq.com"));emps.forEach(emp-> System.out.println(emp));
}

if标签

使用的是xml中的if标签进行sql语句的拼接来解决这个问题。

查询语句

<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">select * from t_emp where true<if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age !=''">and age = #{age}</if><if test="age != null and age !=''">and sex = #{sex}</if><if test="age != null and age !=''">and email = #{email}</if>
</select>

where后面加上的true(或者恒等式)是为了防止某些条件不成立之后,where后面直接接上and导致sql语句出错,加上之后也不会影响查询结果

image-20230402154520756

如上面报错信息,就是因为empName为空字符串,导致where直接与and相连而报错。所以在使用if标签的时候要格外注意这个问题,但下面的where标签就能很好的解决这个问题

where标签

where标签会自动识别是否需要生成where关键字,并且也会自动屏蔽掉内容前多余的and

<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">select * from t_emp<where><if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age !=''">and age = #{age}</if><if test="age != null and age !=''">and sex = #{sex}</if><if test="age != null and age !=''">and email = #{email}</if></where>
</select>

按照上面的情况,执行结果会报错,因为empName为空且未加恒等式导致where与and直接相连。

真的是这样吗?看一下运行结果

image-20230402155432877

结果没有报错,而是直接将员工的信息顺利输出,通过观察日志可以发现,sql’语句中自动添加了where,并且去除了内容前多余的and。

当然,这里只是自动生成where,如果将查询的所有条件都变成null或者空字符串,结果又会怎样呢?image-20230402155816723

观察运行结果和日志发现,这次的sql语句中没有where关键字,所以sql语句就变成了查询全部员工信息。

注意:where标签不能将其中内容后面多余的and或or去除,只能将内容前多余的and或or去除

trim标签

trim标签中包含四个属性:

  • prefix:将trim标签中内容前面添加指定内容
  • suffix:将trim标签中内容后面添加指定内容
  • prefixOverrides:将trim标签中内容前面去除指定内容
  • suffixOverrides::将trim标签中内容后面去除指定内容

由此可以看出,该标签能很好解决where与and的兼容问题

<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">select * from t_emp<trim prefix="where" prefixOverrides="and|or"><if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age !=''">and age = #{age}</if><if test="age != null and age !=''">and sex = #{sex}</if><if test="age != null and age !=''">and email = #{email}</if></trim>
</select>

这里只给出了内容前面加and或or关键字。同理,如果在内容后面加上and或or关键字,则将prefixOverrides改为suffixOverrides即可。

choose、when、otherwise标签

这里看起来是三个标签,实则是一套标签,需要组合使用,其作用相当于Java中的if…else…

when标签至少有一个,而otherwise标签之多只能有一个

<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">select * from t_emp<where><choose><when test="empName != null and empName != ''">emp_name = #{empName}</when><when test="age != null and age != ''">age = #{age}</when><when test="sex != null and sex != ''">sex = #{sex}</when><when test="empName != null and empName != ''">email = #{email}</when><otherwise>did = 2</otherwise></choose></where>
</select>

从代码中看出,其内容中并没有加and,因为该标签如果有一个choose条件成立,则其他条件就会被忽略,所以sql语句中有且最多只有一个成立条件,所以不需要加and关键字

foreach标签

foreach标签相当于Java中的for循环,下面将以批量删除和批量添加为例进行演示

foreach标签中有五个常用的属性:

  • collection:表示的是循环体(集合或数组)
  • item:循环体中的个体
  • separator:循环个体之间以separator设置的属性值值分开
  • open:foreach标签所循环的所有内容的开始符,循环从open设置的属性值开始。
  • close:foreach标签所循环的所有内容的结束符,循环从close设置的属性值结束。

通过数组实现批量删除

其中,批量删除有两种写法,一种是id in (删除的id集合或数组),另一种是id = id or id…

/*** @Description: 通过数组实现批量删除* @CreateTime: 2023/4/2 16:33* @Author: HeXin*/
int deleteMoreArray(@Param("eids") Integer[] eids);

删除语句

第一种

<!--int deleteMoreArray(@Param("eids") Integer[] eids);-->
<delete id="deleteMoreArray">delete from t_emp where eid in<foreach collection="eids" item="eid" separator="," open="(" close=")">#{eid}</foreach>
</delete>

第二种

<!--int deleteMoreArray(@Param("eids") Integer[] eids);-->
<delete id="deleteMoreArray">delete from t_emp where<foreach collection="eids" item="eid" separator="or">eid = #{eid}</foreach>
</delete>

通过集合实现批量添加

/*** @Description:通过集合实现批量添加* @CreateTime: 2023/4/2 16:55* @Author: HeXin*/
int insertMoreByList(@Param("emps") List<Emp> emps);

插入语句

<!--int insertMoreByList(@Param("emps") List<Emp> emps);-->
<insert id="insertMoreByList">insert into t_emp values<foreach collection="emps" item="emp" separator=",">(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},1)</foreach>
</insert>

sql标签

sql标签叫做sql片段,可以将常用的sql片段进行记录,后续需要使用的时候,直接引入使用即可。

接着使用trim标签,根据多条件查询用户信息为例继续演示

查询的方法还是一样,这里就不赘述了。

查询语句

<sql id="empColumns">eid,emp_name,sex,email,dept</sql>
<!--List<Emp> getEmpByCondition(Emp emp);-->
<select id="getEmpByCondition" resultType="Emp">select <include refid="empColumns"/> from t_emp<trim prefix="where" prefixOverrides="and|or"><if test="empName != null and empName != ''">emp_name = #{empName}</if><if test="age != null and age !=''">and age = #{age}</if><if test="age != null and age !=''">and sex = #{sex}</if><if test="age != null and age !=''">and email = #{email}</if></trim>
</select>

引用时使用到了include标签,其中的refid属性表示的是引用sql片段的id


MyBatis的缓存

缓存是指,将当前查询出来的数据进行一个记录,等到下一次再来查询相同的数据时,会直接去缓存中去拿取,而不再去数据库中拿去。这样的好处时,提高了查询的效率,当然,坏处就是,当在服务器中修改了这些数据中的内容,如果不及时更新或删除缓存,会导致再次查询时出现数据的修改的情况不能及时响应。

MyBatis中的缓存分为一级缓存和二级缓存,其中一级缓存是默认开启的。两者的级别不同,也就是两者的范围不一样。

一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。

下面以根据员工id查询员工信息为例

/*** @Description: 根据员工id获取员工信息* @CreateTime: 2023/4/2 20:56* @Author: HeXin*/
Emp getEmpById(@Param("eid") Integer eid);

查询语句

<!--Emp getEmpById(@Param("eid") Integer eid);-->
<select id="getEmpById" resultType="Emp">select * from t_emp where eid = #{eid};
</select>

下面将通过不同的测试代码来测试其范围:

测试代码1

@Test
public void testGetEmpById(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();//第一次获取员工信息CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);Emp emp = mapper.getEmpById(2);System.out.println(emp);//第二次获取员工信息Emp emp1 = mapper.getEmpById(2);System.out.println(emp1);
}

运行结果

image-20230402210717006

从运行结果中可以看到,虽然我们用同一个mapper生成了两个相同的员工信息时,但是SQL语句只执行了一次。

测试代码2

@Test
public void testGetEmpById(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();//第一次获取员工信息CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);Emp emp = mapper.getEmpById(2);System.out.println(emp);//第二次获取员工信息CacheMapper mapper1 = sqlSession.getMapper(CacheMapper.class);Emp emp1 = mapper1.getEmpById(2);System.out.println(emp1);
}

运行结果

image-20230402210926908

当使用不同的mapper生成两个相同的员工信息时,但SQL语句仍然只执行了一次。

测试代码3

@Test
public void testGetEmpById(){//第一次获取员工信息SqlSession sqlSession = SqlSessionUtils.getSqlSession();CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);Emp emp = mapper.getEmpById(2);System.out.println(emp);//第二次获取员工信息SqlSession sqlSession1 = SqlSessionUtils.getSqlSession();CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);Emp emp1 = mapper1.getEmpById(2);System.out.println(emp1);
}

运行结果

image-20230402211203963

当使用不同的SqlSession生成不同的mapper而创建的两个相同的员工数据时,SQL语句执行了两次。

通过上面的三个测试样例,可以得知,一级缓存的范围是在SqlSession内的,只要是同一个SqlSession,在查询相同数据时都会从缓存中拿取数据。

缓存失效

存在使一级缓存失效的四种情况:

  1. 不同的SqlSession对应不同的一级缓存
  2. 同一个SqlSesion但是其查询条件不同
  3. 同一个SqlSession两次查询期间执行了任意一次增删改操作
  4. 同一个SqlSession两次查询期间手动清空了缓存(清空缓存方法:SqlSession.clearCache())

二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。二次缓存需要手动开启,且二级缓存的范围比一级缓存大。

开启条件

  • 在核心配置文件中,设置全局配置属性cacheEnabled=“true”(默认是true,不需要设置)
  • 在映射文件中设置标签<cache/>
  • 二级缓存必须在SqlSession关闭(SqlSession.close()方法)或提交(SqlSession.commit()方法)之后有效
  • 查询的数据所转换的实体类类型必须实现序列化的接口

代码演示

准备工作:Emp完善实现Serializable的接口,在映射文件中加入<cache/>标签。(开启二级缓存)

测试代码

@Test
public void testTwoCache(){InputStream stream = null;try {stream = Resources.getResourceAsStream("mybatisConfig.xml");} catch (IOException e) {e.printStackTrace();}SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);//第一次查询SqlSession sqlSession = build.openSession(true);CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);System.out.println(mapper.getEmpById(1));sqlSession.close();//第二次查询SqlSession sqlSession1 = build.openSession(true);CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);System.out.println(mapper1.getEmpById(1));sqlSession1.close();}

测试结果image-20230402214745387

要像实现此效果,一定要按照开启条件正确开启二级缓存。

缓存失效

两次查询之间执行任意一次的增删改,会使一级和二级缓存同时失效。

相关设置

在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性(默认的是 LRU):缓存回收策略 LRU(Least Recently Used)(最近最少使用的:移除最长时间不被使用的对象)。
    • FIFO(First in First out)(先进先出:按对象进入缓存的顺序来移除它们)。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象 。
  • flushInterval属性:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
  • size属性:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出。
  • readOnly属性:只读,true/false
    • true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
    • false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

缓存查询的顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以直接拿来使用
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. SqlSession关闭之后,一级缓存中的数据会写入到二级缓存中

第三方缓存EHCache

添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version>
</dependency>

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

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

相关文章

《TCP/IP网络编程》第3,4章学习记录

基础知识&#xff1a; struct sockaddr_in {sa_family_t sin_family; //地址族&#xff08;Address Family)uint16_t sin_port; //16位TCP/UDP端口号struct in_addr sin_addr; //32位IP地址char sin_zero[8]; //不使用 }sa_family_t包括&#xff1a; (1)AF_INET,IPv4网络协议…

Linux宝塔Mysql读写分离配置,两台服务器,服务器存在多个库

Linux宝塔Mysql读写分离配置&#xff0c;两台服务器&#xff0c;服务器存在多个库 一、主库操作 #登录数据库&#xff0c;用root登录方便&#xff0c;用其他账号会提示权限不足&#xff0c;需要登录root给予权限 mysql -u root -p 密码#创建一个账号&#xff0c;供从库用该账…

大屏项目也不难

项目环境搭建 使用create-vue初始化项目 npm init vuelatest准备utils模块 业务背景&#xff1a;大屏项目属于后台项目的一个子项目&#xff0c;用户的token是共享的 后台项目 - token - cookie 大屏项目要以同样的方式把token获取到&#xff0c;然后拼接到axios的请求头中…

网络编程 socket

目录 网络编程 套接字&#xff08;socket&#xff09;1. 认识端口号2. TCP协议3. UDP协议4. 网络字节序列5. 常见的套接字6. socket编程接口6.1 socket常见APIsocket函数recvfrom函数sendto函数read函数 从tcp socket中读取接收数据 6.2 sockaddr结构6.3 地址转换函数6.4 udp s…

JVM内存结构—— 程序计数器,虚拟机栈 解析

JVM的内存结构 1. 程序计数器(PC Register )寄存器 1.1 全称:Program Counter Register 1.2 作用 首先,java源代码 被 编译成 二进制的 字节码 (jvm指令) jvm跨平台就是这一套指令,linux 下,windows下指令都是一致的 指令 经过 解释器 把每一条指令 解释成 机器码…

SpringBoot项目从0到1配置logback日志打印

大家好&#xff01;我是sum墨&#xff0c;一个一线的底层码农&#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文&#xff0c;限于本人水平&#xff0c;如果文章和代码有表述不当之处&#xff0c;还请不吝赐教。 以下是正文&#xff01; 一、写文背景 我们在写后端…

安卓进度条:ProgressBar和Seekbar

一、ProgressBar进度条介绍 ProgressBar 是 Android 中的一个进度条控件&#xff0c;用于显示正在进行的任务的进度。它可以以水平或圆形的形式展示进度&#xff0c;并提供了多种样式和属性来满足不同的需求。 相关属性&#xff1a; android:progress&#xff1a;设置进度条的…

计数排序

计数排序 排序步骤 1、以最大值和最小值的差值加一为长度创建一个新数组 2、将索引为0对应最小值&#xff0c;索引为1对应最小值1&#xff0c;索引为2对应最小值2&#xff0c;以此类推&#xff0c;将索引对应最小值到最大值之间所有的值 3、遍历一遍&#xff0c;遇到一个数字…

计算机网络 - http协议 与 https协议(2)

前言 本篇介绍了构造http请求的的五种方式&#xff0c;简单的使用postman构造http请求&#xff0c;进一步了解https, 学习https的加密过程&#xff0c;了解对称密钥与非对称密钥对于加密是如何进行的&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流…

云计算相关概念

文章目录 一、云计算的三种部署模式&#xff1a;公有云、私有云、混合云--区别和特性二、华为云&#xff1a;简介、主要业务、特点和优势、不同场景和行业中的应用三、华为云-三剑客&#xff1a;IaaS、PaaS、SaaS 一、云计算的三种部署模式&#xff1a;公有云、私有云、混合云–…

webpack插件安装

webpack插件安装 1、html-webpack-plugin插件2 、css-loader和style-loader插件3、less-load插件 1、html-webpack-plugin插件 1、下载插件 yarn add html-webpack-plugin -D2、webpack.config.js添加配置 *const HtmlWebpackPlugin require(html-webpack-plugin); const p…

SpringBoot+JWT实现单点登录解决方案

一、什么是单点登录? 单点登录是一种统一认证和授权机制&#xff0c;指在多个应用系统中&#xff0c;用户只需要登录一次就可以访问所有相互信任的系统&#xff0c;不需要重新登录验证。 单点登录一般用于互相授信的系统&#xff0c;实现单一位置登录&#xff0c;其他信任的…

机器学习技术(五)——特征工程与模型评估

机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(2️⃣) 文章目录 机器学习技术&#xff08;五&#xff09;——特征工程与模型评估(:two:)二、模型评估1、Accuracy score2、Confusion matrix混淆矩阵1、多值2、二值 3、Hamming loss4、Precision, recall and F…

深度学习笔记之Transformer(八)Transformer模型架构基本介绍

机器学习笔记之Transformer——Transformer模型架构基本介绍 引言回顾&#xff1a;简单理解&#xff1a; Seq2seq \text{Seq2seq} Seq2seq模型架构与自编码器自注意力机制 Transformer \text{Transformer} Transformer架构关于架构的简单认识多头注意力机制包含掩码的多头注意力…

Elasticsearch【全文检索、倒排索引、应用场景、对比Solr、数据结构】(一)-全面详解(学习总结---从入门到深化)

目录 Elasticsearch介绍_全文检索 Elasticsearch介绍_倒排索引 Elasticsearch介绍_Elasticsearch的出现 Elasticsearch介绍_Elasticsearch应用场景 Elasticsearch介绍_Elasticsearch对比Solr Elasticsearch介绍_Elasticsearch数据结构 Elasticsearch介绍_全文检索 Elasti…

libvirt 热迁移流程及参数介绍

01 热迁移基本原理 1.1 热迁移概念 热迁移也叫在线迁移&#xff0c;是指虚拟机在开机状态下&#xff0c;且不影响虚拟机内部业务正常运行的情况下&#xff0c;从一台宿主机迁移到另外一台宿主机上的过程。 1.2 虚拟机数据传输预拷贝和后拷贝 预拷贝(pre-copy)&#xff1a; …

Windows如何恢复已删除的Word文档?

案例&#xff1a;可以恢复已删除的Word文档吗&#xff1f; “大家好&#xff0c;我遇到了一个问题&#xff0c;需要大家的帮助。昨天我编辑了一个Word文档并保存到了桌面上&#xff0c;但当我今天再次打开电脑时&#xff0c;它就不见了&#xff01;昨天工作完成后&#xff…

Sentinel 规则详解

Sentinel 规则 流控规则 flow1、QPS流控2、并发线程数流控3、流控模式4、流控效果 熔断&#xff08;降级&#xff09;规则 degrade1、慢调用比例2、异常比例3、异常数 热点规则 param-flow授权规则 authority1、应用场景2、自定义来源3、授权规则配置 系统规则 前言&#xff1a…

JMeter进行WebSocket压力测试

背景 之前两篇内容介绍了一下 WebSocket 和 SocketIO 的基础内容。之后用 Netty-SocketIO 开发了一个简单的服务端&#xff0c;支持服务端主动向客户端发送消息&#xff0c;同时也支持客户端请求&#xff0c;服务端响应方式。本文主要想了解一下服务端的性能怎么样&#xff0c;…

4.6.tensorRT基础(1)-实际模型上onnx文件的各种操作

目录 前言1. onnx1.1 读取节点1.2 修改节点1.3 替换节点1.4 删除节点1.5 修改input和output1.6 预处理的接入 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#…