mybatis笔记

一、入门操作

1.简介

  • MyBatis是一流的持久性框架

  • 支持自定义SQL,存储过程和高级映射。

  • MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。

  • MyBatis可以使用简单的XML或注释进行配置,并将图元,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。

2.导入mybatis

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

3.编写mybatis-config.xml配置文件

<?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="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    
  </mappers>
</configuration>

4.编写工具类,获取SqlSesstionFactoray

package com.qslinjing.util;

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.File;
import java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory = null;
    static {
        try {
            // 获取配置文件路径
            String resource = "com/qslinjing/mybatis-config.xml";
            // 获得配置文件输入流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 构建SqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
	
    // 获取SqlSession
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }

}

5. 创建pojo类(用于测试)

package com.qslinjing.pojo;

import com.qslinjing.util.DateUtil;

import java.util.Date;


public class Book {
    private int id; // 编号,唯一
    private String name; // 书名
    private double price; // 价格
    private String author; // 作者
    private String publisher; // 出版社
    private Date pubDate; // 出版日期
    private String isbn; // ISBN编号
    private long count; // 数量

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getPrice() {
        return price;
    }
    public void setPrice(double price) {
        this.price = price;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
    public String getPublisher() {
        return publisher;
    }
    public void setPublisher(String publisher) {
        this.publisher = publisher;
    }
    public Date getPublishDate() {
        return pubDate;
    }
    public void setPublishDate(Date pubDate) {
        this.pubDate = pubDate;
    }

    public void setPublishDate(String publishDate) {
        this.pubDate = DateUtil.str2date(publishDate, "yyyy-MM-dd");
    }
    public String getIsbn() {
        return isbn;
    }
    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }
    public long getCount() {
        return count;
    }
    public void setCount(long count) {
        this.count = count;
    }
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }
    @Override
    public String toString() {
        return "Book [id=" + id + ", name=" + name + ", price=" + price + ", author=" + author + ", publisher="
                + publisher + ", pubDate=" + DateUtil.date2str(pubDate) + ", isbn=" + isbn + ", count=" + count + "]";
    }
}

6. 创建Mapper实体类(接口)

package com.qslinjing.dao;

import com.qslinjing.pojo.Book;

import java.util.List;

public interface BookMapper {
    List<Book> queryAll();
}

7. 编写Mapper实体类的xml映射

<?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="com.qslinjing.dao.BookMapper" >
    <select id="queryAll" resultType="com.qslinjing.pojo.Book">
        select  * from book;
    </select>
</mapper>

8. 添加映射到mybatis-config.xml中

<mappers>
        <mapper resource="com/qslinjing/dao/BookMapper.xml"/>
</mappers>

9. 测试mybatis

package com.qslinjing.service;

import com.qslinjing.dao.BookMapper;
import com.qslinjing.pojo.Book;
import com.qslinjing.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class Test {
    public static void main(String[] args) {
        try(SqlSession sqlSession = MybatisUtil.getSqlSession()){
            BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
            List<Book> books = bookMapper.queryAll();
            for (Book book : books){
                    System.out.println(book);
            }
        }
    }
}

二、CRUD

1. namespace

  • 要与Mapper的类名一致

  • 两个xml文件不能使用同一个namespace

2.select

​ 使用示例:

 <select id="queryAll" resultType="com.qslinjing.pojo.User">
        select * from user
 </select>

​ 参数详解:

id:Mapper实体中对应的方法名
resultType:Mapper实体类对应方法的返回值
parameterType:Mapper实体类中对应方法的参数
parameterMap:传入参数的map集合(已弃用)
resultMap:返回的结果集合(已弃用)

3.update

<update id="update" parameterType="com.qslinjing.pojo.User">
    update user set uname=#{uname}, pwd = #{pwd} where id = #{id}
</update>

4 insert

<insert id="insert" parameterType="com.qslinjing.pojo.User">
    insert into user(id, uname, pwd) values (#{id}, #{uname}, #{pwd})
</insert>

5. delete

<delete id="delete" parameterType="int">
   delete from user where id = #{id}
</delete>

6.小结

  • 当传入参数只有一个且类型为基本类型,parameterType属性可以省略
  • 合理使用Map集合作为参数和返回值,可以提高效率,简化代码,使代码更加灵活
  • 执行增删改操作时必须手动提交事务
  • 编写SQL时可通过#{name|key}直接获取对象或Map集合中对应的值,获取对象值时,必须与对象中的成员变量同名;获取Map值时须与key值相同

三、XML配置

1.配置顺序

properties -> settings -> typeAliases -> typeHandlers -> objectFactory -> objectWrapperFactory -> reflectorFactory -> plugins -> environments -> databaseIdProvider -> mappers

2.引入外部properties配置

​ 外部配置 db.properties

jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=utf8
driver=com.mysql.jdbc.Driver
user=root
password=lin@Jing1208

​ 引入外部配置文件并使用

<?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="com/qslinjing/db.properties">
<!--        也可以在properties的子标签内添加属性,但是会被外部引入的同名属性覆盖-->
        <property name="user" value="test"/>
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!--                使用外部引入属性赋值-->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${user}"/>
<!--                带默认值的引入方式,若变量不存在,则使用默认值-->
<!--                <property name="username" value="${user}?:root"/>-->
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <properties></properties>
</configuration>

​ 注:若直接使用url作为properties中的key,可能会被环境中的url变量覆盖,导致数据库url找不到,改个名字即可

3.类型别名

​ 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如:

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

​ 当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

​ 通过包名指定别名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

​ 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名(实测windows环境下不区分大小写)。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

4.映射器

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等

通过resource映射(推荐使用)

<mappers>
    <mapper resource="com/qslinjing/dao/UserMapper.xml" />
</mappers>

通过class映射

<mappers>
   <mapper class="com.qslinjing.dao.UserMapper"/>
</mappers>

通过url映射(不推荐使用)

<mappers>
    <mapper url="file:///var/mappers/BlogMapper.xml"/>
</mappers>

通过包名引入

<mappers>
    <package name="com.qslinjing.dao"/>
</mappers>

注:通过包名和class映射必须保证Mapper和Mapper.xml文件在同一个包中,否则将找不到对应的xml文件

四、进阶知识

1.生命周期和作用域

  • 不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

  • 依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,因此可以直接忽略它们的生命周期

SqlSessionFactoryBuilder

  • 一旦创建了 SqlSessionFactory,就不再需要它了,最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory

  • 应用运行期间不要重复创建多次,最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。

2.结果集映射resultMap

  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • 可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系

简单结果集映射,不显示指定resultMap,默认采用hashMap

<select id="selectUsers" resultType="map">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select>

虽然在大部分情况下都够用,但是 HashMap 并不是一个很好的领域模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为领域模型。MyBatis 对两者都提供了支持。

在通常情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。

如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名(这是一个基本的 SQL 特性)来完成匹配。比如:

<!-- mybatis-config.xml 中 -->
<typeAlias type="com.someapp.model.User" alias="User"/>

<!-- SQL 映射 XML 中 -->
<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>

显式使用外部的 resultMap 也是解决列名不匹配的另外一种方式。

<resultMap id="userResultMap" type="User">
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

3.注解实现CRUD

使用注解绑定方法

  • 参数为基本类型是需要使用@param进行配置,参数为引用类型是不需要配置
  • 注解绑定多用于简单的SQL语句,对于复杂的SQL语句就显得力不从心了
package com.qslinjing.dao;

import com.qslinjing.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {
    @Select("select * from user")
    List<User> queryAll();
    @Select("select * from user where id = #{uid}")
    User queryById(@Param("uid") int id);
    @Delete("delete from user where id = #{id}")
    int delete(int id);
    @Update("update user set uname = #{uname}, pwd = #{pwd} where id = #{id}")
    int update(User user);
    @Insert("insert into user(id, uname, pwd) values(#{id}, #{uname}, #{pwd})")
    int insert(User user);
}

4.复杂查询结果集映射

多对一

​ Student实体类

package com.qslinjing.pojo;

public class Student {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @java.lang.Override
    public java.lang.String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

​ Teacher实体类

package com.qslinjing.pojo;

import java.util.List;

public class Teacher {
    private int id;
    private String name;
    private List<Student> students;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", stduents=" + students +
                '}';
    }
}

​ 基于多表连接查询

<!--    方式一-->
<resultMap id="StudentTeacher" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>
    <select id="queryAll" resultMap="StudentTeacher" >
        select s.id sid, tid, s.name sname, t.name tname from teacher t, student s where t.id = s.tid
    </select>

​ 基于嵌套查询

<!--    方式二-->
<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="queryAll" resultMap="StudentTeacher">
    select * from student;
</select>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{tid}
</select>

一对多

基于多表连接

<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
    </collection>
</resultMap>
<select id="queryAll" resultMap="TeacherStudent">
    select s.id sid, tid, s.name sname, t.name tname from student s, teacher t where s.tid = t.id;
</select>

小结

  1. 关联 association【多对一】

  2. 集合collection【一对多】

  3. javaType&ofType

    ​ 1.javaType 用来指定实体类中的类型信息

    2.ofType用来指定映射到集合中的Pojo类型,泛型中的约束类型
    

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中属性名和字段的问题、
  • 如果问题不好排查错误,可以使用日志,建议使用log4j

面试高频

  • mysql引擎
  • innoDb底层原理
  • 索引、索引优化

五、动态SQL

什么是动态SQL

简单的说:动态SQL就是指根据不同条件生成不同的SQL语句

官方解释:

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

常用标签

if
choose (when, otherwise)
trim (where, set)
foreach

if

使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。

如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

trim、where、set

前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。

MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

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

至此,我们已经完成了与 XML 配置及映射文件相关的讨论。下一章将详细探讨 Java API,以便你能充分利用已经创建的映射配置。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值