Java从入门到精通二十一(Mybatis框架)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: Mybatis简化jdbc操作框架就是一个软件的半成品,许多实现的细节都已经实现。主要的功能就是可以帮助我们减少繁琐重复的操作。Mybatis就是用来简化JDBC的开发,简化一些繁琐的操作。之前我们在写jdbc的代码的时候就会需要进行获取连接对象,以及操作代码,自己封装结果集。如果我们的表的结构发生变化,基本很多很多的内容都需要我们去改,这是十分繁琐的事情,特别是当表比较多或者改的东西比较多的时候就显得很头疼。

Mybatis简化jdbc操作

框架就是一个软件的半成品,许多实现的细节都已经实现。主要的功能就是可以帮助我们减少繁琐重复的操作。

Mybatis就是用来简化JDBC的开发,简化一些繁琐的操作。


之前我们在写jdbc的代码的时候就会需要进行获取连接对象,以及操作代码,自己封装结果集。如果我们的表的结构发生变化,基本很多很多的内容都需要我们去改,这是十分繁琐的事情,特别是当表比较多或者改的东西比较多的时候就显得很头疼。



存在的问题就是硬编码和操作繁琐


注册驱动、获取连接 上图标1的代码有很多字符串,而这些是连接数据库的四个基本信息,以后如果要将Mysql数据库换成其他的关系型 数据库的话,这四个地方都需要修改,如果放在此处就意味着要修改我们的源代码。 SQL语句 上图标2的代码。如果表结构发生变化,SQL语句就要进行更改。这也不方便后期的维护。


操作繁琐 手动设置参数 手动封装结果集 上图标4的代码是对查询到的数据进行封装,而这部分代码是没有什么技术含量,而且特别耗费时间的。


Mybatis就是用来简化一些繁琐的操作的。



说明:图片来自黑马ppt,和上诉部分说明来自黑马(真的很细致)。


可能刚开始还没有深刻认识,因为使用的不是很频繁,也没有做过太大的项目。后面多用用就可以了。还有就是框架的配置可能看起来比较麻烦,但是这样的框架对于后续的维护是非常方便的。


使用Mybatis框架

首先我们创建一个表


create database mybatis;
use mybatis;
drop table if exists tb_user;
create table tb_user(
  id int primary key auto_increment,
  username varchar(20),
  password varchar(20),
  gender char(1),
  addr varchar(30)
);
INSERT INTO tb_user VALUES (1, 'zhangsan', '123', '男', '北京');
INSERT INTO tb_user VALUES (2, '李四', '234', '女', '天津');
INSERT INTO tb_user VALUES (3, '王五', '11', '男', '西安');


然后我们要使用Mybatis框架,我们就在Maven核心配置文件里面进行导入。因为我们我们需要用到数据库,肯定需要导入驱动。如果需要单元测试那么需要junit,如果需要日志的话,就需要logback。

这些可以详细了解,然后之后用到的时候就去用好了,比较简单。

在pom.xml文件里面需要包含以下。其他的默认。


<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- 添加logback-core依赖 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>


然后注意logback也需要在resource下面进行一下配置。



一定要注意结构层次。

内容


<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level]  %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>
    <logger name="jgdabc" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>
    <!--
      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>


关于logback以及junit我们后面说明。


然后我们需要一个Mybatis的核心配置文件



关键信息


<!--
    environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
    -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
            <!--注意数据库驱动格式,并不是统一的,还有就是数据库url规范,系统的时区和
            数据库的时区不一样也会报错,这些这里可以处理一下-->
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
<!--        这个是映射-->
    </mappers>


关于映射文件,需要我们创建一个,然后在和Mybatis文件中指定路径。



<?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">
<!--
    namespace:名称空间
-->
<!---->
<mapper namespace="test">
<!--resultType作为返回的类型,返回一个User类-->
    <select id="selectAll" resultType="jgdabc.User">
        select *
        from tb_user;
    </select>
    <select id="selectById" resultType="jgdabc.User">
--         select *
--         from tb_user where id = #{id};
    </select>
</mapper>


可以看到映射文件主要是封装了相关的sql操作语句。返回一个类型。返回的类型我们可以去创建相应的类型类。



package jgdabc;
public class User {
    private Integer id;
    private String username;
    private String password;
    private String gender;
  public Integer getId() {
    return id;
  }
  public void setId(Integer id) {
    this.id = id;
  }
  public String getUsername() {
    return username;
  }
  @Override
  public String toString() {
    return "User{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", gender='" + gender + '\'' +
            ", addr='" + addr + '\'' +
            '}';
  }
  public void setUsername(String username) {
    this.username = username;
  }
  public String getPassword() {
    return password;
  }
  public void setPassword(String password) {
    this.password = password;
  }
  public String getGender() {
    return gender;
  }
  public void setGender(String gender) {
    this.gender = gender;
  }
  public String getAddr() {
    return addr;
  }
  public void setAddr(String addr) {
    this.addr = addr;
  }
  private String addr;
}


这个User在测试类可以作为泛型

现在我们来看测试类


package jgdabc_;
import jgdabc.User;
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;
import java.util.List;
public class MybatisDemo {
    public static void main(String[] args) throws IOException {
//        加载mybatis的核心配置文件
        //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3. 执行sql
        List<User> users = sqlSession.selectList("test.selectAll");
        System.out.println(users);
        //4. 释放资源
        sqlSession.close();
    }
}


这些框架性的东西并不需要死记硬背,多用用就记住了。


我们运行一下。




使用Mapper代理


简单记录一下Mybatis里面的这个Mapper代理的使用方式。框架的东西就是这样,把好多细节都隐藏了,看到更多的就是配置文件。


基于二十一文的案例我们使用代理配置,


有三点要求

1:定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。

基于上次的不使用代理的模块我在复制一份,然后使用代理。首先,我们就定义这个接口。这个接口的路径正确放置就行,在main下的java下面就可以,至于在下面定义多级文件夹也没有关系2,怎样方便就怎样来。



之前我们是有一个UserMapper.xml的映射文件,现在我们定义一个同名的接口文件。

然后将接口和映射文件放在同一目录的话可以直接拖动,但是这样显得不太分明,我们将java路径下的java文件和resources下面的配置文件层次分明。


我们可以这样做。然后把UserMapper.xml拖到这个文件夹下面。



我的文件夹比较简单,不是很多级,如果你的是多级的话,在resources下面创建directory,多层级直接要用/分割。


需要注意到这个是相对类路径,也就是相对于main或者java这两个同名的文件的相对位置得到一致就可以认为放置好了。



2: 设置SQL映射文件的namespace属性为Mapper接口全限定名

全限定名就是类名全称,带包路径用点隔开



3: 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致 。


好我们接下来在接口中编写,很简单。我们要返回的是是一个集合List,然后类型是User,所以我们这样写。


package mapper;
import jgdabc.User;
import java.util.List;
public interface UserMapper {
    List<User> selectAll();
}


然后测试类


package jgdabc_;
import jgdabc.User;
import mapper.UserMapper;
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;
import java.util.List;
public class MybatisDemo {
    public static void main(String[] args) throws IOException {
//        加载mybatis的核心配置文件
        //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        使用·代理
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectAll();
        System.out.println(users);
        //3. 执行sql
//        List<User> users = sqlSession.selectList("test.selectAll");
//        System.out.println(users);
//        //4. 释放资源
          sqlSession.close();
    }
}


测试运行



还有一点就是如果我们使用这种代理的方式也就是Mapper接口的名称和sql映射文件的名称相同,在同一目录下,我们可以在核心配置文件中使用批量注册的方式,也就是包扫描。



自此,我们对这个Mybatis框架的第一遍尝试操作就到此。


配置文件实现CRUD

基于上次的代理开始进行配置文件实现CRUD,我们先完成一个查询的操作。我们这次所做的不同就是进一步采用简化的操作。让框架的2使用更加方便


首先创建一张表


-- 删除tb_brand表
drop table if exists tb_brand;
-- 创建tb_brand表
create table tb_brand
(
    -- id 主键
    id           int primary key auto_increment,
    -- 品牌名称
    brand_name   varchar(20),
    -- 企业名称
    company_name varchar(20),
    -- 排序字段
    ordered      int,
    -- 描述信息
    description  varchar(100),
    -- 状态:0:禁用  1:启用
    status       int
);
-- 添加数据
insert into tb_brand (brand_name, company_name, ordered, description, status)
values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
       ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
       ('小米', '小米科技有限公司', 50, 'are you ok', 1);
SELECT * FROM tb_brand;


然后创建实体类Brand


package jgdabc;
/**
 * 品牌
 *
 * alt + 鼠标左键:整列编辑
 *
 * 在实体类中,基本数据类型建议使用其对应的包装类型
 */
public class Brand {
    // id 主键
    private Integer id;
    // 品牌名称
    private String brandName;
    // 企业名称
    private String companyName;
    // 排序字段
    private Integer ordered;
    // 描述信息
    private String description;
    // 状态:0:禁用  1:启用
    private Integer status;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getBrandName() {
        return brandName;
    }
    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }
    public String getCompanyName() {
        return companyName;
    }
    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }
    public Integer getOrdered() {
        return ordered;
    }
    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public Integer getStatus() {
        return status;
    }
    public void setStatus(Integer status) {
        this.status = status;
    }
    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}


这回哦们需要一个BrandMapper映射文件(这个映射文件在后面对改变的事情做出说明)


<?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">
<!--
    namespace:名称空间
-->
<!---->
<mapper namespace="mapper.BrandMapper">
    <resultMap id="brandResultMap" type="brand">
<!--        不一样的写这里-->
<!--        id完成主键字段的映射
-->
<!--        result 完成一般字段的映射-->
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>
    <select id="selectAll" resultMap="brandResultMap">
        select * from tb_brand;
    </select>
<!--    数据库表的字段名称和实体类属性名称不一样,则不会自动封装数据,
起别名:对不一样的列明起别名,让别名和实体类的属性名一样
缺点就是每次都要定义一次别名,非常不方便-->
<!--    可以使用sql片段-->
<!--    不灵活-->
<!--    <sql id="brand_colum">-->
<!--        id,brand_name as brandName,company_name as companyName,ordered,description,status-->
<!--    </sql>-->
<!--    <select id="selectAll" resultType="jgdabc.Brand">-->
<!--#         select *-->
<!--#         from tb_brand;  ;-->
<!--        select-->
<!--            <include refid="brand_colum"></include>-->
<!--        from tb_brand;-->
<!--    </select>-->
</mapper>


映射文件改变了,那么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>
    <typeAliases>
        <package name="jgdabc"/>
<!--        给该包下的类起别名,默认为类名,这样在映射文件中写的时候返回类型直接是user就行(不区分大小写)-->
    </typeAliases>
    <!--
    environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
    -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--数据库连接信息-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///mybatis?serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/BrandMapper.xml"/>
<!--        也可以使用包扫描的方式加载映射文件-->
<!--        mapper/UserMapper.xml-->
<!--        前提是使用了代理
-->
<!--        <package name="mapper"/>-->
<!--        这个是映射-->
    </mappers>
</configuration>


然后其他的配置并没有大的改变

当然还需要一个接口


package mapper;
import jgdabc.Brand;
import java.util.List;
public interface BrandMapper {
   List<Brand> selectAll();
}


然后测试类


package jgdabc_;
import jgdabc.Brand;
import mapper.BrandMapper;
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;
import java.util.List;
public class MyBatisTest {
    public static void main(String[] args) throws IOException {
        testSelectAll();
    }
    public static void testSelectAll() throws IOException {
        //获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = brandMapper.selectAll();
        System.out.println(brands);
        //释放资源
        sqlSession.close();
    }
}


这种框架基本的配置也不会多写,以后改动的就是几句代码。所以不需要去死记。


现在用到一个插件 MyBatisX


MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。 主要功能 XML映射配置文件 和 接口方法 间相互跳转 根据接口方法生成 statement


安装方式就是可以去idea的插件商店进行安装,安装好后重启idea可以发现这样的图标




这个简化操作主要体现在这里,对于如果有多个接口或者是映射文件的时候这样的简化可以带给我们很大的方便。


存在的一个问题就是如果我们的数据库字段属性和java类体的字段属性不以言不放过的话,我们查询到的就只是空的数据。我们最终采用的解决办法。


<?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">
<!--
    namespace:名称空间
-->
<!---->
<mapper namespace="mapper.BrandMapper">
    <resultMap id="brandResultMap" type="brand">
<!--        不一样的写这里-->
<!--        id完成主键字段的映射
-->
<!--        result 完成一般字段的映射-->
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>
    <select id="selectAll" resultMap="brandResultMap">
        select * from tb_brand;
    </select>
<!--    数据库表的字段名称和实体类属性名称不一样,则不会自动封装数据,
起别名:对不一样的列明起别名,让别名和实体类的属性名一样
缺点就是每次都要定义一次别名,非常不方便-->
<!--    可以使用sql片段-->
<!--    不灵活-->
<!--    <sql id="brand_colum">-->
<!--        id,brand_name as brandName,company_name as companyName,ordered,description,status-->
<!--    </sql>-->
<!--    <select id="selectAll" resultType="jgdabc.Brand">-->
<!--#         select *-->
<!--#         from tb_brand;  ;-->
<!--        select-->
<!--            <include refid="brand_colum"></include>-->
<!--        from tb_brand;-->
<!--    </select>-->
</mapper>


如果你的表在数据库变了,那么还需要在核心配置文件更换一下数据库。



注释部分是我们尝试过的,我们最终尝试的就是红框里面的,可以对照上诉粘贴出来的代码。

我们采用的是resultMap方式完成了不一致的属性名和列名的映射。



根据id查询详情数据

先在接口中定义相关的方法


package mapper;
import jgdabc.Brand;
import java.util.List;
public interface BrandMapper {
//   List<Brand> selectAll();
   Brand selectByIdBrand(int id);
}


然后映射文件里面的sql语句



映射的sql语句


select *from tb_brand where id = #{id};


#{}是参数占位符


执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。

还有一种占位符是${},但是可能会引起sql注入的问题,所以使用#{}比较安全


然后主要的测试方法


package jgdabc_;
        import jgdabc.Brand;
        import mapper.BrandMapper;
        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;
        import java.util.List;
        import static jgdabc_.MyBatisTest.testSelectAll;
public class MyBatisTest01 {
    public static void main(String[] args) throws IOException {
        testSelectById();
    }
    public static void testSelectById() throws IOException {
        //接收参数
        int id =1;
        //获取SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        Brand brand = brandMapper.selectByIdBrand(id);
        System.out.println(brand);
        //释放资源
        sqlSession.close();
    }
}


在映射文件中有一些需要注意的转义字符。

比如我们要去id小于某个值得数据,那么在xml映射文件中是不允许这样直接输入小于号的。

可以这样输入大写的cd然后根据idea给出的提示打出输出的转义,然后在里面写小于号。



<select id="selectOnly" resultMap="brandResultMap">
        select  * from tb_brand where id <![CDATA[
        <#{id_1};
        ]]>
    </select>


在写这个id之前最好在接口中写入,然后可以让接口在这里自动生成这个id,然后根据我们的需要做出一些改动即可。然后还是一样的方式,在测试类中引入这个id,我们班可以认为它是方法。



框架就是这样,你可以认为它很死板,减少一些隐含的操作,但是在你使用它之前,没有基础过渡是绝对不行的。


多条件查询

散装参数用注解的方式(@param(“参数名称”))

使用 @Param(“参数名称”) 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位


在接口类中定义这样的方法

要求参数的名称和对应sql语句中参数的占位符名称一样


List<Brand> selectByCondition(@Param("status") int status, @Param("companyName") String
         companyName, @Param("brandName") String brandName);


然后映射文件这样


<!--    条件查询-->
    <select id="selectByCondition" resultMap="brandResultMap">
        select *
        from tb_brand
        where status = #{status}
        and company_name like #{companyName}
        and brand_name like #{brandName};
    </select>


在测试代码中


 

String companyName = "华为";
        String brandName  = "华为";
//        处理参数
        companyName = "%"+companyName+"%";
        brandName= "%"+brandName+"%";
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper =sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
        System.out.println(brands);



实体类封装参数


将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。

该方式要求在映射配置文件的SQL中使用 #{内

容} 时,里面的内容必须和实体类属性名保持一致。


先写这个方法


List<Brand> selectByCondition(Brand brand)


然后映射文件中的sql语句

这里 的sql语句不用变


<select id="selectByCondition" resultMap="brandResultMap">
        select *
        from tb_brand
        where status = #{status}
        and company_name like #{companyName}
        and brand_name like #{brandName};
    </select>


然后测试类


   

模糊查询
        String companyName = "华为";
        String brandName  = "华为";
//        处理参数
        companyName = "%"+companyName+"%";
        brandName= "%"+brandName+"%";
        //将参数封装对象
        Brand brand = new Brand();
        brand.setStatus(status);
        brand.setCompanyName(companyName);
        brand.setBrandName(brandName);
         String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = brandMapper.selectByCondition(brand);//传入brand对象
        System.out.println(brands);


很明显我们这种方式采用的是对象也就是实体类封装的方式



map集合

将多个参数封装到map集合中,将map集合作为接口的方法参数。

该方式要求在映射配置文件的SQL中使用 #{内容}

时,里面的内容必须和map集合中键的名称一致。


首先接口中的方法


List<Brand> selectByCondition(Map map)


然后映射文件中的sql语句还是不需要更改

测试类中需要的


 

int status = 1;
//        模糊查询
        String companyName = "华为";
        String brandName  = "华为";
//        处理参数
        companyName = "%"+companyName+"%";
        brandName= "%"+brandName+"%";
         Map map = new HashMap<>();
        map.put("status",status);
        map.put("companyName",companyName);
        map.put("brandName",brandName);
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = brandMapper.selectByCondition(map);
        System.out.println(brands);
        sqlSession.close();



这就是实现多条件查询的几种方式。


动态sql查询

多条件查询

用if标签

之前我们的多条件查询是在给出所有条件的基础上对数据进行查询。但是如果用户不是输入全部的条件的话,或者只是根据几个数据来匹配一些数据,那么就需要灵活多变。


我们可以用到if标签,我们先用if标签

在sql映射文件中我们这样写


<select id="selectByDynamicCondition" resultMap="brandResultMap">
        select *
        from tb_brand
            where
        <if test="status!=null">
            status = #{status}
        </if>
        <if test="companyName!= null and companyName !=''">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName!=''">
            and brand_name like #{brandName}
        </if>
    </select>


然后接口类中写入


List<Brand> selectByDynamicCondition(Map map);


在测试类中写入


     

int status = 1;
//        模糊查询
        String companyName = "华为";
        String brandName  = "华为";
//        处理参数
        companyName = "%"+companyName+"%";
        brandName= "%"+brandName+"%";
        //将参数封装对象
//        Brand brand = new Brand();
//        brand.setStatus(status);
//        brand.setCompanyName(companyName);
//        brand.setBrandName(brandName);
        //获取SqlSessionFactory
        Map map = new HashMap<>();
        map.put("status",status);
//        map.put("companyName",companyName);
        map.put("brandName",brandName);
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取Mapper接口代理对象
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        List<Brand> brands = brandMapper.selectByDynamicCondition(map);
        System.out.println(brands);


我们没有加入companyName,但是我们还是可以根据已有的条件得到结果。



我们最终在运行时自动拼接到的sql语句为


select * from tb_brand where # where 1=1 status = ? and brand_name like ?


但是需要注意的是,我们注意观察。如果我不添加status条件的话,是会出现问题。什么问题?现在我们不添加status条件然后运行一下,可以看看提示的信息。



截取下来就是


Preparing: select * from tb_brand where  and brand_name like ?


我们知道这样拼接后的结果就是一条错误的语句

所以我们不可能得到正确的结果。那么我们还有什么其他的方法解决这种问题


可以添加这么一句写成这样


<select id="selectByDynamicCondition" resultMap="brandResultMap">
        select *
        from tb_brand
        where 1=1
        <if test="status!=null">
            status = #{status}
        </if>
        <if test="companyName!= null and companyName !=''">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName!=''">
            and brand_name like #{brandName}
        </if>
    </select>


然后运行



你看这样运行拼接的sql语句


Preparing: select * from tb_brand where 1=1 and brand_name like ?


对的这样可以解决,但是存在的问题就是如果我的status再次添加上了,这条语句还是会出现问题,所以这样是不太灵活的。我们·尝试下面的方式


用where标签

这个标签比较只能,它的作用就是会动态的替换掉第一个条件前的and。

作用:

替换where关键字

会动态的去掉第一个条件前的 and

如果所有的参数没有值则不加where关键字

那么我们用这个标签的时候,可以给第一个条件加上and。来尝试使用。


--    </select>-->
    <select id="selectByDynamicCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    <where>
        <if test="status!=null">
            and status = #{status}
        </if>
        <if test="companyName!= null and companyName !=''">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName!=''">
            and brand_name like #{brandName}
        </if>
    </where>
    </select>


我们主要的问题出现在status这个上面。现在我们分别添加和不添加两种状态来运行代码。


测试代码中不添加status



测试代码添加status



看吧完美解决问题。


单条件查询

如果我只给出任意一个条件,当然上面的模式是完全可以满足的。我们只是来说明一下满足一个单条件查询的语法。


明着说就像switch case语句一样。

我们只在sql映射文件中做出说明更改的部分。其他的还是一样的模式。


<select id="selectByConditionSingle" resultMap="brandResultMap">
        select  *
        from tb_brand
        where
# choose相当于switch
# when相当于case
        <choose> <!--相当于switch-->
            <when test="status!=null">
                status = #{status}
            </when>
            <when test="companyName! = null and companyName != ''">
                company_name like {companyName}
            </when>
            <when test="brandName!=null and brandName != ''">
                brand_name like # {brandName}
            </when>
        </choose>
    </select>


这次把方法封装到brand对象了。这样也是可以的。

运行



在单条件查询里面,如果我一个条件都没有写。会发生什么?就上诉的代码段。

这里测试代码这里注释掉了。




如果都满足的话,我们还需要留有余地。不然会出现错误。用otherwise是一种办法


<select id="selectByConditionSingle" resultMap="brandResultMap">
        select  *
        from tb_brand
        where
# choose相当于switch
# when相当于case
        <choose> <!--相当于switch-->
            <when test="status!=null">
                status = #{status}
            </when>
            <when test="companyName! = null and companyName != ''">
                company_name like {companyName}
            </when>
            <when test="brandName!=null and brandName != ''">
                brand_name like # {brandName}
            </when>
            <otherwise>1=1</otherwise>
        </choose>
    </select>


这样的话,至少不会报错。如果你没有添加搜索数据的话,就不出现结果就行了。


然后其实我们还是可以用到智能的where标签


<select id="selectByConditionSingle" resultMap="brandResultMap">
        select  *
        from tb_brand
        <where>
            # when相当于case
            <choose> <!--相当于switch-->
                <when test="status!=null">
                    status = #{status}
                </when>
                <when test="companyName!= null and companyName != ''">
                    company_name like #{companyName}
                </when>
                <when test="brandName!= null and brandName != ''">
                    brand_name like #{brandName}
                </when>
            </choose>
        </where>
        # choose相当于switch
    </select>


where标签的智能之处在于可以帮助检查语法的问题。很明显,我让三个条件都没有匹配,然后这个标签会截断后面的语句。



添加数据并返回主键

添加数据就是常规操作了。我们需要返回主键的用途举个例子就是当用户提交订单的时候,我们需要返回一个订单号。(订单号作为主键)


void add(Brand brand);


然后映射文件


 

<insert id="add" useGeneratedKeys="true" keyProperty="id">
        insert into tb_brand (brand_name, company_name, ordered, description, status)
        values (#{brandName},#{companyName},#{ordered},#{description},#{status});
    </insert>


对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此时设置useGeneratedKeys参数值为true,在执行添加记录之后可以获取到数据库自动生成的主键ID

测试代码


public static void testAdd() throws IOException {
        int status = 1;
        String companyName = "波导手机";
        String brandName = "波导";
        String description = "手机中的战斗机";
        int orderd = 100;
        Brand brand = new Brand();
        brand.setStatus(status);
//        brand.setCompanyName(companyName);
//        brand.setDescription(description);
//        brand.setOrdered(orderd);
//        brand.setBrandName(brandName);
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);这样也可以设置为事务自动提交
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
        brandMapper.add(brand);
        Integer id = brand.getId();
        System.out.println(id);
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }


修改数据


之前我们主要是在查询方面做相关得操作。现在我们进行一个添加数据的操作。


先在接口中写一个方法


//       完成一个修改的功能
       int update(Brand brand);


然后再映射文件中编写具体的sql语句


<update id="update">
        update tb_brand
        <set>
            <if test="brandName != null and brandName != ''">
                brand_name = #{brandName},
            </if>
            <if test="companyName != null and companyName !=''">
                company_name = #{companyName},
            </if>
            <if test="ordered != null">
                ordered = #{ordered},
            </if>
            <if test="description != null and description != ''">
                description = #{description},
            </if>
            <if test="status !=null">
                status = #{status}
            </if>
        </set>
        where id = #{id}
    </update>


一个智能的set标签

当在 update 语句中使用if标签时,如果前面的if没有执行,则或导致逗号多余错误。使用set标签可以将动态的配置 SET 关键字,并剔除追加到条件末尾的任何不相关的逗号。使用 if+set 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值


然后测试代码


public static void testupdate() throws IOException {
        int status = 1;
        String companyName = "波导手机";
        String brandName = "波导";
        String description = "波导手机手机中的战斗机";
        int orderd = 200;
        int id = 5;
        Brand brand = new Brand();
        brand.setStatus(status);
        brand.setCompanyName(companyName);
        brand.setDescription(description);
        brand.setOrdered(orderd);
        brand.setBrandName(brandName);
        brand.setId(id);
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);这样也可以设置为事务自动提交
        BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
//        brandMapper.add(brand);
        int count = brandMapper.update(brand);
        //获取到影响的行数
//        Integer id = brand.getId();
//        System.out.println(id);
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }


如果我们不手动设置事务提交的话,那么是添加不成功的,默认的话,mybatis会默认为手动提交方式,如果不手动提交,mybatis会回滚事务。


根据id删除数据

首先我们在接口中定义一个这样的方法


void deleteById(int id);


然后在sql映射文件操作sql语句


<!--    根据id删除-->
    <delete id="deleteById">
        delete  from tb_brand where id = #{id};
    </delete>


然后在测试方法当中


public static void  testDeleteByID() throws IOException {
        int id = 7;
//        封装对象
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = build.openSession(true);
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        mapper.deleteById(id);
        sqlSession.close();
    }


批量删除数据

如果我们要一次性删除多个,动态批量删除数据怎么做呢?


还是在接口中先定义这个方法


void deleteByIds(@Param("ids") int[] ids);


我们传入一个id的数组


然后在映射文件当中的sql语句


<!--    批量删除-->
<!--    mybatis会将数组参数,封装为一个集合-->
<!--    默认:array=数组-->
<!--    可以使用Param注解来改变map集合当中的默认key的名称-->
<!--    separator代表指定分割符-->
    <delete id="deleteByIds">
       delete from tb_brand where id
        in
# open和close代表包围foreach的前后符号
           <foreach collection="ids" item="id" separator="," open="(" close=")">
                    #{id}
#             这个会根据集合中的id数动态生成多个id,id之间得1分割符为,号
           </foreach>
            ;
    </delete>


然后在测试类


public static  void testDeleteByIds() throws IOException {
        //接收参数
        int[] ids = {5,7,8};//删除的集合
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        mapper.deleteByIds(ids);
        sqlSession.commit();
        sqlSession.close();
    }


Mybatis的参数传递简述

简单说明一下参数传递的问题。其实之前的举例也有说明。


多个参数传递

就直接说,比如查询的操作。我们可以传递字段值,然后我们就可以锁定一个对象。


我们在接口当中写一个方法


Brand select(@Param("id") Integer id,@Param("brandName") String brandName);


我们这个叫@Param注释。以后多参传递的话就这样写,对于代码的阅读性比较好。


()里面的参数要和映射文件中的参数占位符名称一样。这个是要求。

然后像定义的其他的类型可以认为作为对测试代码的传入值的接收。


这样写后,我们看映射文件


<select id="select" resultMap="brandResultMap">
        select  * from tb_brand
        where id = #{id} and  brand_name = #{brandName};
    </select>


然后我们在测试类中这样写一个静态方法,静态方法就可以直接调用。


public static void selectMore() throws IOException {
        Integer id = 5;
        String brandName = "波导";
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        Brand brand = mapper.select(id, brandName);
        System.out.println(brand);
        sqlSession.close();
    }



单个参数的传递


对于单个参数的传递,我们在之前的例子也有遇到过。之前的根据id来得到数据就是典型的一个单个参数的传递。我们可以传入一个普通的java对象,以及集合等数据结构。


将数据封装到对象,传递。

详见目录多条件查询,就不再多说。


简单的注解实现CRUD

注解当然还是在接口中写上

定义好方法,然后在上面写上注解以及相关的sql语句。


@Select("select  * from tb_brand where id = #{id}")
   Brand selectByIdBrand(int id);


这样很简单,我们不需要再sql映射文件中写东西了。然后在测试代码中调用方法。


public  static void selectById() throws IOException {
        Integer  id =5;
        String resources = "mybatis-config.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        BrandMapper mapper = sqlSession.getMapper(BrandMapper.class);
        Brand brand = mapper.selectByIdBrand(id);
        System.out.println(brand);
        sqlSession.close();
    }


但是注解简单,但是只是对于简单的查询。有很多功能它完成不了。我们这个查询还会存在问题。之前我们提到过,当sql中的字段和和对象类类中属性不一致的时候,会返回空。之前我们做的事要么将字段重命名,要么就用一个标签。我们最终采用的标签类似于这样。


<resultMap id="brandResultMap" type="brand">
        <!--        不一样的写这里-->
        <!--        id完成主键字段的映射
        -->
        <!--        result 完成一般字段的映射-->
        <result column="brand_name" property="brandName"/>
        <result column="company_name" property="companyName"/>
    </resultMap>


然后我们可以在下面这样写


<select id="selectOnly" resultMap="brandResultMap">-->
    select *-->
    from tb_brand-->
    where id <![CDATA[-->
    < #{id_1};-->
    ]]>-->
</select>-->


但是你使用注解的来代替的话就不是很方便。另外对于大量的sql语句的话,就不适用了。


ok简单介绍这么多。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
14 3
|
1天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
13 6
|
1天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
11 4
|
1天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
9 3
|
3天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
11 5
|
1天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
7 2
|
3天前
|
存储 Java 数据处理
在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出
【10月更文挑战第14天】在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出。本文通过两个案例展示了Set的实用性和高效性:快速去重和高效查找。通过将列表转换为HashSet,可以轻松实现去重;而Set的contains方法则提供了快速的元素查找功能。这些特性使Set成为处理大量数据时的利器。
11 4
|
3天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。
【10月更文挑战第14天】从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。本文深入探讨Set的核心理念,并通过示例代码展示了HashSet和TreeSet的特点和应用场景。
8 2
|
3天前
|
存储 Java 索引
Java 中集合框架的常见接口和类
【10月更文挑战第13天】这些只是集合框架中的一部分常见接口和类,还有其他一些如 Queue、Deque 等接口以及相关的实现类。理解和掌握这些集合的特点和用法对于高效编程非常重要。
|
4天前
|
存储 Java
Java集合框架中的HashSet和TreeSet,解释了它们如何分别实现无序和有序存储。
【10月更文挑战第13天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解释了它们如何分别实现无序和有序存储。通过解析内部机制和示例代码,帮助读者理解这两种集合的特点和应用场景,从而更好地选择合适的集合类型满足实际需求。
15 3