MyBatis学习笔记

前言

  • MyBatis 是一款优秀的持久层框架。(持久层:dao层主要与数据库进行交互)
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
  • 2013年11月迁移到Github

持久化

数据持久化
持久化就是将程序的数据在持久状态和瞬时状态转化的过程

持久层

Dao层、Service层,Controller层

  • 完成持久化工作的代码块
  • 层界限十分明显

为什么需要Mybatis?

  • 方便
  • 简化对数据库的操作
  • 传统的JDBC代码太复杂
  • 不用Mybatis也可以,更容易上手。技术没有高低之分

特点

简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
。sql和代码的分离,提高了可维护性。
。提供映射标签,支持对象与数据库的orm字段关系映射
。提供对象关系映射标签,支持对象关系组建维护
。提供xml标签,支持编写动态sq|。

扩展

关于数据库引擎
https://www.jianshu.com/p/4bb9f78b4f6d
InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,InnoDB给MySQL提供了具有提交、回滚和崩溃恢复能力的事物安全(ACID兼容)存储引擎
InnoDB是为处理巨大数据量的最大性能设计。
InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池

  • 如果要提供提交、回滚、崩溃恢复能力的事物安全(ACID兼容)能力,并要求实现并发控制,InnoDB是一个好的选择

  • 如果数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率

  • 如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果

  • 如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive

  • 使用哪一种引擎需要灵活选择,一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能

环境搭建

1.新建一个普通的maven项目
2.删除src目录
3.导入maven依赖


<dependencies>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

JUnit是一个Java语言的单元测试框架。
junit的作用:
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。

创建模块

新建Module
在resource下新建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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/zzt?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

编写工具类:

public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try {
            //使用Mybatis第一步:获取sqlsessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream  inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(); //创建一个能执行sql的对象
    }

}

编写代码

编写实体类User。

编写Dao接口UserDao

public interface UserDao {
    List<User>getUserList();
}

接口实现类UserMapper.xml(由原来的UserDaolmpl转变为一个Mapper配置文件)

<?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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.lmj.dao.UserDao">

    <select id="getUserList" resultType="com.lmj.pojo.User">
         select * from user
    </select>

</mapper>

junit测试:

maven由于他的约定大于配置,我们之后可以能遇到我们写的配置文件,无法被导出或者生效的问题
在pom.xml配置文件中加入

  <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>

            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>

        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

进行测试:

public class UserDaoTest {
    @Test
    public void test(){
        //第一步:获得Sqlsession对象
        SqlSession sqlSession= MybatisUtils.getSqlSession();

        //方式一:getMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList=userDao.getUserList();

        for (User user : userList) {
            System.out.println(user);
        }

        //关闭Sqlsession
        sqlSession.close();
    }
}

CRUD(增删改查)

namespace中的包名要和Dao/Mapper接口的包名一致
增删改操作需要提交事务 sqlSession.commit()

查询语句

id为namespace下方法的名字
resultype:sql语句执行的返回值
parameterType :参数类型

1.编写接口

 //获取全部用户
    List<User>getUserList();
 //根据id查询
    User getUserById(int id);

2.编写对应mapper中的sql语句

<mapper namespace="com.lmj.dao.UserMapper">
    <select id="getUserList" resultType="com.lmj.pojo.User">
         select * from user
    </select>
    
<!--insert没有返回-->
    <insert id="addUser" parameterType="com.lmj.pojo.User" >
          insert into user(id,username, password) values (#{id},#{username},#{password});
    </insert>
</mapper>

3.测试

 @Test
    public void addUser(){
        SqlSession sqlSession= MybatisUtils.getSqlSession();

        //获得接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.addUser(new User(12,"testpeople","123"));

        if(res>0) {
            System.out.println("插入成功");
        }
        //提交事务
        sqlSession.commit();
        sqlSession.close();
    }

万能Map

假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map
1.Map传递参数,直接在sql中取出key即可
2.对象传递参数,直接在sq|中取对象的属性即可
3.只有一个基本类型参数的情况下,可以直接在sq|中取到
4.多个参数用Map,或者注解!

  <!--传入map其中user_id为map的键值对-->
    <insert id="addUser2" parameterType="map" >
          insert into user(id,username, password) values (#{user_id},#{user_name},#{user_password});
    </insert>

测试:
 public void addUser2(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("user_id",20);
        map.put("user_name","lmj");
        map.put("user_password","531123");

        int res=mapper.addUser2(map);

        if(res>0) {
            System.out.println("插入成功2");
        }
        //提交事务
        sqlSession.commit();  //在工具类中设置sqlSessionFactory.openSession(true)可以自动提交
        sqlSession.close();
    }

mybatis之配置解析

  1. 核心配置文件
    在这里插入图片描述
    2.事务管理器
    在MyBatis中有两种类型的事务管理器(也就是type="[JDBC|MANAGED]")
    ●JDBC-这个配置就是直接使用了JDBC的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
    ●MANAGED -这个配置几乎没做什么。它从来不提交或回滚一 个连接,而是让容器来管理事务的整个生命周期

3.数据源
有三种内建的数据源类型也就是type=[UNPOOLED | POOLED | JNDI]") :

UNPOOLED-这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。

POOLED-这种数据源的实现利用“池”的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这是一种使得并发Web应用快速响应请求的流行处理方式。

JNDI -这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用。这种数据源配置只需要两个属性:

mybatis的默认事务管理器是JDBC , 连接池POOLED

属性(properties)
引入外部配置文件db.properties(优先使用)

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/zzt?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123

在mybatis-config.xml文件中引入

 <!--引入外部配置文件-->
    <properties resource="db.properties">
        <property name="" value=""/>
    </properties>

    <environments default="development">  <!--default默认环境-->
        <environment id="development">
            <transactionManager type="JDBC"/>   <!--事务管理有两种类型(也就是type="[JDBC|MANAGED]"-->
            <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>

4.类型别名
●类型别名是为Java类型设置一个短的名字。
●存在的意义仅在于用来减少类完全限定名的冗余。

 <typeAliases>
        <typeAlias type="com.lmj2.pojo.User" alias="User"/>
    </typeAliases>

也可以指定一个包名,MyBatis会在包名下面搜索需要的Java Bean,比如:
扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!

在实体类上增加 注解别名 @Alias(“user”)

设置

这是MyBatis中极为重要的调整设置,它们会改变MyBatis的运行时行为。
cacheEnabled:全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置tetchrype属性来覆盖该项的开关状态。

其他配置

●typeHandlers (类型处理器)
●objectFactory (对象工厂)
●plugins插件
。mybatis-generator-core
。mybatis-plus
。通用mapper

映射器(Mappers)

MapperRegistry:注册绑定我们的Mapper文件

<mappers>
       <!--方式一(推荐)<mapper resource="com/lmj2/dao/UserMapper.xml"/>  -->
       <!--方式二: <mapper class="com.lmj2.dao.UserMapper"/>   -->
       <!--方式三:   <package name="com.lmj2.dao"/> -->
    </mappers>

方式二、方式三注意:
●接口和他的Mapper配置文件必须同名!
●接口和他的Mapper配置文件必须在同一个包下!

生命周期和作用域

生命周期,和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题
总体流程
在这里插入图片描述
SqlSessionFactoryBuilder
●一旦创建了SqlSessionFactory,就不再需要它了
●局部变量

sqlSessionFactory
●说白了就是可以想象为:数据库连接池
●SqlSessionFactory 一旦被创建就应该在应用的运行期间一-直存在,没有任何理由丢弃它或重新创建另一个实例。
●因此SqISessionFactory的最佳作用域是应用作用域。
●最简单的就是使用单例模式或者静态单例模式。

SqlSession
●连接到连接池的一一个请求!
●SqISession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
●用完之后需要赶紧关闭,否则资源被占用!

数据库字段名与实体类名字不一致解决方法

1.直接修改数据库语句

select id username password as pwd from user where id=#{id}

2.resultMap结果集映射

<resultMap id="UserMap" type="User">
        <!-- colum为数据库字段,property为实体类字段,两者字段一致可省略-->
        <result column="password" property="pwd" />
    </resultMap>

    <!--parameterType为参数类型-->
    <select id="getUserById" parameterType="int" resultMap="UserMap">
         select * from user where id=#{id}
    </select>

结果映射
resultMap 元素是 MyBatis 中最重要最强大的元素

日志

日志工厂:
Mybatis通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:
●SLF4J
●LOG4J [掌握]
●LOG4J2
●JDK_ LOGGING
●COMMONS_ LOGGING
●STDOUT_ LOGGING [掌握]
●NO_ LOGGING

在Mybatis中具体使用那个一日志实现,在设置中设定!

<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

STDOUT_ LOGGING标准日志输出

Log4j日志

什么是Log4j
●Log4j是Apache的-一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
●我们也可以控制每一 条日志的输出格式;
●通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
●通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

1.导入log4j包

<dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>

2.log4j.properties

### 配置根 ###
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console 和file的定义在下面的代码
log4j.rootLogger = debug,console ,file

### 设置输出sql的级别,其中logger后面的内容全部为jar包中所包含的包名 ###
log4j.logger.org.apache=dubug
log4j.logger.org.mybatis=dubug
#log4j.logger.java.sql.Connection=dubug
log4j.logger.java.sql.Statement=dubug
log4j.logger.java.sql.PreparedStatement=dubug
log4j.logger.java.sql.ResultSet=dubug

### 配置输出到控制台 ###
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern =  %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n

### 配置输出到文件 ###
log4j.appender.file = org.apache.log4j.FileAppender
log4j.appender.file.File = logs/lmj_log.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

3.配置log4j

    <settings>
       <!-- <setting name="logImpl" value="STDOUT_LOGGING"/> -->
        <setting name="logImpl" value="LOG4J"/>
    </settings>

4.log4j的使用

  • 在要使用Log4j的类中,导入包import org.apache.log4j.Logger;
  • 日志对象,参数为当前类的class
    static Logger logger = Logger.getLogger(UserMapperTest.class);
  • 日志级别
       logger.info("info:进入了testlog4j");  //相当于以前的测试输出信息
       logger.debug("debug:进入了testlog4j");
       logger.error("error:进入了testlog4j");

分页

使用Limit分页

select * from user limit startIndex , pageSize;
//startIndex 从第几个开始查
//pageSize 每页显示的个数
//limit 4 默认从1到3
//特殊:limit 0 -1  查询全部(mysql的bug现已修复,现在会报错)

使用Mybatis实现分页,核心Sql
1.接口

 //分页
    List<User> getUserByLimit(Map<String,Integer> map);

2.Mapper.xml

    <!--分页-->
    <select id="getUserByLimit" parameterType="map" resultMap="UserMap">
        select * from user limit #{startIndex},#{pageSize}
    </select>

3.测试

 @Test
   public void getUserByLimit(){
       SqlSession sqlSession=MybatisUtils.getSqlSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);

       //构造hashmap
       HashMap<String, Integer> map = new HashMap<String, Integer>();
       map.put("startIndex",0);
       map.put("pageSize",2);
       List<User> userByLimit = mapper.getUserByLimit(map);
       for (User user : userByLimit) {
           System.out.println(user);
       }
   }

RowBounds实现分页
1.接口

//分页2
    List<User> getUserByRowBounds();

2.Mapper.xml

<!--分页2-->
    <select id="getUserByRowBounds"  resultMap="UserMap">
        select * from user
    </select>

3.测试

@Test
    public void getUserByRowBounds(){
        SqlSession sqlSession=MybatisUtils.getSqlSession();

        //RowBounds实现
        RowBounds rowBounds=new RowBounds(1,2);

        //通过java代码层面实现分页
        List<User> userList=sqlSession.selectList("com.lmj2.dao.UserMapper.getUserByRowBounds",null,rowBounds);
        for (User user : userList) {
            System.out.println(user);
        }
    }

使用注解开发(简化开发)

本质:是用反射实现
简单的sql语句可以用注解去写,复杂的可以用配置文件去写

接口代码

public interface UserMapper {
    //获取全部用户
    @Select("select * from user")
    List<User> getUserList();

    //根据id查询
    @Select("select * from user where id=#{id}")
    User getUserById(@Param("id") int id);

}

config.xml配置文件中绑定接口

<!--绑定接口-->
    <mappers>
        <mapper class="com.lmj.dao.UserMapper"/>
    </mappers>

测试代码

@Test
    public void test(){
        SqlSession sqlSession= MybatisUtils.getSqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

       List<User> users= mapper.getUserList();

        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void test2(){
        SqlSession sqlSession= MybatisUtils.getSqlSession();
        UserMapper mapper=sqlSession.getMapper(UserMapper.class);

        User user=mapper.getUserById(3);

        System.out.println(user);

    }

mybatis详细流程实现

在这里插入图片描述

#{}和${}区别

在这里插入图片描述

lombok插件

方便编写实体类的get和set等一些方法
使用步骤:
1.在IDEA中安装Lombok插件
2.在项目中导入lombok的jar包

  <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
    </dependencies>

说明:
@Data:无参构造,get、 set.tostring. hashcode, equals
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Getter

嵌套查询

<mapper namespace="com.lmj.dao.TeacherMapper">
    <!--id为namespace下方法的名字-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select t.id tid,t.xm tname,s.name sname,s.sex ssex,s.age sage
        from teacher t,student s
        where t.tid=s.tid
        and t.tid=#{tid};
    </select>

    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="xm" column="tname"/>
        <!--复杂的属性,需要单独处理. -->
        <collection property="students" ofType="Student">
            <result property="name" column="sname"/>
            <result property="sex" column="ssex"/>
            <result property="age" column="sage"/>
        </collection>
    </resultMap>

</mapper>

associton:对象(多对一)
collection: 集合(一对多)
javaType :用来指定实体类中属性的类型
ofType :用来指定映射到List或者集合中的poji类型

<mapper namespace="com.lmj.dao.StudentMapper">
   <select id="getStudent3" resultMap="StudentTeacher2">
         select s.name sname,s.age,s.sex,t.xm tname
         from student s,teacher t
         where s.tid=t.tid;
    </select>

    <resultMap id="StudentTeacher2" type="Student">
        <result property="name" column="sname"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <association property="teacher" javaType="Teacher">
            <result property="xm" column="tname"/>
        </association>
    </resultMap>
  </mapper>

动态sql

	<select id="queryStudent" parameterType="map" resultType="Student">
        select * from student
         <where>
        <if test="name!=null">
         name like  "%"#{name}"%"
        </if>
        <if test="sex!=null">
         and sex=#{sex}
        </if>
        </where>
    </select>

测试代码

    @Test
    public void test(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class);

 //   int res= mapper1.addStudent(new Student(IDutils.getId(),"lmj",21,"男"));
     HashMap map = new HashMap();
    map.put("name","test");
    map.put("sex","男");
    List<Student> students = mapper1.queryStudent(map);
      for (Student teacher : students) {
        System.out.println(teacher);
       }
       
    sqlSession.close();

where元索只会在至少有一个子元素的条件返回SQL子句的情况下才去插入“WHERE"子句。且,若语句的开头为"AND"或“OR",where 元索也会将它们去除。

缓存

1.我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
2.将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查
询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

1.MyBatis系统中默认定义了两级缓存: 一级缓存和二级缓存
2.默认情况下,只有一 级缓存开启。 (SqlSession级别的缓存, 也称为本地缓存)
3.二级缓存需要手动开启和配置,他是基于namespace级别的缓存,通过实现Cache接口来自定义二级缓存

数据更新频繁不适合放在缓存区:设置不经过缓存useCache=“false”

缓存失效
1.查询不同的东西
2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
3.查询不同的Mapper .xml
4.手动清理缓存!(调用sqlsession.clearCache()方法)

面试高频

●Mysq|引擎
●InnoDB底层原理
●索引
●索引优化!

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐