Mybatis的sql mapper映射文件详解

1、sql 映射文件常见关键字 

sql 映射文件中需要注意的一些关键字:

  • parameterType: 指定要求输入参数的类型,可以指定为基本数据类型(如 int、float 等)、包装数据类型(如 String、Interger 等)以及用户自己编写的 JavaBean 封装类。不管参数是基本数据类型还是JavaBean,parameterType 都可以不填,mybatis 通过反射机制能够自动识别到调用 sql 语句的参数并进行填充。一般情况下可以不指定该关键字。
  • resultType: 指定输出的结果类型。 resultType 就是用来指定数据库返回的信息对应的 Java 的数据类型。
  • #{}:表示参数占位符,在大括号中书写参数名称来接受对应参数。#{} 实际上是通过 prepareStatement 对象来执行 sql 的,可以避免 SQL 注入问题。当传入的参数是java对象时,#{} 会自动根据参数名来获取对象的属性值。当 “#{}” 接受的是基本数据类型时可以用 value 或者其他任意名称来获取,比如#{value}。
  • ${}:${} 为字符串替换,是通过拼接字符串的方式来拼接成 sql 的。使用 “${}” 拼接符号拼接 SQL ,会引起 SQL 注入,所以一般不建议使用 “${}”。在 SQL 配置中,有时候需要拼接 sql 语句(例如模糊查询时),用 “#{}” 是无法达到目的的。

mybatis 的 sql 语句如果需要接收多个参数,我们可以通过以下方式来传递多个参数:

  1. 通过 java 对象进行传参。比如一个 java 类的对象
  2. 使用 @param 传递命名参数,sql 语句中通过 #{自定义参数名} 的形式来获取参数
  3. 可以使用 #{arg0}  #{arg1} 的形式来根据参数的位置来获取参数。注意,mybatis 3.4之前的版本是通过 #{0}、#{1} 这种形式来使用的
  4. 通过 map 集合传递参数

 

2、关键字#{}和${}

#{}:表示参数占位符,在大括号中书写参数名称来接受对应参数。#{} 实际上是通过 prepareStatement 对象来执行 sql 的,可以避免 SQL 注入问题。当传入的参数是java对象时,#{} 会自动根据参数名来获取对象的属性值。当 “#{}” 接受的是基本数据类型时可以用 value 或者其他任意名称来获取,比如#{value}。

${}:${} 为字符串替换,是通过拼接字符串的方式来拼接成 sql 的。使用 “${}” 拼接符号拼接 SQL ,会引起 SQL 注入,所以一般不建议使用 “${}”。在 SQL 配置中,有时候需要拼接 sql 语句(例如模糊查询时),用 “#{}” 是无法达到目的的。

 

2.1、#{}和${}的区别

在MyBatis 的映射配置文件中,动态传递参数有两种方式:#{} 占位符、${} 拼接符。

两者的区别如下:

  1. #{} 为参数占位符 ?,即sql 预编译;${} 为字符串替换,即 sql 拼接
  2. #{} 为动态解析 -> 预编译 -> 执行;${} 为动态解析 -> 编译 -> 执行
  3. #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
  4. 变量替换后,#{} 对应的变量自动加上单引号 ' ';变量替换后,${} 对应的变量不会加上单引号 ' '
  5. #{} 能防止sql 注入;${} 不能防止sql 注入

 

两者使用示例:

# 示例#{}的使用,
# 定义语法
select * from t_user where uid=#{uid}
# 然后
select * from t_user where uid= ?
# 不管传入的数值为数字类型或者字符串类型,最后都会自动给加上单引号
# 假设传入参数为10或者你好的中文,最后结果为:
select * from t_user where uid= '1'
select * from t_user where uid= '你好'

# 示例${}的使用,假设传入参数为1
# 定义语法,如果最后结果需要有单引号,我们需要主动加上单引号:
select * from t_user where uid= '${uid}'
# 最终结果
select * from t_user where uid= '1'

 

2.2、关键字#{}自动给变量加单引号的问题

使用 #{} 占位符时,MyBatis 为了防止SQL注入会为变量值自动加单引号。

示例:

# 定义语句
select * from table_a where name=#{name}

# 假设变量name="abc",则实际执行的SQL语句为:
select * from table_a where name='abc'

 

正常情况下,加上单引号可以避免 sql 注入问题,但是在有些情况下加入单引号会导致 sql 语句直接报错,比如如果变量名是表名或者列名时。

示例:

create table if not exists #{tableName}

# 如果变量tableName="abc",则实际执行的SQL如下,该SQL将无法执行
create table if not exists 'abc'   

# 此时我们可以使用${}来替代#{}:
create table if not exists ${tableName}

#  则实际执行SQL为
create table if not exists abc

在变量是表名或者列名时,我们可以使用 ${},在使用 ${} 时需注意传入的变量值一定需保证是可靠的。

 

3、resultType

resultType指定输出的结果类型,就是用来指定数据库返回的信息对应的 Java 的数据类型。该 java 数据类型可以是任意的数据类型,不一定是一个实体类。如果 resultType 指定的是一个实体类,mybatis 执行 sql 语句后,会调用指定数据类型的类的无参构造函数来创建对象,将指定的列值赋值给同名的类属性。

mybatis 中 resultType 可选类型有如下几种:

  1. java 的基础类型及其包装类 int(java.lang.Integer)、string(java.lang.String)、double(java.lang.Double)等。
  2. 实体类,自己定义的实体类。
  3. map类型,如果使用resultMap这里可以使用自定义map。
  4. 集合,即返回的时一个List集合,其中该集合的类型可以为1,2,3中提到的类型。

注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用 resultType 或 resultMap,但不能同时使用。

 

3.1、返回基本数据类型

如果是基本数据类型,则 resultType 的值可以是基本数据类型的类的全限定名称,或者也可以直接写别名即可。比如:java.lang.Integer 的别名是  int。

基本数据类型的类的别名映射关系如下:

 

示例:

<mapper namespace="user">
    <!-- 返回值为int,resultType可以写成int或者java.lang.Integer-->
    <select id="countUser" resultType="int">
        select count(*) from user
    </select>
</mapper>

 

3.2、返回一个 JavaBean

当返回一个 JavaBean 时,数据库中的列名需跟 JavaBean 的属性名一一对应,否则该属性将无法赋值成功。

比如根据某个字段获得数据库中的信息,把查询的结果信息封装成某个 JavaBean 类型的数据。

示例:

<mapper namespace="dao.StudentDao">

    <select id="findStudentById" parameterType="int" resultType="entity.Student">
        select * from student where id = #{id}
    </select>
</mapper>

测试代码:

//mapper接口类
public interface StudentDao {
    public Student findStudentById(int id);
}

//测试代码
Student student = studentDao.findStudentById(2);
System.out.println(student);

mybatis 执行 sql 语句后,会调用指定数据类型的类的无参构造函数来创建对象,将指定的列值赋值给同名的类属性,即自动将查询的结果映射成 JavaBean 中的属性。

 

3.2.2、自定义别名

当返回一个自定义的 JavaBean 对象时,我们可以在 mybatis 的配置文件中为该 JavaBean 定义一个别名,然后就可以在 resultType 中使用该别名了。

先在 mybatis 配置文件中定义别名:

<?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>
   ...

    <typeAliases>
        <!--可以为多个类定义别名-->
        <typeAlias type="entity.Student" alias="myStu"></typeAlias>
        
        <!--或者也可以使用package来为该包中的所有类定义别名,类名就是别名(不区分大小写)-->
        <package name="entity"/>
    </typeAliases>

    ...
</configuration>

使用 package 标签定义别名比较方便,但如果为多个包都定义了别名,而不同包中有相同类名,则可能在 sql 映射文件中使用别名可能会报错,因为 mybatis 不知道你这个别名指定的是哪个类。

定义别名后就可以在sql mapper 文件中使用了。可以使用 typeAlias 中指定的别名,如果是通过 package 标签定义的别名,则可以直接使用类名的全小写作为作为别名:

<mapper namespace="dao.StudentDao">
    <select id="findStudentById" parameterType="int" resultType="myStu">
        select * from student where id = #{id}
    </select>

    <select id="findStudentById" parameterType="int" resultType="student">
        select * from student where id = #{id}
    </select>
</mapper>

后面使用该 sql 跟使用普通 sql 一样。

 

3.3、返回 List 集合

有时候我们要查询的数据不止一条,比如:模糊查询,全表查询等,这时候返回的数据可能不止是一条数据,对于多数据的处理可以存放在List集合中。

返回 list 集合时,resultType 返回值类型是集合内元素的数据类型,而不是 list。

示例:

<mapper namespace="dao.StudentDao">

    <select id="findStudentAll" resultType="entity.Student">
        select * from Student
    </select>

    <select id="selectMultiStudent" resultType="entity.Student">
        select * from Student where id = #{myId} or name = #{myName}
    </select>

</mapper>

测试代码:

//Mapper接口类
public interface StudentDao {
    public List<Student> findStudentAll();
    public List selectMultiStudent(@Param("myId") Integer id, @Param("myName") String name);
}

//测试代码
List list = studentDao.selectMultiStudent(1, "李四");
List list2 = studentDao.findStudentAll();

 

3.4、返回Map类型

MyBatis 还支持将查询的数据封装成Map

如果查询的结果是一条,mybatis 会将查询出来的结果以数据库列名作为 key,列值作为 value 存入到 Map 中。

示例:

<mapper namespace="dao.StudentDao">
    <select id="getStuAsMap" resultType="map">
        select * from Student where id = #{id}
    </select>
</mapper>

测试代码:

//mapper接口类:
public interface StudentDao {
    public Map<String, Object> getStuAsMap(int id);
}

//测试代码:
Map<String, Object> map = studentDao.getStuAsMap(1);
System.out.println(map);   //输出结果:{name=wen, id=1, age=12}

 

如果查询的结果是多条数据,我们也可以把查询的数据以{表中某一字段名, JavaBean}方式来封装成Map

示例:

<mapper namespace="dao.StudentDao">    
     <!--注意 resultType 返回值类型,不再是 'map',而是 Map 的 value 对应的 JavaBean 类型 -->
    <select id="getAllEmpsAsMap" resultType="employee">
        select * from t_employee
    </select>
</mapper>

测试代码:

// mapper接口类:
// 查询所有员工的信息,把数据库中的id字段作为key,对应的 value 封装成 Employee 对象。@MapKey 中的值表示用数据库中的哪个字段名作 key
@MapKey("id")
Map<Integer, Employee> getAllEmpsAsMap();

 

4、resultMap

当数据库的列名和类的属性名能一一对应时,查询返回的数据能正确赋值给对应类的属性。但是当查询的返回的数据中列名和类的属性名不同时,此时类无法被正确赋值,此时我们可以使用 resultMap 属性。使用 resultMap 可以将指定的列名映射到特定的 java 类的属性。

示例:

<mapper namespace="dao.StudentDao">
<resultMap id="studentMap" type="entity.Student"> <!--column指定的是数据库的列名,property指定的是对应的实体类的属性。也就是希望将column指定的列名赋值给该实体类的property指定的属性--> <!--主键列使用id标签--> <id column="id" property="myid"></id> <!--非主键列使用result标签--> <result column="name" property="myname"></result> <result column="age" property="myage"></result> </resultMap>
<select id="findStudentAll" resultMap="studentMap"> select * from Student </select> </mapper>

测试代码:

//mapper接口类:
public interface StudentDao {
    public Student findStudentById(int id);
}

//测试代码:
System.out.println(studentDao.findStudentAll());  //输出结果:[Student{id=1, name='wen', age=12}, Student{id=2, name='李四', age=14}]

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

 

使用 resultMap 可以将指定的列名映射到特定的 java 类的属性。或者是我们也可以简单地在 sql 语句中使用 as 来为返回结果起个别名,以便对应上类的属性名。

比如:

<mapper namespace="dao.StudentDao">
    <select id="findStudentById" parameterType="int" resultType="myStu">
        select id, name as myname, age as myage from student where id = #{id}
    </select>
</mapper>

 

5、给sql映射文件传递参数

5.1、通过 @param() 传递命名参数

在 sql 映射文件中,如果需要给 sql 语句传递多个参数,我们可以定义参数为一个 java 类,除以之外,我们也可以通过 @param("参数名") 的形式来传递多个参数。

下面以 mapper代理开发模式使用为例:

sql 映射文件内容:

<?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="dao.StudentDao">
    <!--通过#{}来获取自定义参数名-->
    <select id="selectMultiStudent" resultType="entity.Student">
        select * from Student where id = #{myId} or name = #{myName}
    </select>
</mapper>

接口类:

package dao;

import entity.Student;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface StudentDao {
    //通过@Param命名参数来传递多个参数,并且给参数命名
    public List selectMultiStudent(@Param("myId") Integer id, @Param("myName") String name);
}

使用:

public class Test02 {
    @Test
    public void test01() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = factory.openSession();

        StudentDao studentDao = session.getMapper(StudentDao.class);

        List list = studentDao.selectMultiStudent(1, "李四");
        System.out.println(list);

        session.close();
    }
}

 

5.2、通过 map 集合传递多个参数

我们可以通过一个 map 集合来给 sql 语句传递多个参数,在 sql 映射中通过 #{key} 的形式来获取参数。

下面以 mapper代理开发模式使用为例。sql 映射文件内容:

<?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="dao.StudentDao">
    <select id="selectByMap" resultType="entity.Student">
        select * from Student where id = #{myId} or name = #{myName}
    </select>
</mapper>

接口类:

package dao;

import entity.Student;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

public interface StudentDao {
    public List selectByMap(Map map);
}

使用:

public class Test02 {

    @Test
    public void test01() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = factory.openSession();

        StudentDao studentDao = session.getMapper(StudentDao.class);

        Map map = new HashMap();
        map.put("myId", 1);
        map.put("myName", "李四");
        List list = studentDao.selectByMap(map);
        System.out.println(list);

        session.close();
    }
}

 

6、动态SQL

动态 SQL 是 MyBatis 的强大特性之一。在实际应用开发过程中,我们往往需要写复杂的 SQL 语句,需要拼接,而拼接SQL语句又稍微不注意,由于引号,空格等缺失可能都会导致错误。Mybatis提供了动态SQL,也就是可以根据用户提供的参数,动态决定查询语句依赖的查询条件或SQL语句的内容。

常见的动态SQL标签有:

  • if
  • where
  • foreach
  • choose (when, otherwise)

 

6.1、if 标签

语法:

<if test="条件">SQL语句</test>

<!-- 示例-->
<if test="age != null">
    age > #{age}
</if>

当 test 里面的条件为 true 时,会将该标签里面包含的 SQL 语句拼接到最终生成的 SQL 语句中。

示例:

<mapper namespace="dao.StudentDao">
    <select id="selectByIf" resultType="entity.Student">
        select * from Student where
        <if test="name != null and name != ''">
            name = #{name}
        </if>
<if test="age != null"> and age > #{age} </if> </select> </mapper>

测试代码:

//接口类
public interface StudentDao {
    public Student selectByIf(Student Student);
}

//测试使用
Student student = new Student();
student.setName("wen");
student.setAge(10);
studentDao.selectByIf(student);

上面的 sql 映射文件的写法,当 if 的判断为 true 时,才会将 if 标签里面的 SQL 语句拼接到最终的 SQL 语句当中。

 

6.1.1、if 标签的存在问题

使用 if 标签会有个问题,例如上面的判断当中,当第一个条件不满足,而第二个条件满足时,或者是都不满足时,此时语法变成了:

select * from Student where and age > ?
select * from Student where

上面的两个 SQL 语句将直接报错。

此时我们可以在 where 条件后面加一个永远为真的判断以此来解决该问题,如下:

<mapper namespace="dao.StudentDao">
    <select id="selectByIf" resultType="entity.Student">
        select * from Student where 1=1 
        <if test="name != null and name != ''">
            and name = #{name}
        </if>

        <if test="age != null">
            and age > #{age}
        </if>
    </select>
</mapper>

或者是使用 <where> 标签来解决也行。

 

6.1.2、mybatis传单个参数和<if>标签同时使用的问题

如果 mybatis 的 SQL mappper 中只接收了单个参数,而且是基本数据类型的,比如一个字符串、一个整型数据等,这时候使用 if  标签来判断该参数是否为空时,可能会报错,提示信息类似:nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'xxx' in 'class java.lang.Integer' 。

解决方法:此时需要去掉 if 标签,或者将该参数放在 map 或者一个 JavaBean 中都行。

可参考:https://www.cnblogs.com/laxiag-4u/p/9088267.html

 

6.2、where 标签

为了避免 if 标签存在的问题,我们可以将 where 标签和 if 标签配合使用。

<where> 标签里面可以包含多个 if 标签,当这些 if 标签中有一个为 true 时,则 where 会自动往 SQL 语句中增加 where 关键字,并且会自动剔除掉 if 标签返回的多余的 and、or 等关键字。否则的话,where 关键字不会增加到 SQL 语句当中。这样就能完美解决 if 标签可能存在的问题。

语法:

<where>
    <if test="条件">
        SQL语句
    </if>
    <if test="条件">
        SQL语句
    </if>
</where>

示例:

<mapper namespace="dao.StudentDao">
    <select id="selectByIfAndWhere" resultType="entity.Student">
        select * from Student
        <where>
            <if test="name != null and name != ''">
                name = #{name}
            </if>
            <if test="age != null">
                or age > #{age}
            </if>
        </where>
    </select>
</mapper>

测试代码:

//接口类
public interface StudentDao {
    public List selectByIfAndWhere(Student student);
}

//测试使用:
Student student = new Student();
student.setAge(13);
studentDao.selectByIfAndWhere(student)

<where> 标签会自动判断是否需要补全 where 关键字,并且会自动剔除多余的 and、or 等 sql 关键字,通过 where 标签就可以正常使用 if 标签了。

 

6.3、foreach 标签

mybatis 的 foreach 标签经常用于遍历集合,构建 in 条件语句或者批量操作语句。

<foreach> 标签的属性主要有 item,index,collection,open,separator,close。

  • collection:表示迭代集合的名称。必选
    • 如果 collection 类型是一个List的时候,则 collection 的值为 list;如果是一个 array 数组的时候,collection 的值为 array;如果collection类型为map,则 collection 的值可以是该 map 的某个 key 值,foreach 将该 key 的 value 值作为遍历对象进行遍历
  • item:表示集合中的每一个元素。必选
  • index:该元素的索引。可选
  • open:该语句开始时的字符。可选
  • separator:每个元素之间的分隔符。可选
  • close:该语句结束时的字符。可选

 

使用示例:

参数为 list 类型:

<?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="dao.StudentDao">
    <!--下面代码最终实际会生成SQL语句:select * from Student where id in (1,2...)-->
    <select id="selectByForeach" resultType="entity.Student">
        select * from Student where id in
        <foreach collection="list" index="index" item="myid" open="(" close=")" separator=",">
            #{myid}
        </foreach>
    </select>
</mapper>

<foreach> 标签包含的语句,如果 item 的值是一个对象的话,则应该取对象的属性值,例如 #{itemObj.name}

测试代码:

//接口类
public interface StudentDao {
    public List selectByForeach(List list);
}

//测试代码
List list = new ArrayList();
list.add(1);
list.add(2);
System.out.println( studentDao.selectByForeach(list) );

 

参数为 map 类型:

<?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="dao.StudentDao">
    <!--下面代码最终实际会生成SQL语句:select * from Student where name like ... and id in (1,2...)  -->
    <select id="selectByForeach2" resultType="entity.Student">
        select * from Student where name like "%"#{myname}"%" and id in
        <foreach collection="idlist" index="index" item="myid" open="(" close=")" separator=",">
            #{myid}
        </foreach>
    </select>
</mapper>

参数为 map 类型时,collection 的值是选定的需要用来遍历的 value 值的 key,即上面会将 map 的 idlist 的值来进行 foreach 遍历。

测试代码:

//接口类
public interface StudentDao {
    public List selectByForeach2(Map map);
}

//测试代码
 List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
Map<String, Object> map = new HashMap<>();
map.put("idlist", list);
map.put("myname", "wen");
System.out.println(studentDao.selectByForeach2(map));

 

6.4、代码片段(复用sql代码)

如果有一段 sql 代码在多个地方都会出现,我们可以将该段代码独立为 sql 代码片段,这样就可以在其他地方直接引用该片段而不用重复书写。

<?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="dao.StudentDao">
    <!-- 定义代码片段 -->
    <sql id="mysql">
        select id,name,email,age from student
    </sql>
    
    <!-- 下面使用代码片段 -->
    <select id="findStudentById" parameterType="int" resultType="myStu">
        <include refid="mysql" />
    </select>
</mapper>

 

posted @ 2021-09-06 00:24  wenxuehai  阅读(3135)  评论(0编辑  收藏  举报
//右下角添加目录