Mybatis的映射、动态sql与关联查询

输入映射

parameterType (输入类型)

输入类型的数据参数就是被映射到预期 sql 语句上,完成完整的 sql 语句,再传递到程序去执行操作数据库。

通过上一章的学习,我们可以发现输入类型可以传递简单类型,或者传入一个 pojo 对象。其实还可以传入一个 pojo 包装对象(Pojo 类中的一个属性是另外一个 pojo)

根据用户名模糊查询用户信息,查询条件放到 QueryVo 的 user 属性中。
<!--编写 mapper.xml 文件-->
<!--使用包装类型查询用户-->
<select id="findUserByQueryVo" parameterType="QueryVo" resultType="User">
        select * from user where username like '%${user.username}%'
</select>
//编写QueryVo
public class QueryVo {
    //包含其他的pojo
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return "QueryVo [user=" + user + "]";
    }
}
//Mapper接口
public interface UserMapper {
    List<User> findUserByQueryVo(QueryVo vo);
}
//测试
public class TestMapper {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws IOException {
        // 创建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        this.sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }
    @Test
    public void testQueryUserByQueryVo() {
        // mybatis和spring整合,整合之后,交给spring管理
        SqlSession sqlSession = this.sqlSessionFactory.openSession();
        // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 使用userMapper执行查询,使用包装对象
        QueryVo queryVo = new QueryVo();
        // 设置user条件
        User user = new User();
        user.setUsername("小);
        // 设置到包装对象中
        queryVo.setUser(user);
        // 执行查询
        List<User> list = userMapper.queryUserByQueryVo(queryVo);
        for (User u : list) {
            System.out.println(u);
        }
        // mybatis和spring整合,整合之后,交给spring管理
        sqlSession.close();
        }
}

说明一下:后面测试时,@before 初始化操作不再重复写,直接在测试方法里获取 SqlSession。

还有一点需要注意一下

默认情况下,使用#{}格式的语法会导致 MyBatis 创建预处理语句属性并安全地设置值。这样做更安全,更迅速,通常也是首选做法,不过有时你只是想直接在 SQL 语句中插入一个不改变的字符串。比如,:ORDER BY ${columnName}。这里 MyBatis 不会修改或转义字符串。以这种方式接受从用户输出的内容并提供给语句中不变的字符串是不安全的,会导致潜在的 SQL 注入攻击,因此要么不允许用户输入这些字段,要么自行转义并检验。

输出映射

resultType (输出类型)

同样的,输出类型可以是简单类型、pojo 对象或者 pojo 列表,这些我们在前面已经了解过,不在啰嗦。

resultType 可以指定将查询结果映射为 pojo,但需要 pojo 的属性名和 sql 查询的列名一致方可映射成功。如果 sql 查询字段名和 pojo 的属性名不一致呢? Mybatis 为我们提供了一个强大的输出映射 resultMap。

resultMap

尽管 sql 查询字段名和 pojo 的属性名不一致,可以通过 resultMap 将字段名和属性名作一个对应关系,将查询结果映射到 pojo 对象中。

resultMap 可以实现将查询结果映射为复杂类型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。

例如:当我们使用resultType时

<select id="selectUsers" resultType="User">
  select id, username, hashedPassword
  from some_table
  where id = #{id}
</select> 

其实MyBatis会自动创建一个 ResultMap,基于数据库属性名来映射列到 JavaBean 的属性上,所以必须得保证数据库字段名和JavaBean属性一致。
如果列名没有精确匹配,你可以在列名上使用 select 字句的别名(一个 基本的 SQL 特性)来匹配属性。

<select id="selectUsers" resultType="User">
  select
    user_id             as "id",
    user_name           as "userName",
    hashed_password     as "hashedPassword"
  from some_table
  where id = #{id}
</select>
但是,Mybatis给我们提供了简便强大的resultMap
例如,我们的实体类userId和数据库字段user_id不匹配

public class Orders implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    private Integer id;
    private Integer userId;
    private String number;
    private Date createtime;
    private String note;
}  
 <!--type为返回类型-->
<resultMap type="com.pngyul.mybatis.pojo.Orders" id="oederResultMap">
    <!--property为属性,column为对应的数据库字段,这样再查询就能匹配上-->
    <result property="userId" column="user_id" />
</resultMap>

<select id="findUserById" parameterType="int" resultMap="oederResultMap">
    select * from orders where id =#{id}
 </select>

动态sql

MyBatis 的强大特性之一便是它的动态 SQL。我们使用 JDBC 时会手动拼接条件,Mybatis 解决了这一情况。

if标签

动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分。

<!--根据id和姓名查找用户-->
<select id="findUserByIdAndUsername" parameterType="User" resultType="User">
      select * from user 
      where 1=1
      <if test="id !=null and id!=''">
          and id=#{id}
      </if>
      <if test="username !=null and username !=''">
          and username =#{username}
      </if>
</select>
@Test
public void testQueryUserByWhere(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setId("10");
    user.setUsername("张三");

    List<User> UserList = userMapper.queryUserByWhere(user);
    for(User u : UserList ){
        System.out.println(u);
    }
    sqlSession.close();
}

choose, when, otherwise 标签

有些时候,我们不想用到所有的条件语句,而只想从中选择。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

例如:提供了 “id” 就按 “id” 查找,提供了 “username” 就按 “username” 查找,若两者都没有提供,就按照 otherwise 里面的条件查找*。其实是蛮鸡肋的一功能。

<!-- 根据id和名字查找用户 -->
<select id="findUserByIdAndUsername" parameterType="User" resultType="User">
      select * from user 
      where 1=1
      <choose>
          <when test="id != null">
              AND id = #{id}
          </when>
          <when test="username != null and username !=''">
              AND username = #{username}
          </when>
          <otherwise>
              AND sex = 1
          </otherwise>        
      </choose>
</select>

where 标签

上面的例子我们都需要写 where 1=1,因为如果不写,条件拼接就会出现很多错误,Mybatis 通过 where 标签帮我们解决了这个问题。

<select id="findUserByIdAndUsername" parameterType="com.cad.domain.User" resultType="com.cad.domain.User">
        select * from user 
      <where>
          <if test="id != null">
              AND id = #{id}
          </if>
          <if test="username != null and username !=''">
              AND username = #{username}
          </if>   
      </where>
</select>

foreach 标签

动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。

//一个创建对象,封装一个List和一个数组
public class QueryVo {

    private List<Integer> idList;
    private Integer[] ids;


    public List<Integer> getIdList() {
        return idList;
    }

    public void setIdList(List<Integer> idList) {
        this.idList = idList;
    }

    public Integer[] getIds() {
        return ids;
    }

    public void setIds(Integer[] ids) {
        this.ids = ids;
    }

    @Override
    public String toString() {
        return "QueryVo [idList=" + idList + ", ids=" + Arrays.toString(ids) + "]";
    }

}
<!-- 根据多个id查找用户信息 -->

一般数组
<select id="findUserByids" parameterType="QueryVo" resultType="User">
       SELECT * FROM user
    <where>
        <!-- foreach标签,进行遍历 -->
        <!-- collection:遍历的集合,这里是QueryVo的ids属性 -->
        <!-- item:遍历的项目,可以随便写,,但是和后面的#{}里面要一致 -->
        <!-- open:在前面添加的sql片段 -->
        <!-- close:在结尾处添加的sql片段 -->
        <!-- separator:指定遍历的元素之间使用的分隔符 -->
        <foreach collection="array" item="item" open=" id IN (" close=")"
            separator=",">
            #{item}
        </foreach>
    </where>
</select>

List集合
<select id="findUserByIdList" parameterType="QueryVo" resultType="User">
        SELECT * FROM user
    <where>
        <foreach collection="list" item="item" open=" id IN (" close=")"
            separator=",">
            #{item}
        </foreach>
    </where>
</select>
@Test
public void testFindUserByids(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    Integer[] ids = new Integer[3];
    ids[0] = 1;
    ids[1] = 10;
    ids[2] = 27;

    List<User> list = userMapper.findUserByids(ids);
    for(User u : list ){
        System.out.println(u);
    }
    sqlSession.close();
}

@Test
public void testFindUserByIdList(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<Integer> idList = new ArrayList<>();
    idList.add(1);
    idList.add(10);
    idList.add(27);

    List<User> list = userMapper.findUserByIdList(idList);
    for(User u : list ){
        System.out.println(u);
    }
    sqlSession.close();
}

如果我们的 list 在某个对象内部,那么 foreach 时,collection 可以直接使用 list 名字。如果我们直接 new 了一个数组或者 list 集合,collection 属性必须使用 array 或者 list,这样才能遍历到。

关联查询

前面我们讲了将简单的数据映射到对象中,可惜实际工作中的任务不总是这么简单,往往多个对象之间存在着各种关联关系,我们来看看使用 Mybatis 怎么处理。我们还是需要使用到 resultMap

一对一映射

1、我们创建 Order 表与 User 表关联

CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL COMMENT '下单用户id',
  `number` varchar(32) NOT NULL COMMENT '订单号',
  `createtime` datetime NOT NULL COMMENT '创建订单时间',
  `note` varchar(100) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`),
  KEY `FK_orders_1` (`user_id`),
  CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ;


INSERT INTO `orders` VALUES ('3', '1', '1000010', '2015-02-04 13:22:35', null);
INSERT INTO `orders` VALUES ('4', '1', '1000011', '2015-02-03 13:22:41', null);
INSERT INTO `orders` VALUES ('5', '10', '1000012', '2015-02-12 16:13:23', null);

2、创建对应的实体类 pojo

public class Orders  implements Serializable{

    private static final long serialVersionUID = 1L;

    private Integer id;

    private Integer userId;

    private String number;

    private Date createtime;

    private String note;

    private User user;


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number == null ? null : number.trim();
    }

    public Date getCreatetime() {
        return createtime;
    }

    public void setCreatetime(Date createtime) {
        this.createtime = createtime;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note == null ? null : note.trim();
    }

}

3、编写映射文件

<?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.pngyul.mybatis.mapper.OrdersMapper">

<resultMap type="Orders" id="orderUserResultMap">
    <!--只会映射指定的字段,其他未显式指定的都为null-->
    <id column="id" property="id" />
    <result property="userId" column="user_id" />
    <result property="number" column="number" />
    <result property="createtime" column="createtime" />
    <result property="note" column="note" />
    <!--用association表示关联的对象,property为属性名,javaType为类型-->
    <association property="user" javaType="User">
        <id column="user_id" property="id" />
        <result property="username" column="username" />
        <result property="address" column="address" />
    </association>
</resultMap>
<select id="queryOrderUserResultMap" resultMap="orderUserResultMap">
        SELECT
        o.id,
        o.user_id,
        o.number,
        o.createtime,
        o.note,
        u.username,
        u.address
        FROM
        `orders` o
        LEFT JOIN `user` u ON o.user_id = u.id
</select>
</mapper>

4、编写 Mapper 接口,将映射文件添加到核心配置文件中,然后测试

@Test
public void testQueryOrderUserResultMap(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    List<Orders> list = ordersMapper.queryOrderUserResultMap();
    for(Orders order : list ){
        System.out.println(order);
    }
    sqlSession.close();
}

一对多关联

1、我们在 use r实体类中添加 orders 字段

package com.pngyul.mybatis.pojo;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    private Integer id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    private List<Orders> orderList;

    public List<Orders> getOrderList() {
        return orderList;
    }
    public void setOrderList(List<Orders> orderList) {
        this.orderList = orderList;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", sex=" + sex + ", birthday=" + birthday + ", address="
                + address + ", orderList=" + orderList + "]";
    }


}

2、配置映射文件

<resultMap type="user" id="userOrderResultMap">
    <id property="id" column="id" />
    <result property="username" column="username" />
    <result property="birthday" column="birthday" />
    <result property="sex" column="sex" />
    <result property="address" column="address" />

    <!-- 配置一对多的关系 -->
    <!--这里使用ofType指定集合内部存储对象的类型-->
    <collection property="orderList" javaType="list" ofType="orders">
    <!-- 配置主键,是关联Order的唯一标识 -->
        <id property="id" column="oid" />
        <result property="number" column="number" />
        <result property="createtime" column="createtime" />
        <result property="note" column="note" />
    </collection>
</resultMap>

<!-- 一对多关联,查询订单同时查询该用户下的订单 -->
<select id="queryUserOrder" resultMap="userOrderResultMap">
        SELECT
        u.id,
        u.username,
        u.birthday,
        u.sex,
        u.address,
        o.id oid,
        o.number,
        o.createtime,
        o.note
        FROM
        `user` u
        LEFT JOIN `orders` o ON u.id = o.user_id
</select>

3、将方法添加到 mapper 接口中,我们测试一下

@Test
public void testQueryUserOrder(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    List<User> list = ordersMapper.queryUserOrder();
    for(User user : list ){
        System.out.println(user);
    }
    sqlSession.close();
}

猜你喜欢

转载自blog.csdn.net/PNGYUL/article/details/81835075