【MyBatis】| Dynamic SQL (important)

Table of contents

One: Dynamic SQL

1. if tag

2. where tag

3. trim label

4. set label

5. choose when otherwise

6. foreach tag

7. SQL tags and include tags (understand)


One: Dynamic SQL

Some business scenarios also require dynamic splicing of SQL statements, for example:

① Batch delete  

delete from t_car where id in(1,2,3,4,5,6,....这⾥的值是动态的,根据⽤户选择的id不同,值是不同的);

② Multi-condition query

select * from t_car where brand like '丰⽥%' and guide_price > 30 and .....;

Requirements: multi-condition query

Possible conditions include: brand (brand), guide price (guide_price), car type (car_type), etc.

1. if tag

One of the three brothers: CarMapper interface, write method

There are multiple parameters, use the @Param annotation to define variables to enhance readability!

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 多条件查询,使用if
    List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
}

The second of three brothers: CarMapper.xml file, write sql statement

Using the if tag, when the condition is met, the SQL statement will be spliced:

The test attribute in the if tag is required. The test attribute is generally an expression, and the corresponding value is true or false.

②The test attribute can use:

First: When the @Param annotation is used , the parameter name specified by the @Param annotation must appear in the test ;

Second: When the @Param annotation is not used , what appears in the test is: arg0, arg1 or param1, param2....

Third: When POJO is used , the attributes of the POJO class appear in the test ;

③In the dynamic SQL of MyBatis, if multiple conditions are met at the same time, && cannot be used, only and can be used.

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <select id="selectByMultiCondition" resultType="Car">
        select * from t_car where
        <if test="brand != null and brand != ''">
            brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice !=''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </select>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

①Assuming that the three parameters are not empty, the result of the query is completely fine!

②Assuming that the three parameters are all empty, there will be problems , and the original SQL statement becomes: select * from t_car where , which is not grammatical! what to do? Add a constant condition 1=1 after where

③Assume that the first condition is not empty, and the last two are empty , that is, the first SQL statement is spelled directly, and problems will also occur: select * from t_car where 1==1 brand like "%" ? "%" , an and is needed between the two conditions as a connection, so the first statement should also be preceded by and

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testSelectByMultiCondition(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 假设三个条件都不为空
        List<Car> cars = mapper.selectByMultiCondition("比亚迪",2.0,"新能源");
        // 假设三个条件都为空
        List<Car> cars = mapper.selectByMultiCondition("",null,"");
        // 假设第一个不为空,后两个为空
        List<Car> cars = mapper.selectByMultiCondition("比亚迪",null,"");

        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
}

So the final SQL statement is modified to: add 1=1 and and conditions

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <select id="selectByMultiCondition" resultType="Car">
        select * from t_car where 1=1
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice !=''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </select>
</mapper>

2. where tag

The role of the where tag: make the where clause more dynamic and intelligent 

① When all the conditions are empty, the where tag guarantees that no where clause will be generated.

② Automatically remove redundant and or or in front of certain conditions.

 One of the three brothers: CarMapper interface, write method

The structure of the whole method remains the same, just change the method name

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 使用where标签,让where子句更加的智能
    List<Car> selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
}

The second of three brothers: CarMapper.xml file, write sql statement

Use the where tag to process the where clause specifically, so we can write SQL statements according to the original idea

Note: If you write and after the statement, you can’t remove it, you can only remove the redundant and in front , for example: brand like "%"#{brand}"%" and

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <select id="selectByMultiConditionWithWhere" resultType="Car">
        select * from t_car
        <where>
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%"
            </if>
            <if test="guidePrice != null and guidePrice !=''">
                and guide_price > #{guidePrice}
            </if>
            <if test="carType != null and carType != ''">
                and car_type = #{carType}
            </if>
        </where>
    </select>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

①Assuming that all three are empty, the where tag will not generate a where clause;

②Assuming that the first one is empty and the latter two are not empty, then the second clause with and will be spliced, and the where tag will automatically remove the redundant and;

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testSelectByMultiConditionWithWhere(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 三个都为空
        List<Car> cars = mapper.selectByMultiCondition("",null,"");
        // 假设第一个为空,后两个不为空
        List<Car> cars = mapper.selectByMultiCondition("",2.0,"新能源");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
}

3. trim label

Attributes of the trim tag :

①prefix: Add content before the statement in the trim tag (prefix)

②suffix: Add content after the statement in the trim tag (add suffix)

③prefixOverrides: prefix override (remove prefix)

④ suffixOverrides: suffix override (remove suffix)

 One of the three brothers: CarMapper interface, write method

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 使用trim标签
    List<Car> selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
}

The second of three brothers: CarMapper.xml file, write sql statement

Instead of using the where tag, use the prefix and suffixOverrides attributes to complete the query operation, and we can also complete the operation by putting the and in the back, for example:

①prefix="where", means adding where in front of all the contents of the trim tag

②suffixOverrides="and|or", means to remove the suffix and or or of the content in the trim tag

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <select id="selectByMultiConditionWithTrim" resultType="Car">
        select * from t_car
        <trim prefix="where" suffixOverrides="and|or">
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%" and
            </if>
            <if test="guidePrice != null and guidePrice !=''">
                guide_price > #{guidePrice} and
            </if>
            <if test="carType != null and carType != ''">
                car_type = #{carType}
            </if>
        </trim>
    </select>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

①The three are all empty, and there is no problem at all. Adding where to the prefix is ​​not just added casually. You need to judge whether there are clauses first, and then add where if there are clauses

②The first one is not empty, and the rest are all empty, so that the suffix of the where clause has an additional and. We defined suffixOverrides="and|or" earlier, and the suffix and or or in the trim tag is removed. no problem at all

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testSelectByMultiConditionWithTrim(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 三个都不为空
        List<Car> cars = mapper.selectByMultiConditionWithTrim("", null, "");
        // 第一个不为空,其它都为空
        List<Car> cars = mapper.selectByMultiConditionWithTrim("比亚迪", null, "");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
}

4. set label

It is mainly used in the update statement to generate the set keyword, and remove the last redundant "," at the same time

For example: we only update the submitted fields that are not empty, if the submitted data is empty or "", then we will not update this field.

(1) The test is updated using the original method

 One of the three brothers: CarMapper interface, write method

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 通过id进行更新
    int updateById(Car car);
}

The second of three brothers: CarMapper.xml file, write sql statement

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <update id="updateById">
        update t_car set
            car_num = #{carNum},
            brand = #{brand},
            guide_price = #{guidePrice},
            produce_time = #{produceTime},
            car_type = #{carType}
        where
            id = #{id}
    </update>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testUpdateById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(1L, null, null, null, null, "新能源");
        int count = mapper.updateById(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
}

Problem: The original existing data, but we did not pass in the data when updating, it will update the original stored data to empty!

(2) Use the set tag to update

 One of the three brothers: CarMapper interface, write method

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 通过set标签进行更新
    int updateBySet(Car car);
}

The second of three brothers: CarMapper.xml file, write sql statement

The set tag here actually has two functions:

①Complete the update of the data. The fields without assignment will not be updated, and only the fields whose submitted data is not empty will be updated.

②Delete the extra comma, for example: the last field car_type is empty, so there will be an extra comma, and the set tag can remove the extra comma

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <update id="updateBySet">
        update t_car
        <set>
          <if test="carNum != null and carNum !=''">car_num = #{carNum},</if>
          <if test="brand != null and brand !=''">brand = #{brand},</if>
          <if test="guidePrice != null and guidePrice !=''">guide_price = #{guidePrice},</if>
          <if test="produceTime != null and produceTime !=''">produce_time = #{produceTime},</if>
          <if test="carType != null and carType !=''">car_type = #{carType}</if>
        </set>
        where
          id = #{id}
    </update>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testUpdateBySet(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(2L, null, "奔驰C200", null, null, "新能源");
        int count = mapper.updateBySet(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
}

The update result is as follows, if the submitted data is empty or "", this field will not be updated

5. choose when otherwise

These three tags are used together, and the syntax is as follows:

<choose>
     <when></when>
     <when></when>
     <when></when>
     <otherwise></otherwise>
</choose>

 is equivalent to:

if(){
 
}else if(){
 
}else if(){
 
}else if(){
 
}else{
}

Only one branch will be selected!

Requirement: Query based on the brand first, if no brand is provided, then query according to the guide price, if no guide price is provided, then query according to the production date.

 One of the three brothers: CarMapper interface, write method

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 根据chose when otherwise
    List<Car> selectByChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);
}

The second of three brothers: CarMapper.xml file, write sql statement

Three conditions: In fact, it is one of three choices. As long as one of them is executed, the other branches will not be executed; so there is no need to add and

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <select id="selectByChoose" resultType="Car">
        select * form t_car
        <where>
            <choose>
                <when test="brand != null and brand !=''">
                    brand like "%"#{brand}"%"
                </when>
                <when test="guidePrice != null and guidePrice !=''">
                    guide_price > #{guidePrice}
                </when>
                <otherwise>
                    car_type = #{carType}
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

① When the three conditions are not empty, query according to the first condition; when the previous condition is empty, query in the following order

②The three conditions in the special case are all empty. In fact, the query statement of the last otherwise label is passed, but the null is passed, and the data cannot be queried.

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testSelectByChoose(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 都不为空,按照第一个字段进行查询
        List<Car> cars = mapper.selectByChoose("比亚迪", 2.0, "新能源");
        // 都为空,按照最后一个字段进行查询,把null传过去
        List<Car> cars = mapper.selectByChoose("", null, "");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
}

6. foreach tag

Loop arrays or collections and dynamically generate SQL, such as this SQL:

batch deletion

delete from t_car where id in(1,2,3);
delete from t_car where id = 1 or id = 2 or id = 3;

 batch add

insert into t_car values
 (null,'1001','凯美瑞',35.0,'2010-10-11','燃油⻋'),
 (null,'1002','⽐亚迪唐',31.0,'2020-11-11','新能源'),
 (null,'1003','⽐亚迪宋',32.0,'2020-10-11','新能源')

(1) Batch delete

 One of the three brothers: CarMapper interface, write method

The parameter of the method is an array, this is the first contact, the key is to master!

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
    // 批量删除,foreach标签
    int deleteByIds(@Param("ids") Long[] ids);
}

The second of three brothers: CarMapper.xml file, write sql statement

(1) Attributes of the foreach tag:

collection: Specify an array or collection

item: represents an element in an array or collection, which is actually a variable

separator: The separator between loops must be a comma, because the SQL statement is in this form: delete from t_car where id in(1,2,3);

open: What does the front of all the SQL statements spliced ​​by the foreach loop start with?

close: what ends at the end of all the SQL statements spliced ​​by the foreach loop

(2) How does the collection parameter pass an array or collection? Take the ids directly? This will report an error, the error message is: [array, arg0] indicates that a Map collection is created at the bottom layer to store data, for example: map.put("array", array) or map.put("arg0", array); so the default parameters To use array or arg0 ; but for readability, the @Param annotation is used above, and ids can be used directly

(3) The first and second methods use the in method, and the separator uses "comma,"; the third method uses the or method, and the separator is actually or

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <delete id="deleteByIds">
        <!--第一种方法-->
        delete from t_car where id in(
          <foreach collection="ids" item="id" separator=",">
              #{id}
          </foreach>
        )
        <!--第二种方法-->
        delete from t_car where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
        <!--第三种方法:使用or的形式-->
        delete from t_car where
        <foreach collection="ids" item="id" separator="or">
            id=#{id}
        </foreach>
    </delete>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

Prepare a corresponding array and pass it over. The data in the array is the id corresponding to the element we want to delete

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
   @Test
    public void testDeleteByIds(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 准备一个数组
        Long[] ids = {1L,2L,4L};
        int count = mapper.deleteByIds(ids);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
}

(2) Batch insert

 One of the three brothers: CarMapper interface, write method

Batch insertion: The parameter is no longer an array, but a List collection, and an alias is annotated with @Param.

package com.bjpowernode.mybatis.mapper;

import com.bjpowernode.mybatis.pojo.Car;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CarMapper {
     // 批量插入,一次插入多条Car信息,foreach标签
    int insertBatch(@Param("cars") List<Car> cars);
}

The second of three brothers: CarMapper.xml file, write sql statement

①The most important thing here is how to pass parameters? We are using the element Car in the collection, so the parameter is the attribute name corresponding to Car, just "click" it.

②Similarly, the elements in the collection here also use commas as separators, so the separator parameter is also " , "

<?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.bjpowernode.mybatis.mapper.CarMapper">
    <insert id="insertBatch">
        insert into t_car values
        <foreach collection="cars" item="car" separator=",">
            (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
        </foreach>
    </insert>
</mapper>

The third of three brothers: CarMappeTest class, used to write test classes

Prepare the data of the Car object, then add the data of these Car objects to the List collection, and finally pass the List collection as a parameter to the method

package com.bjpowernode.mybatis.test;

import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
  @Test
    public void testInsertBatch(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 准备数据
        Car car1 = new Car(null,"1200", "帕萨特1", 30.0, "2020-11-11", "燃油车");
        Car car2 = new Car(null,"1201", "帕萨特2", 30.0, "2020-11-11", "燃油车");
        Car car3 = new Car(null,"1202", "帕萨特3", 30.0, "2020-11-11", "燃油车");
        // 把数据添加到List集合当中
        List<Car> cars = new ArrayList<>();
        cars.add(car1);
        cars.add(car2);
        cars.add(car3);
        // 把集合传过去
        int count = mapper.insertBatch(cars);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
}

7. SQL tags and include tags (understand)

(1) sql tag: used to declare the sql segment, with the id attribute as the unique identifier. 

(2) include tag: call the refid attribute (pass the id of the sql tag), and include the declared sql segment into a certain sql statement

Role: code reuse, easy maintenance.

    <!--重复的代码抽取出来-->
    <sql id="carColumnNameSql">
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
    </sql>
     <!--代码复用-->
    <select id="selectAll" resultType="Car">
        select
            <include refid="carColumnNameSql"/>
        from  t_car;
    </select>

Guess you like

Origin blog.csdn.net/m0_61933976/article/details/128570677