MyBatis全篇

news2025/5/10 19:23:15

文章目录

  • 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;
	}
	
	@Override
	public 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 mapper
        PUBLIC "-//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();
    }
    //获取SqlSessionFactoryBuilder
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //获取sqlSessionFactory
    SqlSessionFactory build = builder.build(stream);
    //获取SqlSession
    SqlSession 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 configuration
        PUBLIC "-//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();
    }
    //获取SqlSessionFactoryBuilder
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    //获取sqlSessionFactory
    SqlSessionFactory build = builder.build(stream);
    //获取SqlSession
    SqlSession 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 configuration
        PUBLIC "-//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;
	}
	
	@Override
	public 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;
	}
	
	@Override
	public 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.coloradmin.cn/o/701512.html

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

相关文章

Java Web HTMLCSS(2)23.6.28

2&#xff0c;CSS 2.1 概述 CSS 是一门语言&#xff0c;用于控制网页表现。我们之前介绍过W3C标准。W3C标准规定了网页是由以下组成&#xff1a; 结构&#xff1a;HTML表现&#xff1a;CSS行为&#xff1a;JavaScript CSS也有一个专业的名字&#xff1a;Cascading Style Sh…

Springboot 如何自动上传秒杀商品数据到Redis中上架商品

一、概述 如下图秒杀活动&#xff1a; 在这个秒杀活动中&#xff0c;需要自动上架一定时间段的商品&#xff0c;我们如何实现自动上传呢&#xff1f; 我们可以通过定时任务来实现的。在秒杀活动开始前&#xff0c;需要将商品信息存储到数据库中&#xff0c;并设置好库存和价格…

Linux学习之进程的通信方式信号:kill命令

kill -l可以看到可以使用信号量。 把下边的内容使用编辑器&#xff0c;比如vim写到./a.sh。 #!/bin/bashecho $$ while : # 无限循环 do: donecat a.sh看一下文件里边的内容。 chmod ur,ux a.sh给当前用户赋予a.sh文件的写和执行权限。 在第一个端口里边&#xff0c;使用./a…

在 TypeScript 中有效地使用 keyof 和 typeof 来表示类型

在本文中&#xff0c;我们将学习如何通过组合类型运算符和枚举来提取和声明常量类型typeof&#xff0c;以使您的代码库得到优化。keyof 先决条件 为了获得更好的编码体验&#xff0c;您应该在 IDE 中安装 TypeScript&#xff0c;例如VSCode。它将为您提供许多基本功能&#xff…

Linux——进程通信之共享内存

目录 一. 回顾上文 二.共享内存 1.定义 2.特点&#xff1a; 3.实现步骤&#xff1a; 如下为成功链接共享内存使用权的完整步骤&#xff1a; 4.函数介绍 4.1shmget函数 4.1.2参数介绍 4.2ftok函数&#xff1a; 4.2.1参数介绍 关于ftok(); shmget();函数的代码实验…

基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用

查看原文>>>基于当量因子法、InVEST、SolVES模型等多技术融合在生态系统服务功能社会价值评估中的应用及论文写作、拓展分析 本文将讲述用于评估生态系统服务价值的当量因子法、InVEST模型、SolVES模型及其原理&#xff0c;您将学会三种模型的原理与运行方法&#xf…

基于Docker的JMeter分布式压测

目录 前言&#xff1a; Docker Docker在JMeter分布式测试中的作用 Dockerfile用于JMeter基础&#xff1a; Dockerfile for JMeter Server / Slave: 总结 前言&#xff1a; 基于Docker的JMeter分布式压测是一种将JMeter测试分布在多个容器中进行的方法&#xff0c;可以提高…

《计算机系统2》学习笔记

目录 计算机系统漫游 Amdahl定理 信息的表示和处理 信息存储 进制转化 小端法 大端法 布尔代数 位级运算 逻辑运算 移位运算 整数表示 无符号数编码 补码编码 有符号数和无符号数之间的转换 扩展数的位表示 截断数字 整数运算 无符号加法 无符号数求反 有…

信号链噪声分析5

目录 概要 整体架构流程 技术名词解释 技术细节 小结 概要 提示&#xff1a;这里可以添加技术概要 残余相位噪声测量法消除了外部噪声源&#xff08;例如电源或输入时钟&#xff09;的影响&#xff0c;而绝对相位 噪声测量法包含了这些来源的噪声。残余相位噪声装置可以隔离并…

Upload靶场通关笔记

文章目录 一、Pass-011.抓包上传2.获取上传路径3.工具验证 二、Pass-02三、Pass-031.使用httpd.conf自定义后缀2.提取上传文件名3.工具测试4.注意点四、Pass-041.上传.htaccess2.上传图片3.工具测试 五、Pass-05六、Pass-061.空格.号绕过2.工具测试 七、Pass-07八、Pass-081.特…

联想黄莹:6G将是全智能应用下连接虚拟与现实世界的“超级通道”

6月28日&#xff0c;以“时不我待”为主题的MWC上海世界移动通信大会正式开幕。在当天下午举办的“6G愿景及关键推动力”大会论坛上&#xff0c;联想集团副总裁、联想研究院5G实验室负责人黄莹博士发表了“共铸辉煌&#xff1a;对6G技术和应用的思考与展望”主题演讲。他认为&a…

STM32F407 GPIO口输出配置配置步骤

STM32F407ZGT6 是意法半导体&#xff08;STMicroelectronics&#xff09;公司推出的一款高性能ARM Cortex-M4核心的32位微控制器&#xff08;MCU&#xff09;。它是 STM32F4 系列的一员&#xff0c;具备强大的处理能力和丰富的外设功能&#xff0c;适用于各种应用领域。 【1】…

3.6.6.异步SIGIO : fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN) 3.6.7.存储映射IO

3.6.6.异步IO &#xff1a;SIGIO 3.6.6.1、何为异步IO (1)几乎可以认为&#xff1a;异步IO就是操作系统用软件实现的一套中断响应系统。 (2)异步IO的工作方法是&#xff1a;我们当前进程注册一个异步IO事件&#xff08;使用signal注册一个信号SIGIO的处理函数&#xff09;&…

【Django学习】(十)模型序列化器_关联字段序列化

这篇文章是针对模型类序列化器以及如何关联字段序列化 进行深入讲解的&#xff1b; class ProjectModelSerializer(serializers.ModelSerializer):email serializers.EmailField(write_onlyTrue)interfaces InterfaceModelSerializer(label所属接口的信息, help_text所属接口…

git常见操作汇总

存档&#xff0c;方便本人查询~ 除了add、commit、push、pull外有些操作也是需要掌握的~在复习之前先准备一下需要的项目&#xff1a; mkdir git-demo1 cd git-demo1 git init基础操作 # 在工作区新增一个README.md文件&#xff0c;并写入 # Hello World# 查看哪些原件做了修…

Java安全——安全管理器

Java安全 安全管理器 Java安全管理器是一个Java安全模型中的一个组件&#xff0c;主要的作用是提高Java应用程序的安全性。Java应用程序中的每个线程都会对安全管理器进行检查&#xff0c;在执行代码之前&#xff0c;会先经过安全管理器的核验&#xff0c;安全管理器根据代码来…

Flink SQL之Temporal Joins

1.Temporal Joins&#xff08;时态JOIN&#xff09; 时态表是一个随时间演变的表&#xff0c;在Flink中也称为动态表。 时态表中的行与一个或多个时态周期相关联&#xff0c;并且所有Flink表都是时态的&#xff08;动态的&#xff09;。时态表包含一个或多个版本化的表快照&a…

Oracle数据库中的包的介绍及示例

Oracle的包是一种封装存储过程&#xff0c;函数&#xff0c;变量和游标等代码对象的方法。包可以视为一组相关的程序单元&#xff0c;它们共享相同的命名空间和存储空间。包可以被看做是一个数据库程序库&#xff0c;它包含一个或多个程序单元&#xff0c;这些单元可以被视为一…

图片加载失败捕获上报及处理

图片加载失败捕获上报及处理 前端页面中加载最多的静态资源之一就是图片了&#xff0c;当出现图片加载失败时&#xff0c;非常影响用户体验。这时候我们就需要对图片是否成功加载进行判断&#xff0c;并对图片加载失败进行处理。 图片加载监听 单个捕获 HTML中的img标签可以…

vue 组件简单实例及传参交互

前言:vue 可以比较灵活的使用 html的片段&#xff0c;并将html的片段进行数据隔离&#xff0c;参数也可以互相传递&#xff0c;组件与组件之间也可以进行数据的交互 合理的使用组件可以避免重复代码或者很方便的调用第三方组件库 vue组件 简单实例组件传参实际应用父子组件交互…