MyBatis微总结

看这篇文章之前,思考一个问题:Mybatis怎么写映射文件/注解去告诉程序操作的?
此笔记大部分是代码的形式呈现,代码中有相应的注释进行解析,希望对你有所帮助┗( ▔, ▔ )┛
其它相关文章:

Spring微总结

MyBatis的基本介绍

1、什么是mybatis?
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
2、持久化:数据从瞬间状态变为持久状态。
3、持久层:完成持久化工作的代码块。—dao
4、Mybatis就是帮助程序猿将数据存入数据库中,和从数据库中取数据。
5、传统的jdbc操作:有很多重复代码块。比如:数据取出时的封装。数据库的建立连接等等。通过框架可以减少重复代码,提高开发效率。
6、Mybatis是一个半自动化的ORM框架。O—object,R—relationship,M—mapping
7、Mybatis的功能:
Mybatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。Mybatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。
本人使用idea进行学习操作的。
在这里插入图片描述
本文章一、二为入门知识

一、MyBatis的第一个应用

步骤:
导入mybatis相关jar包

asm-3…3.1.jar
cqlib-2.2.2.jar
commons-logging-1.1.1.jar
javassist-3.17.1.GA.jar
log4j-1.2.17.jar
log4j-api-1.7.5.jar
log4j-core-2.0-rc1.jar
mybatis-3.2.7.jar
slf4j-api-1.7.5.jar
slf4j-log4j12-1.7.5.jar
数据库驱动包

I、引入pom.xml依赖:
<dependencies>
            <dependency>
      <groupId>asm</groupId>
      <artifactId>asm</artifactId>
      <version>3.3.1</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.3.0</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.29</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.12</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.12</version>
    </dependency>
    <dependency>
      <groupId>javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.9.0</version>
    </dependency>
</dependencies>
II、编写mybatis核心配置文件(极其重要,已被一个小坑坑了,要小心)

操作步骤:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里就是个坑,要在target创建mybatis-config文件
在这里插入图片描述
在这里插入图片描述
核心文件内容如下参考:
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">
            <!--mybatis使用jdbc事务管理方式
            JDBC-这个配置直接使用JDBC的提交和回滚功能。它依赖于从数据源获得连接来管理事务的生命周期。
            MANAGED-这个配置基本上什么都不做。它从不提交或者回滚一个连接的事务。而是让容器(例如:Spring或者J2EE应用服务器)来管理事务的生命周期
            -->
            <transactionManager type="JDBC"/>
            <!--
            数据源类型:
                    UNPOOLED-这个类型的数据源实现只是在每次需要的时候简单地打开和关闭连接。
                    POOLED-这个数据源的实现缓存了JDBC连接对象,用于避免每次创建新的数据库连接
                    时都初始化和进行认证,加快程序响应。并发WEB应用通常这种做法来获得快速响应。
            -->
            <!--mybatis使用连接池方式来获取连接-->
            <dataSource type="POOLED">
                <!--配置与数据库交互的4个必要属性-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="1214"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="cn/sxt/entity/user.mapper.xml"/>
    </mappers>
</configuration>

其中我的MySQL表名称和内容为:
在这里插入图片描述

III、创建SqlSessionFactory,以及获得SqlSession

MyBatisUtil.java:

 package cn.sxt.util;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import javax.annotation.Resources;
import java.io.IOException;
import java.io.InputStream;
//创建SqlSessionFactory,以及获得SqlSession
public class MyBatisUtil {
/**
 * 通过配置文件 创建SqlSessionFactory 是一个SqlSession的工厂类
 */
    public static SqlSessionFactory getSqlSessionFactory() throws IOException {
        //得到配置文件流
        InputStream inputStream = Resources.class.getResourceAsStream("/mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory;
    }
    /**
     * SqlSession 通过id 找到对应的sql语句,执行sql语句
     */
    public static SqlSession getSession() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        return sqlSessionFactory.openSession();
    }
}
IV、创建实例类

User.java:

package cn.sxt.entity;

public class User {
    private int id;
    private String name;
    private String pwd;

    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 String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
V、编写sql语句的映射文件

user.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="cn.sxt.entity.UserMapper">
    <select id="selectUser" resultType="cn.sxt.entity.User">
        select * from user where id = #{id}
    </select>
</mapper>
最后测试:

Test.java:

package cn.sxt.test;

import cn.sxt.entity.User;
import cn.sxt.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.io.IOException;

public class Test {
    public static void main(String[] args) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        User user = session.selectOne("cn.sxt.entity.UserMapper.selectUser",2);
        System.out.println("id=" + user.getId() + ",name=" + user.getName() + ",pwd=" + user.getPwd());
        session.close();
    }
}

其结果为:
在这里插入图片描述

二、基本的crud操作

1、搭建mybatis框架
a、导入相关jar包
b、编写核心配置文件(配置数据库连接的相关信息以及配置了mapper映射文件)
c、编写dao文件
d、编写mapper映射文件
e、编写实体类

crud实现内容

user.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">
        <!--namespace命名空间防止sql语句的id重名
        namespace 命名 包名+类名/包名+mapper文件名
        parameterType 指sql语句参数类型
        resultType 返回结果类型
        -->
<mapper namespace="cn.sxt.entity.UserMapper">
    <!-- 查询单个用户
            id在该文件中是唯一-->
    <select id="selectUser" resultType="cn.sxt.entity.User">
        select * from user where id = #{id}
    </select>
    <!-- 查询所有用户 -->
    <select id="selectAll" resultType="cn.sxt.entity.User">
        select * from user
    </select>
    <!-- 添加用户 -->
    <insert id="addUser" parameterType="cn.sxt.entity.User" useGeneratedKeys="true">
        insert into user (name,pwd) values (#{name},#{pwd})
    </insert>
    <!-- 更新用户信息 -->
    <update id="updateUser" parameterType="cn.sxt.entity.User">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>
    <!-- 删除用户 -->
    <delete id="deleteUser">
        delete from user where id=#{id}
    </delete>
</mapper>

UserDao.java:

package cn.sxt.dao;

import cn.sxt.entity.User;
import cn.sxt.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.io.IOException;
import java.util.List;

public class UserDao {
    public List<User> getAll() throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        List<User> list = session.selectList("cn.sxt.entity.UserMapper.selectAll");
        session.close();
        return  list;
    }
    public User getById(int id) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        User user = session.selectOne("cn.sxt.entiry.UserMapper.selectUser",id);
        session.close();
        return user;
    }
    public int add(User user) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        int result = session.insert("cn.sxt.entity.UserMapper.addUser",user);
        session.commit();
        session.close();
        return result;
    }
    public int update(User user) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        int result = session.update("cn.sxt.entity.UserMapper.updateUser",user);
        session.commit();
        session.close();
        return result;
    }
    public int delete(int id) throws IOException {
            SqlSession session = MyBatisUtil.getSession();
            int result = session.delete("cn.sxt.entity.UserMapper.deleteUser",id);
            session.commit();
            session.close();
            return result;
    }
}

User.java添加toString:

@Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }

在Test.java中添加运行下列语句便可完成添加用户:

UserDao userDao = new UserDao();
        User user = new User();
        user.setId(7);
        user.setName("xiaobai");
        user.setPwd("6677");
        System.out.println(userDao.add(user));

在Test.java中添加运行下列语句便可完成删除用户:

UserDao userDao = new UserDao();
        System.out.println(userDao.delete(5));

在Test.java中添加运行下列语句便可完成查询所有用户:

UserDao userDao = new UserDao();
        List<User> list = userDao.getAll();
        for (User u:list) {
            System.out.println(u);
        }

到这里,便就是MyBatis的入门,其实我们想想,Mybatis的入门很简单,无非就是:当我们要要操作数据库时,总要先把数据库相关的配置好吧,而且这里写一次基本下次就不用写了。
操作数据库时,总要告诉程序怎么操作吧,所以就要用到映射文件(可以是XML的映射文件,也可以是注解的方式)
然后我们操作SQLSession去执行命令,最后把session关闭,减少资源浪费。
总:读取核心配置文件–>sqlSessionFactory类–>sqlSession–>(执行相关操作)

三、优化配置文件

导入properties配置文件
a)、在src下加入db.properties配置文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=1214

在这里插入图片描述
并在mybatis-config.xml修改:

<properties resource="db.properties"/>

<!--配置与数据库交互的4个必要属性-->
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
I、别名的优化:

mybatis-config.xml添加:

<typeAliases>
        <!--为指定类型指名 别名 使得在mapper映射文件中可以简化引用-->
        <typeAlias type="cn.sxt.entity.User" alias="User"/>
        <!--为某个包下的所有类指定别名 默认别名是对应的类名-->
    	<!--<package name="cn.sxt.entity.User"/>-->
    </typeAliases>

并把user-mapper.xml里的cn.sxt.entity.User替换成User,然后在运行Test一样可以运行。

II、解决属性名与列名不一致

当我们把User.java的pwd改为password,而数据库仍然还是pwd,那我们要如何获取呢?
User.java:

package cn.sxt.entity;

public class User {
    private int id;
    private String name;
    //private String pwd;
    private String password;

    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 String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

//    public String getPwd() {
//        return pwd;
//    }
//
//    public void setPwd(String pwd) {
//        this.pwd = pwd;
//    }

//    @Override
//    public String toString() {
//        return "User{" +
//                "id=" + id +
//                ", name='" + name + '\'' +
//                ", pwd='" + pwd + '\'' +
//                '}';
//    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

更改后运行会出去这个情况:
在这里插入图片描述问题:密码没有获取到
原因: mybatis会根据查询的列名(会将列名转为小写)去进行设值(列名setter方法)
解决列名和属性名不一致的办法
a)、为列名指定别名和java实体类的属性名一致
user.mapper.xml:

 <!-- 查询所有用户 -->
    <select id="selectAll" resultType="User">
            select id,name,pwd password from user
        </select>

b)、设置结果映射类型

<!-- 查询单个用户
            id在该文件中是唯一-->
    <select id="selectUser" resultType="UserMap">
            select id,name,pwd password from user where id = #{id}
        </select>
         <!-- id为主键 -->
    <resultMap id="UserMap" type="User">
    <!--column是数据库中表的列名 property是对应实体类的属性名-->
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="pwd" property="password"/>
    </resultMap>

四、分页实现

1、分析mysql的分页语句:limit startIndex,pageNum
user.mapper.xml映射文件:

<!-- 查询所有用户 -->
    <select id="selectAll" parameterType="Map" resultType="User">
            select * from user limit #{startIndex},#{pageSize}
        </select>

UserDao.java:

//分页查询
    public List<User> getAll(int currentPage,int pageSize) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        Map<String,Integer> map = new HashMap<String, Integer>();
        map.put("startIndex",(currentPage-1)*pageSize);
        map.put("pageSize",pageSize);
        List<User> list = session.selectList("cn.sxt.entity.UserMapper.selectAll",map);
        return list;
    }

注意:不用为参数设置类,可以采用map结构来解决这个问题。
2、通过RowBounds来实现分页
user.mapper.xml不用做任何改变

<select id="getAll" resultType="User">
            select * from user
        </select>

UserDao.java中需要新建RowBounds对象
RowBounds rowBounds = new RowBounds(index,size); index是下标,size数据

 public List<User> getAll(int currentPage,int pageSize) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
        List<User> list = session.selectList("cn.sxt.entity.UserMapper.getAll",null,rowBounds);
        session.close();
        return list;
    }

我的数据库内容:
在这里插入图片描述
例在Tset.java中分页操作:

UserDao userDao = new UserDao();
        List<User> list = userDao.getAll(1,3);
        for (User u:list) {
            System.out.println(u);
        }

其结果为:
在这里插入图片描述

五、使用注解实现mybatis

1、面向接口编程:
好处: 扩展性好,分层开发中,上层不用管具体的实现,大家都遵循共同的标准,使得开发变得容易。规范性更好
2、注解的实现
a)编写UserDao1接口

public interface UserDao1 {
    @Select("select * from user")
    public List<User> getList();
}

b)在核心配置文件中 导入

<mappers>
        <mapper class="cn.sxt.dao.UserDao1"/>
    </mappers>

3、Test.java使用

public static void main(String[] args) throws IOException {
        SqlSession session = MyBatisUtil.getSession();
        UserDao1 userDao = session.getMapper(UserDao1.class);
        List<User> list = userDao.getList();
        for (User u:list) {
            System.out.println(u);
        }

结果可得:

在这里插入图片描述
与前面的方法相比,这个方法更简便。

六、关于联表的处理

1、数据库表的设计
创建student和teacher两张表后,对student进行表设计:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多对一的处理

2)、实体类
Teacher.java:

package cn.sxt.entity;

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

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

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

Student.java:

package cn.sxt.entity;

public class Student {
    private int id;
    private String name;
    private Teacher teacher;
    //多个学生对应一个老师

    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 Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}

mybatis-config.xml:

 <mappers>
        <mapper resource="cn/sxt/entity/student.mapper.xml"/>
    </mappers>

3)、编写映射文件student.mapper.xml:
两种方式:
结果嵌套处理

<mapper namespace=cn.sxt.entity.student.mapper">
    <!--
        多对一处理有两种方式
        1、按结果嵌套处理
        2、按查询嵌套处理
    -->
    <!--按结果嵌套处理-->
    <select id="getStudents" resultMap="StudentTeacher">
        select s.id sid,s.name sname,s.tid stid,t.id tid,t.name tname from student s,teacher t where s.tid=t.id
    </select>
    <resultMap id="StudentTeacher" type="cn.sxt.entity.Student">
        <id column="sid" property="id"/>
        <result column="sname" property="name"/>
        <!--关联对象 property 关联对象在Student 实体类中的属性-->
        <association property="teacher" javaType="cn.sxt.entity.Teacher">
            <id column="tid" property="id"/>
            <result column="tname" property="name"/>
        </association>
    </resultMap>
</mapper>

查询嵌套处理

<!--
    按查询嵌套处理
    -->
    <select id="getStudents" resultMap="StudentTeacher">
        select * from student
    </select>
    <resultMap id="StudentTeacher" type="cn.sxt.entity.Student">
    <!--association关联属性 property属性名 javaType属性的类型 column关联属性在多的一方表中的列名-->
        <association property="teacher" column="tid" javaType="cn.sxt.entity.Teacher" select="getTeacher">
        </association>
    </resultMap>
    <select id="getTeacher" resultType="cn.sxt.entity.Teacher">
        select * from teacher where id=#{id}
    </select>

最后两种方法的结果为:
在这里插入图片描述

一对多的处理

数据库已上面的相同
实体类:
Student.java:

package cn.sxt.entity;

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;
    }
}

Teacher.java:

package cn.sxt.entity;

import java.util.List;

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

    public String getName() {
        return name;
    }

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

    public int getId() {
        return id;
    }

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

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

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

编写映射文件teacher.mapper.xml:
两种处理方式:
第一种:
teacher.mapper.xml:

<mapper namespace="cn.sxt.entity.teacher.mapper">
    <select id="getTeacher" resultMap="TeacherStudent">
               select s.id sid,s.name sname,s.tid stid,t.id tid,t.name tname from student s,teacher t where s.tid=t.id
            </select>
    <resultMap id="TeacherStudent" type="cn.sxt.entity.Teacher">
        <id column="tid" property="id"/>
        <result column="tname" property="name"/>
        <collection property="students" ofType="cn.sxt.entity.Student">
            <id column="sid" property="id"/>
            <result column="sname" property="name"/>
        </collection>
    </resultMap>
        </mapper>

TeacherDao:

public class TeacherDao {
    public Teacher getTeacher(int id) throws IOException{
        SqlSession session = MyBatisUtil.getSession();
        Teacher teacher = session.selectOne("cn.sxt.entity.teacher.mapper.getTeacher",id);
        session.close();
        return teacher;
    }
}

mybatis.config.xml:

<mappers>
            <mapper resource="cn/sxt/entity/student.mapper.xml"/>
    </mappers>

Test.java:

public class Test {
    public static void main(String[] args) throws IOException {
        TeacherDao teacherDao = new TeacherDao();
        Teacher teacher = teacherDao.getTeacher(1);
        System.out.println("teacher name=" + teacher.getName());
        List<Student> list = teacher.getStudents();
        for (Student stu:list){
            System.out.println("Student name=" + stu.getName());
        }

第二种:

teacher.mapper.xml:

<select id="getTeacher" resultMap="TeacherStudent">
            select * from teacher where id=#{id}
        </select>
    <resultMap id="TeacherStudent" type="cn.sxt.entity.Teacher">
        <!--column是一端在多端的外键,写的是一端的主键列名-->
        <collection property="students" javaType="ArrayList" ofType="cn.sxt.entity.Student" column="id" select="cn.sxt.entity.Student.mapper.gteStudentByTid"></collection>
    </resultMap>

student.mapper.xml:

<select id="getStudentByTid" resultType="cn.sxt.entity.Student">
        select * form student where tid=#{id}
    </select>

mybatis.config.xml:

<mappers>
            <mapper resource="cn/sxt/entity/student.mapper.xml"/>
            <mapper resource="cn/sxt/entity/teacher.mapper.xml"/>
    </mappers>

其结果都为:

在这里插入图片描述

七、动态SQL

MyBatis最强大的特性之一就是它的动态语句功能。 如果您以前有使用JDBC或者类似框架的经历,您就会明白把SQL语句条 件连接在一起是多 么的痛苦,要确保不能忘记空格或者不要在columns列后面省略一个逗号等。动态语句能够完全解决掉这些痛苦。

尽管与动态SQL-起工作不是在开- -个party, 但是MyBatis 确实能通过在任何映射SQL语中使用强大的动态SQL来改进这些状况。

动态SQL元素对于任何使用过JSIL或者类似于XML之类的文本处理器的人来说,都是非常熟悉的。在上- -版本中,需要了解和学习非常多的元素,但在MyBatis 3中有了许多的改进,现在只剩下差不多二分之一的元素。 MyBatis 使用了基于强大的OGNL表达式来消除了大部分元素。

●if

●choose (when, otherwise)

●trim (where,set)

●foreach
现在我们主要了解一下if
1、动态sql指根据不同的查询条件,生成不同的sql语句。
2、Mapper文件

<select id="getUserByCondition" parameterType="Map" resultType="cn.sxt.entity.User">
        select * from user
        <where>
            <if test="name!=null">
                name like CONCAT('%',#{name},'%')
            </if>
        </where>
    </select>
发布了8 篇原创文章 · 获赞 45 · 访问量 8074

猜你喜欢

转载自blog.csdn.net/weixin_45537947/article/details/105590212