Mybatis基本使用

向SQL语句传参

  • #{}形式
    Mybatis会将SQl语句中的#{}转化为问号占位符,可以防止sql注入
    image-20240913152447839

    image-20240913152432956
    #{id}被替换为?

  • ${}形式
    ${}形式传参,底层Mybatis做的是字符串拼接操作。
    image-20240913152639526
    通常不会采用${}的方式传值。一个特定的适用场景是:通过Java程序动态生成数据库表,表名部分需要Java程序通过参数传入;而JDBC对于表名部分是不能使用问号占位符的,此时只能使用

  • 结论:实际开发中,能用#{}实现的,肯定不用${}。

    特殊情况: 动态的不是值,是列名或者关键字,需要使用${}拼接

数据输入

概念说明

这里数据输入具体是指上层方法(例如Service方法)调用Mapper接口时,数据传入的形式。

  • 简单类型:只包含一个值的数据类型
    • 基本数据类型:int、byte、short、double、……
    • 基本数据类型的包装类型:Integer、Character、Double、……
    • 字符串类型:String
  • 复杂类型:包含多个值的数据类型
    • 实体类类型:Employee、Department、……
    • 集合类型:List、Set、Map、……
    • 数组类型:int[]、String[]、……
    • 复合类型:List、实体类中包含集合……

单个简单类型参数

  • Mapper接口中抽象方法的声明

    1
    Employee selectEmployee(Integer empId);
  • SQL语句

    1
    2
    3
    <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
    </select>
  • 单个简单类型参数,在#{}中可以随意命名,不过通常还是和接口中的参数名保持一致


实体类类型参数

  • Mapper接口中抽象方法的声明(此处传入的是实体类)

    1
    int insertEmployee(Employee employee);
  • SQL语句

    1
    2
    3
    <insert id="insertEmployee">
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
    </insert>
  • 如果传入的是实体类类型参数,Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。
    例如填入empName,会自动转换为getEmpName()方法获取Employee的属性值empName


多个简单类型数据

零散的多个简单类型参数,如果没有特殊处理,那么Mybatis无法识别自定义名称

  • Mapper接口中抽象方法的声明

    1
    int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
  • SQL语句

    1
    2
    3
    <update id="updateEmployee">
    update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
    </update>
  • 通过@Param注解,将参数重新起名,sql语句中#{}直接填入@Param的value值即可识别


Map类型参数

  • Mapper接口中抽象方法的声明

    1
    int updateEmployeeByMap(Map<String, Object> paramMap);
  • SQL语句

    1
    2
    3
    4
    5
    <update id="updateEmployeeByMap">

    update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}

    </update>
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    private SqlSession session;
    //junit5会在每一个@Test方法前执行@BeforeEach方法
    @BeforeEach
    public void init() throws IOException {
    session = new SqlSessionFactoryBuilder()
    .build(
    Resources.getResourceAsStream("mybatis-config.xml"))
    .openSession();
    }

    @Test
    public void testUpdateEmpNameByMap() {
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    Map<String, Object> paramMap = new HashMap<>();
    paramMap.put("empSalaryKey", 999.99);
    paramMap.put("empIdKey", 5);
    int result = mapper.updateEmployeeByMap(paramMap);
    log.info("result = " + result);
    }

    //junit5会在每一个@Test方法后执行@@AfterEach方法
    @AfterEach
    public void clear() {
    session.commit();
    session.close();
    }

    对应关系
    #{}中填写Map中的key
    使用场景

    当传递参数比较零散,又没有实体类使用时,用@Param注解传入很麻烦,这时就可以全部封装到Map中传入

数据输出

输出类型

  • 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
  • 查询操作的查询结果

单个简单类型

  • Mapper接口中的抽象方法

    1
    int selectEmpCount();
  • SQL语句

    1
    2
    3
    <select id="selectEmpCount" resultType="int">
    select count(*) from t_emp
    </select>

    Mybatis 内部给常用的数据类型设定了很多别名。 以 int 类型为例,可以写的名称有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等

  • junit测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Test

    public void testEmpCount() {

    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

    int count = employeeMapper.selectEmpCount();

    log.info("count = " + count);

    }
  • select标签,通过resultType指定查询返回值类型!

    resultType = “全限定符 | 别名 | 如果是返回集合类型,写范型类型即可”

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

    1
    2
    3
    4
    <typeAliases>
    <typeAlias alias="Author" type="domain.blog.Author"/>
    <typeAlias alias="Blog" type="domain.blog.Blog"/>
    </typeAliases>

    这样配置,在想要使用domain.blog.Blog时,直接使用Blog即可

    也可以直接指定包名,Mybatis会在包名下搜索需要的JavaBean

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

    此时在包domain.blog下的所有JavaBean,在没有注解的情况下,都会使用Bean的首字母小写的非限定类名来作为它的别名,例如domain.blog.Author的别名为author

    也可以直接用注解起别名来使用

    1
    2
    3
    4
    @Alias("author")
    public class Author {
    ...
    }

返回实体类对象

  • Mapper接口的抽象方法

    1
    Employee selectEmployee(Integer empId);
  • SQL语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
    <!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
    <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">

    <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
    <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}

    </select>

    通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来。

  • 增加全局配置自动识别对应关系
    在 Mybatis 全局配置文件中,做了下面的配置,select语句中可以不给字段设置别名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 在全局范围内对Mybatis进行配置 -->
    <settings>
    <!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
    <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
    <!-- 规则要求数据库表字段命名方式:单词_单词 -->
    <!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>

    </settings>

返回Map类型

  • 适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。

  • Mapper接口的抽象方法

    1
    Map<String,Object> selectEmpNameAndMaxSalary();
  • SQL语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
    <!-- 返回工资最高的员工的姓名和他的工资 -->
    <select id="selectEmpNameAndMaxSalary" resultType="map">
    SELECT
    emp_name 员工姓名,
    emp_salary 员工工资,
    (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
    FROM t_emp WHERE emp_salary=(
    SELECT MAX(emp_salary) FROM t_emp
    )
    </select>
  • junit测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Test
    public void testQueryEmpNameAndSalary() {

    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);

    Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();

    Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();

    for (Map.Entry<String, Object> entry : entrySet) {

    String key = entry.getKey();

    Object value = entry.getValue();

    log.info(key + "=" + value);

    }
    }

返回List类型

查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可

  • Mapper接口中抽象方法

    1
    List<Employee> selectAll();
  • SQL语句

    1
    2
    3
    4
    5
    <!-- List<Employee> selectAll(); -->
    <select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary
    from t_emp
    </select>
  • junit测试

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void testSelectAll() {
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    List<Employee> employeeList = employeeMapper.selectAll();
    for (Employee employee : employeeList) {
    log.info("employee = " + employee);
    }
    }

返回主键值

  1. 自增长类型主键

    • Mapper接口中抽象方法

      1
      int insertEmployee(Employee employee);
    • SQL语句

      1
      2
      3
      4
      5
      6
      7
      <!-- int insertEmployee(Employee employee); -->
      <!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
      <!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
      <insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
      insert into t_emp(emp_name,emp_salary)
      values(#{empName},#{empSalary})
      </insert>
    • junit测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      @Test
      public void testSaveEmp() {
      EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
      Employee employee = new Employee();
      employee.setEmpName("john");
      employee.setEmpSalary(666.66);
      employeeMapper.insertEmployee(employee);
      log.info("employee.getEmpId() = " + employee.getEmpId());
      }

      Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。

  2. 非自增长类型主键
    对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!

    • 使用 selectKey 帮助插入UUID作为字符串类型主键示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <insert id="insertUser" parameterType="User">
      <selectKey keyProperty="id" resultType="java.lang.String"
      order="BEFORE">
      SELECT UUID() as id
      </selectKey>
      INSERT INTO user (id, username, password)
      VALUES (
      #{id},
      #{username},
      #{password}
      )
      </insert>

实体类属性和数据库字段对应关系

  1. 别名对应

    • 将字段别名设置为和实体类属性名一致

      1
      2
      3
      4
      5
      6
      7
      8
      9
      <!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
      <!-- Employee selectEmployee(Integer empId); -->
      <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">

      <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
      <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
      select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}

      </select>

      实体类属性的规则
      etXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小写,就是属性名。

  2. 全局配置自动识别驼峰式命名规则

    • 在Mybatis全局配置文件加入如下配置:

      1
      2
      3
      4
      5
      6
      7
      <!-- 使用settings对Mybatis全局进行设置 -->
      <settings>

      <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
      <setting name="mapUnderscoreToCamelCase" value="true"/>

      </settings>
    • SQL语句中可以不使用别名

      1
      2
      3
      4
      5
      6
      <!-- Employee selectEmployee(Integer empId); -->
      <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">

      select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}

      </select>
  3. 使用resultMap

    • 使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
      <resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">

      <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
      <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
      <id column="emp_id" property="empId"/>

      <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
      <result column="emp_name" property="empName"/>

      <result column="emp_salary" property="empSalary"/>

      </resultMap>

      <!-- Employee selectEmployeeByRM(Integer empId); -->
      <select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">

      select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}

      </select>

mapperXML标签总结

  • insert – 映射插入语句。

  • update – 映射更新语句。

  • delete – 映射删除语句。

  • select – 映射查询语句。

  • select标签:

    1
    2
    3
    <select id="selectPerson" resultType="hashmap" resultMap="自定义结构"> 
    SELECT * FROM PERSON WHERE ID = #{id}
    </select>

    这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
    MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

    1
    SELECT * FROM PERSON WHERE ID=?

    select标签的属性

    属性 描述
    id 在命名空间中唯一的标识符,可以被用来引用这条语句。
    resultType 期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
    resultMap 对外部 resultMap 的命名引用。
    timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
    statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
  • update,insert,delete标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <insert
    id="insertAuthor"
    statementType="PREPARED"
    keyProperty=""
    keyColumn=""
    useGeneratedKeys=""
    timeout="20">

    <update
    id="updateAuthor"
    statementType="PREPARED"
    timeout="20">

    <delete
    id="deleteAuthor"
    statementType="PREPARED"
    timeout="20">