6、MyBatis的SQL映射(mapper)文件

学习资源:动力节点《2020最新MyBatis教程【IDEA版】-MyBatis从入门到精通》



MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。

SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):

  • cache:给定命名空间的缓存配置。
  • cache-ref:其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap已废弃!老式风格的参数映射。
  • sql:可被其他语句引用的可重用语句块。详见:点击跳转
  • insert:映射插入语句
  • update:映射更新语句
  • delete:映射删除语句
  • select:映射查询语

1、指定约束文件

mybatis-3-mapper.dtd 是约束文件的名称,扩展名是 dtd。

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

约束文件作用:限制和检查在当前文件中出现的标签,属性必须符合 MyBatis 的要求。


2、Mapper标签

<mapper> 标签是 SQL 映射文件的根标签,对应 dao 包下的一个接口,在这个标签内部使用 sql 对应实现 dao 接口中的方法即可。

<mapper namespace="">
	CRUD操作
<mapper>

namespace

意为命名空间,是<mapper>标签的一个属性,用于绑定连接 SQL 映射文件和 dao 接口

一个 dao 接口对应一个 SQL 映射文件,所以为了区分不同的 dao 接口,命名空间要求是唯一的,推荐使用 dao 接口的全限定名称(可以自定义)。

<mapper namespace="com.chen.dao.StudentDao">
	CRUD操作
<mapper>

3、CRUD

MyBatis 默认不自动提交事务,所以 update、insert、delete 执行后需要手动 commit

在 SQL 映射文件中,只需要使用对应的标签语句写出我们要实现的 dao 接口的方法,之后再借助 MyBatis的 dao 动态代理,即可在 Java 代码中执行 dao 接口的方法。

CRUD 操作在 MyBatis 的帮助下,是很容易实现。但是 MyBatis 的 CRUD 语句也有一些复杂的部分:传参返回值


3.1、select

简单的 select 语句:

<select id="selectStudents" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student
</select>

Java 代码执行:

// 查询所有学生信息
// 返回值问题有些复杂
List<Student> = dao.selectStudents();

3.2、insert

简单的 insert 语句:

<insert id="insertStudent">
        insert into student values (#{id},#{name},#{email},#{age})
</insert>

Java 代码执行:

// 插入一条学生信息
// 返回值表示执行 insert 后,影响数据库数据的行数
int rows = dao.insertStudent(new Student(1006, "lisa", "5981956@163.com", 24));

3.3、update

简单的 update 语句:

<update id="updateStudent">
    update student set age = #{age} where id=#{id}
</update>

Java 代码执行:

// 更新 id 是 1001 的学生年龄为 37
// 返回值表示执行 update 后,影响数据库数据的行数
int rows = dao.updateStudent(1001, 37);

3.4、delete

简单的 delete 语句:

<delete id="deleteStudent">
    delete from student where id=#{studentId}
</delete>

Java 代码执行:

// 删除 id 是 1002 的学生
// 返回值表示执行 insert 后,影响数据库数据的行数
int rows = dao.deleteStudent(1002);

4、# 和 $

4.1、#

#:占位符,告诉 mybatis 使用实际的参数值代替,并使用 PrepareStatement 对象执行 sql 语句, #{…} 代替 sql 语句的“?”。 这样做更安全、更迅速,通常也是首选做法。

mapper文件:

<select id="selectStudentById" resultType="com.bjpowernode.domain.Student">
	select id,name, email,age from student where id=#{studentId}
<select> 	

mapper 文件转为 MyBatis 的执行是:

String sql="select id,name,email,age from student where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1,1005);

解释:

where id=? 就是 where id=#{studentId}
       ps.setInt(1,1005) , 1005 会替换掉 #{studentId}


4.2、$

$:字符串替换, 告诉 mybatis 使用 ${} 包含的“字符串”替换所在位置,使用 Statement 把 sql 语句和 ${} 的内容连接起来。

$ 主要用在替换表名,列名,不同列排序等操作。

使用实例1:分别使用 id , email 列查询 Student

  1. 接口方法
Student findById(int id);
  1. mapper文件
<select id="findById" resultType="com.bjpowernode.domain.Student">
	select * from student where id=${studentId}
</select>
  1. mapper 文件转为 MyBatis 的执行是:
String sql="select id,name, email,age from student where id"+"1001";
Statement st = conn.statement(sql);

使用实例2:通用方法,使用不同列作为查询条件

  1. 接口方法
Student findByDiffField(@Param("col") String colunName,@Param("cval") Object value);
  1. mapper 文件:
<select id="findByDiffField" resultType="com.bjpowernode.domain.Student">
	select * from student where ${col} = #{cval}
</select>

4.3、对比

  • # 使用 ?在sql语句中做站位的, 使用 PreparedStatement 执行 sql,效率高
  • $ 不是占位符方式,而是字符串连接得方式,使用 Statement 对象执行 sql,效率低
  • # 能够避免 sql 注入,更安全;$ 有sql注入的风险,缺乏安全性
  • # 一般用来占位 sql 语句中 = 后面字段的值;$一般用来替换 sql 语句中的表名或者列名

5、传参问题

从 Java 代码中把接口的方法参数传递到 mapper.xml 文件,需要遵守的规范

5.1、parameterType 属性

parameterType 是 CRUD 标签语句的一个属性,它对应 dao 接口中方法参数的类型,对应方法参数的类型不同,parameterType 也有不同的值对应,一般赋值为方法参数类型的全限定名或别名。如:

<select id="selectStudentById" parameterType="java.lang.Integer" resultTytpe="com.bjpowernode.domain.Student">
    select id, name, email, age from student where id = #{id}
</select>

MyBatis 默认支持的别名(MyBatis 内部定义):

别名 映射类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map/HashMap

但是对于大多数简单的使用场景,MyBatis 通过反射机制都能够发现接口方法的参数的类型,所以 parameterType 属性一般也不需要赋值。


5.2、传递一个简单类型的参数

Dao 接口中方法的参数是一个简单类型的参数(Java 基本类型和 String),那么在 mapper.xml 文件中要获取这个参数,只需要使用占位符 #{ 任意字符 }即可获取,括号中的内容和方法的参数名无关。如:

接口方法:

Student selectById(int id);

mapper文件:

mapper 文件:
<select id="selectById" resultType="com.bjpowernode.domain.Student">
    <!-- #{studentId} , studentId 是自定义的变量名称,和方法参数名无关 -->
	select id,name,email,age from student where id=#{studentId}
</select>

使用 #{} 之后, MyBatis 执行 sql 是使用 JDBC 中的 PreparedStatement 对象

由 MyBatis 执行下面的代码:

  1. MyBatis 创建 ConnectionPreparedStatement 对象
String sql="select id,name, email,age from student where id=?";
PreparedStatement pst = conn.preparedStatement(sql);
pst.setInt(1,1001);
  1. 执行 sql 封装为 resultType="com.bjpowernode.domain.Student" 这个对象 ResultSet rs = ps.executeQuery()
Student student = null;
while(rs.next()){
   // 从数据库取表的一行数据, 存到一个java对象属性中
   student = new Student();
   student.setId(rs.getInt("id));
   student.setName(rs.getString("name"));
   student.setEmail(rs.getString("email"));
   student.setAge(rs.getInt("age"));
}

return student;  // 给了dao方法调用的返回值

所以说,MyBatis 是封装的 JDBC 操作。

5.3、传递多个参数,使用 @Param

当 Dao 接口方法有多个参数,则需要通过名称使用参数。 在方法的所有形参前面加入 @Param(“自定义参数名”) ,那么在 mapper 文件使用 #{自定义参数名},mapper.xml 文件就可以获取到这个参数。

使用实例:

  1. 接口方法:
public List<Student> selectStudentsByMulitParam(@Param("stuName"), String name, @Param("stuAge") Integer age);
  1. mapper文件:
<select id="selectStudentByMulitParam" resultTtpe="com.bjpowernode.domain.Student">
    select * from student where name=#{stuName} or age=#{stuAge}
</select>

5.4、传递多个参数,使用对象

使用 java 对象传递参数,java 的属性值就是 sql 需要的参数值,每一个属性就是一个参数,语法格式:#{ property,javaType=java 中数据类型名,jdbcType=数据类型名称 }

image-20200830084647389

因为 javaType、jdbcType 的类型 MyBatis 都可以检测出来,所以一般不需要设置。 简化后的格式为: #{对象的属性}

可传递的的 Java 对象参数是灵活的,只需要 sql 映射语句的参数和对象的属性名一致即可。

使用实例:

  1. 创建保存参数值的对象 QueryParam :
public class QueryParam {
    private String queryName;
	private int queryAge;
	//set , get 方法
}  
  1. 接口方法:
List<Student> selectMultiObject(QueryParam queryParam);  
  1. mapper 文件:
 <select id="selectMultiObject" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student
    where name=#{queryName,javaType=string,jdbcType=VARCHAR}
    or age =#{queryAge,javaType=int,jdbcType=INTEGER}
</select>
<!-- 简化写法 -->
<select id="selectMultiObject" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student where name=#{queryName} or age=#{queryAge}
</select>

5.5、传递多个参数,按位置

接口方法的参数位置从 0 开始, 引用参数语法 #{ arg 位置 } , 第一个参数是#{arg0},第二个是#{arg1}。

注意: mybatis3.3 版本和之前的版本使用 #{0} ,#{1} 方式, 从 mybatis3.4 开始使用 #{arg0} 方式。

但是传递多个参数的话,还是推荐使用 @Param 或 使用对象传参。

使用实例:

  1. 接口方法:
List<Student> selectByNameAndAge(String name,int age);
  1. mapper 文件
<select id="selectByNameAndAge" resultType="com.bjpowernode.domain.Student">
	select id,name,email,age from student where name=#{arg0} or age =#{arg1}
</select>

5.6、传递多个参数,使用 Map

Map 集合可以存储多个值,使用 Map 可以向 mapper 文件一次传入任意多个参数。

Map 集合使用 String的 <key,Object> 类型的值存储参数。

mapper 文件使用 #{key} 引用 Map 中的数据。

使用实例:

  1. 定义一个 Map
Map<String,Object> data = new HashMap<String,Object>();
data.put(“myname”,”李力”);
data.put(“myage”,20);
  1. 接口方法
List<Student> selectMultiMap(Map<String,Object> map);
  1. mapper文件
<select id="selectMultiMap" resultType="com.bjpowernode.domain.Student">
	select id,name,email,age from student where name=#{myname} or age =#{myage}
</select>

不推荐使用 Map 传参的原因:

  • Map 的 key 定义具有随意性,key 的改变会引起占位符的改变
  • 方法的参数是一个 Map ,无法直接确定该方法的参数个数、参数的类型,可读性很差

6、封装 MyBatis 的输出结果(查询结果)

在 MyBatis 中执行了 sql 语句,都会有返回一个 Java 对象: select 返回一个复杂的 Java 对象, update、 insert、 delete 返回一个整型值。

那么我们应该如何为 MyBatis 指定返回值类型呢?

6.1、resultType

reslutType 是 <select> 标签的一个属性,只有 <select> 标签具有这个属性,并且必须要为这个属性赋值。

reslutType 是用于指定执行 sql 后得到的 ResultSet 转换为 Java 对象,这个 Java 对象的类型,指定类型使用全限定名类型的别名

reslutType 对返回结果的处理方式

  1. MyBatis 执行 sql 语句,然后调用实体类的无参构造方法,创建一个该类的空对象
  2. MyBatis 把 ResultSet 的列值通过 setter 赋给对象的同名属性

image-20200830105318224


6.1.1、返回简单类型

简单类型:Java 基本类型和 String。

别名 映射类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map/HashMap
  1. 接口方法:
int countStudent();  
  1. mapper文件
<select id="countStudent" resultType="int">
	select count(*) from student
</select>
  1. 测试方法
@Test
public void testRetunInt(){
    int count = dao.countStudent();
    System.out.println("学生总人数: "+ count);
}

6.1.2、返回一个对象类型

这里说的对象类型,通常是根据业务自定义的实体类,但也可以不是实体类(qaq)。

MyBatis 框架的处理:使用类的无参构造器创建一个空对象,然后调用 setXXX() 方法给同名属性赋值,所以类中一定要创建 setter

  1. 接口方法
Student selectById(int id);
  1. mapper 文件
<select id="selectById" resultType="com.bjpowernode.domain.Student">
	select id,name,email,age from student where id=#{studentId}
</select>
  1. 测试方法
@Test
public void testSelectById(){
    Student studet = dao.selectById(1001);
    System.out.println(studet);
}
sql 语句查询的字段 java 对象方法
id setId( rs.getInt(“id”) )
name setName( rs.getString(“name”) )
email setEmail( rs.getString(“email”) )
age setAge( rs.getInt(“age”) )

6.1.3、返回一个列表

如果返回的是集合,那应该设置为集合中元素的类型,而不是集合本身。

  1. 接口方法
List<Student> selectStudents();
  1. mapper文件
<select id="selectStudents" reslutType="com.bjpowernode.domain.Student">
    select * from student
</select>
  1. 测试方法
@Test
public void testSelectList(){
    List<Student> studets = dao.selectStudents();
}

6.1.4、返回一个 Map

MyBatis 可以将查询的结果数据封装为一个 Map,字段名作为 key,字段值作为 value 。

使用实例:

  1. 接口方法
Map<Object,Object> selectReturnMap(int id);
  1. mapper文件
<select id="selectReturnMap" resultType="java.util.HashMap">
	select name,email from student where id = #{studentId}
</select>
  1. 测试方法
@Test
public void testReturnMap(){
    Map<Object,Object> retMap = studentDao.selectReturnMap(1002);
    System.out.println("查询结果是 Map:"+retMap);
}

注意:Map 作为接口返回值, sql 语句的查询结果最多只能有一条记录,大于一条记录会产生错误。


6.2、resultMap

看官方文档吧,好复杂的样子~

resultMap 可以自定义 sql 查询结果和 Java 对象属性的映射关系,可以更灵活的把列值赋值给指定属性。

属性 描述
id 当前命名空间中的一个唯一标识,用于标识一个结果映射。
type 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

resultMap 使用场景:

  • 表字段名和 Java 对象属性名不一样
  • 一对多,多对一查询

因为 MyBatis 在输出结果时,会将查询到的字段赋给同名的属性,但是由于数据库字段名和 Java 属性的命名规范是不相同的,极有可能出现字段名和属性名不一致的情况,这种情况下,如果不使用 resultMap 进行对应关系的重映射,可以查出数据但是无法输出数据。

一个 mapper 文件中可以有多个 resultMap ,只需要赋予不同的 id 即可区分;同时 resultMap 是可以复用的,可同时被多个 <select> 引用。

注意:resultMap和resultType不要一起用,二选一。


6.2.1、字段名-属性名关系重映射

使用实例:

  1. 实体类
public class Student {
    //属性名和列名不一致
    private Integer sId;
    private String sName;
    private String sEmail;
    private Integer sAge;
    // set ,get , toString
}
  1. 接口方法
List<Student> selectUseResultMap();
  1. mapper文件
<!--使用resultMap
	resultMap也是 <select> 标签的一个属性
	只需要赋给已定义的 <resultMap> 的id,即可引用新的映射关系
-->
<select id="selectUseResultMap" resultMap="studentMap">
    select id,name,email,age from student where name=#{queryName} or age=#{queryAge}
</select>

<!-- 创建 resultMap -->
<resultMap id="studentMap" type="com.bjpowernode.domain.Student">
    <!-- 
		新的,字段和属性的映射关系
		column:字段名
		property:属性名
	-->
    <!-- 主键字段使用 id -->
    <id column="id" property="sId" />
    <!--非主键字段使用 result-->
    <result column="name" property="sName"/>
    <result column="email" property="sEmail" />
    <result column="age" property="sAge" />
</resultMap>

<resultMap id="xxx" type="xxx.xxx.xxx.xxx">
    ......
</resultMap>

6.2.2、多对一查询

7、为自定义类型起别名

MyBatis 内部默认支持一些别名,如 java.lang.Integer 的别名为 int,因此 resultType="java.lang.Integer" 就可以简写为resultType="int"

使用别名的意义在于减少完全限定名的冗余,方便引用某个类。

起过别名后,在主配置文件的其他任何需要用到全限定名称的位置,都可以使用该类的别名替换了。

MyBatis 支持为自定义类型定义别名,可以使用以下几种方式:

  1. 为一个类设置一个自定义的别名
<typeAliases>
    <!-- 
		type:自定义类型的全限定名称
		alias:别名(推荐使用短小、容易记忆的)
	-->
    <typeAlias type="com.chen.pojo.Student" alias="student"/>
    <typeAlias type="com.chen.pojo.User" alias="vipUser"/>
</typeAliases>
  1. 批量定义别名,扫描整个包下的类,别名为类名(首字母大写或小写都可以)
<typeAliases>
    <package name="com.bjpowernode.domain"/>
    <package name="...其他包"/>
</typeAliases>
  1. 在自定义类上使用注解 @Alias("自定义别名")
@Alias("stu")
public class Student {
    ......
}

推荐使用全限定名称


8、解决字段名和属性名不一致

推荐使用 resultMap

8.1、使用 resultMap 进行重映射

8.2、列别名

resultMap 的处理原则是将同名的字段值赋给同名的属性,所以可以给字段起别名 $-->$ Java 对象的属性名。

使用实例:

  1. 实体类
public class Student {
    //属性名和列名不一致
    private Integer sId;
    private String sName;
    private String sEmail;
    private Integer sAge;
    // set ,get , toString
}
  1. 接口方法
List<Student> selectDiffColProperty();
  1. mapper文件
<select id="selectDiffColProperty" resultType="">
	select id as sId, name sName, email sEmail, age sAge from student
</select>

9、模糊查询

在 SQL 中有如下这种模糊查询语句:

-- 查询所有姓李的学生的信息
select * from student where name like "李%"

那么 MyBatis 是如何实现模糊查询的呢?

MyBatis 模糊查询的实现有两种方式:

  • 在 Java 代码中给查询数据加上"%"
  • 在 mapper 文件 sql 语句的条件位置加上"%"

推荐使用方式1


9.1、Java 代码指定 like 的内容

使用实例:

  1. 接口方法:
List<Student> selectLikeFirst(String name);  
  1. mapper文件
<select id="selectLike_1" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student
    where name like #{studentName}
</select>

3.测试方法

@Test
public void testSelectLike_1(){
    
    String name="李%";
    List<Student> stuList = studentDao.selectLike_1(name);
    stuList.forEach( stu -> System.out.println(stu));
}

9.2、mapper 文件中拼接 like 的内容

注意:"%" #{xxx} "%" ,前后双引号和占位符之间是有一个空格的。

使用实例:

  1. mapper 文件
<select id="selectLike_2" resultType="com.bjpowernode.domain.Student">
    select id,name,email,age from student
    where name like #{studentName} "%"
</select>
  1. 测试方法
@Test
public void testSelectLike_2(){
    String name="李";
    List<Student> stuList = studentDao.selectLikeSecond(name);
    stuList.forEach( stu -> System.out.println(stu));
}
原文地址:https://www.cnblogs.com/sout-ch233/p/13608337.html