MyBatis 3. XML映射文件

XML 映射文件

1. 概述

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

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

  • cache – 给定命名空间的缓存配置。
  • cache-ref – 其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

2. 增删改查实例

1.userCrudMapper 接口

public interface UserCrudMapper {
   //mybatis 允许增、删、改直接定义Integer、Long、Boolean返回值
   public int addUser(User user);

   public int deleteUser(User user);

   public int updateUser(User user);

   public User getUser(Integer id);

   public List<User> getAllUsers();
}

2.sql 映射文件 UserCrudMapper.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="com.chen.dao.UserCrudMapper">
    <select id="getUser" parameterType="int" resultType="user">
        SELECT * FROM  users where id = #{id}
    </select>

    <insert id="addUser" parameterType="user">
        INSERT INTO users (name,age) VALUES (#{name},#{age})
    </insert>

    <update id="updateUser" parameterType="user">
        UPDATE  users SET name=#{name},age=#{age} WHERE id=#{id}
    </update>

    <delete id="deleteUser" parameterType="user">
        DELETE FROM users WHERE  id=#{id}
    </delete>

    <select id="getAllUsers" resultType="user">
        SELECT * FROM users
    </select>
</mapper>

3.在 mybatis 中配置

<mappers>
    <mapper resource="UserCrudMapper.xml"/>
</mappers>

4.测试方法

getSqlSessionFactory 方法

private SqlSessionFactory getSqlSessionFactory() throws IOException {
    String resource = "mybatis-config.xml";

    InputStream inputStream = Resources.getResourceAsStream(resource);

    return new SqlSessionFactoryBuilder().build(inputStream);
}

test 方法

@Test
public void testCrud() throws IOException {
   SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
   //默认为手动提交
   SqlSession sqlSession = sqlSessionFactory.openSession(true);
   try {
      UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
      System.out.println(mapper.getAllUsers());
      System.out.println(mapper.addUser(new User(0, "test", 10)));
      System.out.println(mapper.updateUser(new User(6,"ga",21)));
      System.out.println(mapper.getUser(6));
      System.out.println(mapper.deleteUser(6));
   } finally {
      sqlSession.close();
   }
}

3. INSERT 获取自增主键值

MyBatis 使用 JDBC 的 getGeneratedKeys(仅对 insert 和 update 有用) 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。

keyProperty 唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。

sql 映射文件:

<insert id="addUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users (name,age) VALUES (#{name},#{age})
</insert>

测试方法:

@Test
public void testGeneratedKeys() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession sqlSession = sqlSessionFactory.openSession(true);
    try {
        UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
        User user = new User(0, "test", 10);
        System.out.println(mapper.addUser(user));
        //此时可以打印出主键值
        System.out.println(user.getId());
    } finally {
        sqlSession.close();
    }
}

4. 参数处理

4.1. 单个参数

mybatis 不会做特殊处理,直接通过 #{参数名} 来取出参数值。

4.2. 多个参数

多个参数时候(例如:接口方法为public User getUserByIdAndName(Integer id,String name)),如果像单个参数时候直接通过#{参数名} 来取出参数值,会报出Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2] 之类的异常。

这是因为 mybatis 会将多个参数封装成 map ,而 map 的 key 是 param1 ... paramN 或者是参数的索引 ,而 value 即传入的参数值。#{}就是从 map 中获取指定 key 值。

即此时的 sql 映射文件修改为:

<select id="getUserByIdAndName" resultType="user">
    SELECT * FROM  users where id = #{param1} and name =#{param2}
</select>   

或者

<select id="getUserByIdAndName" resultType="user">
    SELECT * FROM  users where id = #{0} and name =#{1}
</select>

4.3. @Param 注解

为参数使用 @Param起一个名字,mybatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字。

例如 dao 接口的方法为:public User getUserByIdAndName(@Param("id") Integer id, @Param("name") String name)

此时 sql 映射文件修改为

<select id="getUserByIdAndName" resultType="user">
    SELECT * FROM  users where id = #{id} and name =#{name}
</select>

4.4. POJO参数

当这些参数属于一个 POJO 时,我们直接传递 POJO,直接通过#{参数名} 取出传入 pojo 的字段。

4.5. Map

我们也可以直接传入 map,dao 接口的方法为:public User getUserByMap(Map<String,Object> map); ,此时直接通过 #{key} 来取出 map 对应的值。

sql 映射文件为

<select id="getUserByMap" resultType="user">
    SELECT * FROM  users where id = #{id} and name =#{name}
</select>

测试代码

@Test
public void testParamByMap() throws IOException {
   SqlSession sqlSession = getSqlSessionFactory().openSession(true);
   try {
    UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
   Map<String,Object> map = new HashMap<>();
   map.put("id",7);
   map.put("name","test");
   System.out.println(mapper.getUserByMap(map));
       } finally {
        sqlSession.close();
    }
}

如果传入参数是 Collection(例如 List、Set)类型或者数组,mybatis 会将传入的参数封装到 map 中。此时 Collection 类型的 key 为 collection,如果是 List 还可以使用 list,而数组的 key 为 array。

4.6. ${}#{} 的区别

将 sql 映射文件修改为:

<select id="getUserByIdAndName" resultType="user">
    SELECT * FROM  users where id = ${id} and name = #{name}
</select>

此时log4j在控制台打印出:

DEBUG 02-18 14:27:36,381 ==>  Preparing: SELECT * FROM users where id = 8 and name = ?   (BaseJdbcLogger.java:145) 

DEBUG 02-18 14:27:36,434 ==> Parameters: test(String)  (BaseJdbcLogger.java:145) 

DEBUG 02-18 14:27:36,473 <==      Total: 1  (BaseJdbcLogger.java:145) 

可以看出 sql 语句为 :SELECT * FROM users where id = 8 and name = ?

所以:

  • #{} :是以预编译的形式,将参数设置到 sql 语句;可以防止 sql 注入
  • ${} :直接将参数拼装到 sql 语句。

默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值 。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,像 ORDER BY,你可以这样来使用:

ORDER BY ${columnName}

这里 MyBatis 不会修改或转义字符串。

5. SELECT

5.1. 返回结果封装为 Map

如何将多条记录封装成 Map<Integer,User> ,而 key 为这条记录的主键,值是封装后的 user 对象。

可以通过 @MapKey("id") 注解告诉 mybatis 封装这个 map 时候使用哪个属性作为键。例如:

@MapKey("id")
public Map<Integer,User> getUsersByNameLikeReturnMap(String name);

此时 sql 映射文件:

<select id="getUsersByNameLikeReturnMap" resultType="user">
    SELECT * FROM users where name like #{name}
</select>

测试方法:

@Test
public void testReturnMap() throws IOException {
    SqlSession sqlSession = getSqlSessionFactory().openSession(true);
    try {
        UserCrudMapper mapper = sqlSession.getMapper(UserCrudMapper.class);
        Map<Integer, User> map = mapper.getUsersByNameLikeReturnMap("%test%");

        System.out.println(map);
    } finally {
        sqlSession.close();
    }
}

5.2. ResultMap

自动映射有三种方法:

1.通过 autoMappingBehavior 开启自动映射的功能,其默认是PARTIAL。唯一的要求是列名和 javaBean 属性名一致。当 autoMappingBehavior 设置为 null 则会取消自动映射。

2.如果数据库字段命名规范,POJO属性符合驼峰命名法,如 A_COLUMN -> aColumn,可以通过设置 mapUnderscoreToCamelCase 为 true 来开启自动驼峰命名规则映射。

3.通过自定义 resultMap ,实现高级结果集映射。

resultMap 元素是 MyBatis 中最重要最强大的元素。它就是让你远离 90%的需要从结果集中取出数据的 JDBC 代码的那个东西, 而且在一些情形下允许你做一些 JDBC 不支持的事 情。 事实上, 编写相似于对复杂语句联合映射这些等同的代码, 也许可以跨过上千行的代码。 ResultMap 的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们 的关系。

ResultMap 标签的概念视图

  • constructor: 类在实例化时,用来注入结果到构造方法中
    • idArg :ID 参数;标记结果作为 ID 可以帮助提高整体效能
    • arg : 注入到构造方法的一个普通结果
  • id :一个 ID 结果;标记结果作为 ID 可以帮助提高整体效能
  • result :注入到字段或 JavaBean 属性的普通结果
  • association :一个复杂的类型关联;许多结果将包成这种类型
    • 嵌入结果映射 :结果映射自身的关联,或者参考一个
  • collection:复杂类型的集合
    • 嵌入结果映射 :结果映射自身的集,或者参考一个
  • discriminator :使用结果值来决定使用哪个结果映射
    • case:基于某些值的结果映射
    • 嵌入结果映射 :这种情形结果也映射它本身,因此可以包含很多相 同的元素,或者它可以参照一个外部的结果映射。

ResultMap 的属性

属性 描述
id 当前命名空间中的一个唯一标识,用于标识一个result map
type 类的全限定名, 或者一个类型别名
autoMapping 如果设置这个属性,MyBatis将会为这个ResultMap开启或者关闭自动映射。这个属性会覆盖全局的属性autoMappingBehavior。默认值为:unset。

例子

dao 接口

public interface UserMapper {
   public User getUserById(Integer id);
}

配置 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="com.chen.dao.UserMapper">
    <resultMap id="UserResultMap" type="user">
        <!-- id 指定主键-->
        <!-- column 指定列名,property 指定对应 pojo 的属性-->
        <id column="id" property="id"/>
        <!-- result 指定其他列-->
        <result column="name" property="name"/>
        <result column="age" property="age"/>
    </resultMap>

    <select id="getUserById" resultMap="UserResultMap">
        SELECT * FROM  users WHERE id = #{id}
    </select>
</mapper>

测试方法

@Test
public void testResultMap() throws IOException {
   SqlSession sqlSession = getSqlSessionFactory().openSession(true);
   try {
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      User user = mapper.getUserById(7);
      System.out.println(user);
   } finally {
      sqlSession.close();
   }
}

关联查询

在修改完数据库后,对于 user 类关联 department 类。

user 类

public class User {
   private int id;
   private String name;
   private int age;
   private Department department;

   //get,set方法
}

department 类

public class Department {
   private Integer id;
   private String name;

    //get,set方法
}

在原有的 dao 接口上增加

public User getUserAndDeptById(Integer id);
1.联合查询,使用级联属性封装结果集

此时 sql 映射文件添加

<resultMap id="MyResultMap" type="user">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="age" property="age"/>
    <result column="did" property="department.id"/>
    <result column="department_name" property="department.name"/>
</resultMap>

<select id="getUserAndDeptById" resultMap="MyResultMap">
    SELECT u.id id,
    u.name name,
    u.age age,
    u.d_id d_id,
    d.id did,
    d.department_name  department_name
    FROM users u,department d
    WHERE u.d_id=d.id AND u.id=#{id}
</select>

测试方法

@Test
   public void testResultMap() throws IOException {
      SqlSession sqlSession = getSqlSessionFactory().openSession(true);
      try {
         UserResultMapMapper mapper = sqlSession.getMapper(UserResultMapMapper.class);

         User user = mapper.getUserAndDeptById(7);
         System.out.println(user);
      } finally {
         sqlSession.close();
      }
   }
2.association 元素来实现多表查询

通过 association 元素来定义关联的单个对象的规则。

sql 映射文件添加

<resultMap id="AssociationResultMap" type="user">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="age" property="age"/>

    <!-- association 可以指定关联的 javaBean 对象-->
    <!-- property 指定哪个属性是关联的对象-->
    <!-- javaType 指定这个属性对象的类型-->
    <association property="department" javaType="com.chen.entity.Department">
        <id column="did" property="id"/>
        <result column="department_name" property="name"/>
    </association>
</resultMap>
3.association 元素进行分步查询

还可以通过 association 元素的 select 属性进行分步查询,select 属性是另外一个映射语句的 ID。并且将会通过 column 属性来传递类名。

新建 DepartmentMapper 接口

public interface DepartmentMapper {
   public Department getDepartmentById(Integer id);
}

新建 DepartmentMapper.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="com.chen.dao.DepartmentMapper">

    <select id="getDepartmentById" resultType="com.chen.entity.Department">
        SELECT id,department_name name FROM department WHERE id=#{id}
    </select>
</mapper>

sql 映射文件添加

<resultMap id="AssociationResultMap2" type="user">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="age" property="age"/>

    <association property="department"
  select="com.chen.dao.DepartmentMapper.getDepartmentById"
                 column="d_id"
    />
</resultMap>

<select id="getUserAndDeptByIdStep" resultMap="AssociationResultMap2">
    SELECT * FROM users where id = #{id}
</select>

测试代码

@Test
public void testAssociation() throws IOException {
   SqlSession sqlSession = getSqlSessionFactory().openSession(true);
   try {
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      User user = mapper.getUserAndDeptByIdStep(7);
      System.out.println(user);
   } finally {
      sqlSession.close();
   }
}
4.延迟加载

用分步查询在每次查询 user 对象时,都会同时查询其 department 级联属性。而通过 延迟加载 可以使我们在使用 department 级联属性再去查询。

延迟加载可以通过在 mybatis-config.xml 中的 settings 元素中的 lazyLoadingEnabled 子元素和 aggressiveLazyLoading 子元素来控制。

1.lazyLoadingEnabled

延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。其默认值为 false 。

2.aggressiveLazyLoading

当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods)。其默认值为 false (true in ≤3.4.1)。

在 mybatis3.4.1 版本之前 aggressiveLazyLoading 属性值为 true,所以我们需要显式地指定配置的值。

mybatis-config.xml 中增加配置

<settings>
    <setting name="lazyLoadingEnabled " value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

当我没有使用 user 的 department 属性时,mybatis 并没有去查询 department 属性,日志如下

DEBUG 02-22 15:23:38,703 ==>  Preparing: SELECT * FROM users where id = ?   (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:23:38,752 ==> Parameters: 7(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:23:38,880 <==      Total: 1  (BaseJdbcLogger.java:145) 

而当我使用时,mybatis 就会自动去查询该属性,日志如下

DEBUG 02-22 15:19:57,262 ==>  Preparing: SELECT * FROM users where id = ?   (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:19:57,309 ==> Parameters: 7(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:19:57,423 <==      Total: 1  (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:19:57,424 ==>  Preparing: SELECT id,department_name name FROM department WHERE id=?   (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:19:57,424 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-22 15:19:57,437 <==      Total: 1  (BaseJdbcLogger.java:145) 
5.collection 定义关联集合封装规则

一个 user 只属于一个department ,而一个 department 可以有很多 user ,我们可以通过 collection 元素来映射嵌套结果集合到 List 中。

Department 类增加一个字段

private List<User> userList;

增加 DepartmentMapper 接口方法

public Department getDepartmentAndUsersById(Integer id);

修改 DepartmentMapper.xml

<resultMap id="deptAndUser" type="com.chen.entity.Department">
    <id column="did" property="id"/>
    <result column="department_name" property="name"/>
    <!-- collection定义集合类型的属性封装规则-->
    <collection property="userList" ofType="com.chen.entity.User">
        <id column="uid" property="id"/>
        <result column="name" property="name"/>
        <result column="age" property="age"/>
    </collection>
</resultMap>

<select id="getDepartmentAndUsersById" resultMap="deptAndUser">
     SELECT d.id did,
     d.department_name department_name,
     u.id uid,
     u.name name,
     u.age age
     FROM  department d
     LEFT JOIN users u
     ON d.id=u.d_id
     WHERE d.id=#{id}
</select>
6.collection 分步查询和延迟加载

增加 DepartmentMapper 接口方法

public Department getDepartmentAndUsersByIdStep(Integer id);

增加 UserMapper 接口方法

public List<User> getUsersByDeptId(Integer id);

修改 UserMapper.xml

<!--查询属于该 department 的 user-->
<select id="getUsersByDeptId" resultType="com.chen.entity.User">
    SELECT * FROM users where d_id = #{id}
</select>

修改 DepartmentMapper.xml

xml
<!--collection分步查询-->
<select id="getDepartmentAndUsersByIdStep" resultMap="deptAndUserStep">
SELECT id,department_name name FROM department WHERE id=#{id}
</select>
<resultMap id="deptAndUserStep" type="com.chen.entity.Department">
<id column="id" property="id"/>
<result column="department_name" property="name"/>
<collection property="userList" ofType="com.chen.entity.User" select="com.chen.dao.UserMapper.getUsersByDeptId" column="id"/>
</resultMap>

与 association 元素的延迟加载类似,配置 mybatis-config.xml 中的 settings 元素中的 lazyLoadingEnabled 子元素和 aggressiveLazyLoading 子元素即可。

7.分步查询传递多列值和延迟加载的fetchType

在分步查询中,如果要传递多列的值,可以将其封装成 map 后传递,此时 column="{key1=column1,key2=column2}"

在 association 和 collection 中,有 fetchType 这个属性,有效值为 lazyeager,其默认为 lazy ,即延迟加载,如果使用eager,它将取代全局配置参数lazyLoadingEnabled

discriminator 鉴别器

有时一个单独的数据库查询也许返回很多不同 (但是希望有些关联) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的, 还有包括类的继承层次结构。 鉴别器非常容易理解,因为它的表现很像 Java 语言中的 switch 语句。

鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型(尽管字符串在很多情形下都会有用)。

例子:

如果 user 的年龄等于 20,就查询部门信息,而如果不等于 20,则不查询。

增加 UserMapper 接口方法

public User getUsersByDiscriminator(Integer id);

修改 UserMapper.xml 如下

<resultMap id="usersByDiscriminator" type="com.chen.entity.User">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="age" property="age"/>

    <!-- column 指定判断哪列-->
    <discriminator javaType="int" column="age">
        <case value="20" resultType="com.chen.entity.User">
            <association property="department"
                         select="com.chen.dao.DepartmentMapper.getDepartmentById"
                         column="d_id"
            />
        </case>
    </discriminator>

</resultMap>

<select id="getUsersByDiscriminator" resultMap="usersByDiscriminator">
    SELECT * FROM users where id = #{id}
</select>

猜你喜欢

转载自blog.csdn.net/qq_37138933/article/details/79496192