SSM框架mybatis 详解二 No.7

Mybatis映射文件

输入映射

ParameterType

指定输入参数的java类型,可以使用别名或者类的全限定名。它可以接收简单类型、POJO、HashMap。

传递简单类型

参考需求:根据用户ID查询用户信息。

传递POJO对象

参考需求:添加用户。

传递POJO包装对象

开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

需求

综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。

定义包装对象

一般User.java类要和数据表表字段一致,最好不要在这里面添加其他字段.

所以针对要扩展的po类,我们需要创建一个扩展类,来继承它。

编写Mapper接口

通过包装类来进行复杂的用户信息综合查询

public List<UserExt> findUserList(UserQueryVO userQueryVO);

编写mapper映射文件

<!-- 通过包装类来进行复杂的用户信息综合查询 -->
<select id="findUserList" parameterType="userQueryVO" resultType="userExt">
        SELECT * FROM USER WHERE sex=#{userExt.sex} AND username LIKE '%${userExt.username}%'
</select>

注意:入参的类型变为UserQueryVO、结果集的类型变为UserExt,#{}里面的参数变为UserQueryVO对象中的userExt属性的sex和username子属性。

编写测试代码

@Test
public void findUserListTest() {
    // 创建SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 通过SqlSession,获取mapper接口的动态代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    //构造userQueryVO对象
    UserQueryVO userQueryVO = new UserQueryVO();

    // 构造UserExt对象
    UserExt userExt = new UserExt();
    userExt.setSex("1");
    userExt.setUsername("小明");

    userQueryVO.setUserExt(userExt);

    // 调用mapper对象的方法
    List<UserExt> list = userMapper.findUserList(userQueryVO);

    System.out.println(list);
    // 关闭SqlSession
    sqlSession.close();
}

传递HashMap

同传递POJO对象一样,map的key相当于pojo的属性。

映射文件

<!-- 传递hashmap综合查询用户信息 -->
<select id="findUserByHashmap" parameterType="hashmap" resultType="user">
   select * from user where id=#{id} and username like '%${username}%'
</select>。

测试代码

Public void testFindUserByHashmap()throws Exception{
    //获取session
    SqlSession session = sqlSessionFactory.openSession();
    //获限mapper接口实例
    UserMapper userMapper = session.getMapper(UserMapper.class);
    //构造查询条件Hashmap对象
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("id", 1);
    map.put("username", "管理员");

    //传递Hashmap对象查询用户列表
    List<User>list = userMapper.findUserByHashmap(map);
    //关闭session
    session.close();
}

异常测试:传递的map中的key和sql中解析的key不一致。测试结果没有报错,只是通过key获取值为空。

输出映射

resultType

先看下原先resultType作为输出结果映射时,它的特点,如何再把列名改为别名,看看是否还能不能映射成功。

使用方法

使用resultType进行结果映射时,查询的列名和映射的pojo属性名完全一致,该列才能映射成功。
如果查询的列名和映射的pojo属性名全部不一致,则不会创建pojo对象;
如果查询的列名和映射的pojo属性名有一个一致,就会创建pojo对象。

输出简单类型

当输出结果只有一列时,可以使用ResultType指定简单类型作为输出结果类型。

需求

综合查询用户总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息)。

Mapper映射文件

<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="UserQueryVO"
        resultType="int">
        SELECT count(1) FROM USER WHERE sex = #{userExt.sex} AND username   LIKE '%${userExt.username}%'
</select>

Mapper接口

//综合查询用户信息总数。学习:resultType输出简单类型
public int findUsersCount(UserQueryVO vo);

测试代码

@Test
public void testFindUsersCount() {
    // 创建SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 通过SqlSession,获取mapper接口的动态代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    //构造userQueryVO对象
    UserQueryVO userQueryVO = new UserQueryVO();

    // 构造UserExt对象
    UserExt userExt = new UserExt();
    userExt.setSex("1");
    userExt.setUsername("小明");

    userQueryVO.setUserExt(userExt);

    int count = mapper.findUsersCount(userQueryVO);
    System.out.println(count);  // 关闭SqlSession
    sqlSession.close();
}

输出POJO单个对象和列表

注意:输出单个pojo对象和pojo列表(盛放pojo对象)时,mapper映射文件中的resultType的类型是一样的,mapper接口的方法返回值不同。

Mapper映射文件

Mapper映射文件是同一个

<select id="findUsersByName" parameterType="java.lang.String" resultType="cn.java.mybatis.po.User">
        SELECT * FROM USER WHERE username LIKE '%${value}%'
</select>

Mapper接口

下面看下mapper接口的不同之处

1、输出单个pojo对象

//根据用户名称来模糊查询用户信息

public User findUsersByName(String username);

2、输出pojo列表

//根据用户名称来模糊查询用户信息列表

public List<User> findUsersByName(String username);

总结:同样的mapper映射文件,返回单个对象和对象列表时,mapper接口在生成动态代理的时候,会根据返回值的类型,决定调用selectOne方法还是selectList方法。

resultMap

resultMap可以进行高级结果映射(一对一、一对多映射)。

使用方法

如果查询出来的列名和属性名不一致,通过定义一个resultMap将列名和pojo属性名之间作一个映射关系。

1、定义resultMap
2、使用resultMap作为statement的输出映射类型。

需求

把下面SQL的输出结果集进行映射

SELECT id id_,username username_,sex sex_ FROM USER WHERE id = 1

Mapper映射文件

定义resultMap:
<!-- 定义resultMap -->
<!-- 
    [id]:定义resultMap的唯一标识
    [type]:定义该resultMap最终映射的pojo对象
    [id标签]:映射结果集的唯一标识列,如果是多个字段联合唯一,则定义多个id标签
    [result标签]:映射结果集的普通列
    [column]:SQL查询的列名,如果列有别名,则该处填写别名
    [property]:pojo对象的属性名   -->
<resultMap type="user" id="userResultMap">
    <id column="id_" property="id"/>
    <result column="username_" property="username"/>
    <result column="sex_" property="sex"/>
</resultMap>

定义statement:
<!-- 根据ID查询用户信息(学习resultMap) -->
<select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
SELECT id id_,username username_,sex sex_ FROM USER WHERE id = #{id}
</select>

Mapper接口定义

//根据ID查询用户信息(学习resultMap)
public User findUserByIdResultMap(int id);

定义Statement使用resultMap映射结果集时,Mapper接口定义方法的返回值类型为mapper映射文件中resultMap的type类型。

测试代码

@Test
public void findUserByIdResultMapTest() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

// 调用mapper对象的方法
User user = userMapper.findUserByIdResultMap(1);

System.out.println(user);
// 关闭SqlSession
sqlSession.close();
}

动态SQL

通过Mybatis提供的各种动态标签实现动态拼接sql,使得mapper映射文件在编写SQL时更加灵活,方便。常用动态SQL标签有:if、where、foreach;

If和where

If标签:作为判断入参来使用的,如果符合条件,则把if标签体内的SQL拼接上。

注意:用if进行判断是否为空时,不仅要判断null,也要判断空字符串‘’;

Where标签:会去掉条件中的第一个and符号。

需求

用户信息综合查询列表和用户信息综合查询总数这两个statement的定义使用动态SQL。

映射文件

<!-- 综合查询用户信息,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersByQueryVO" parameterType="cn.java.mybatis.po.QueryUserVO"
        resultType="User">
        SELECT * FROM USER
    <where>
        <if test="userExt != null">
            <if test="userExt.sex != null and userExt.sex != ''">
                AND sex = #{userExt.sex}
            </if>
            <if test="userExt.username != null and userExt.username != ''">
                AND username LIKE '%${userExt.username}%'
            </if>
        </if>
    </where>
</select>

<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="QueryUserVO"
    resultType="int">
SELECT count(1) FROM USER 
<where>
    <if test="userExt != null">
        <if test="userExt.sex != null and userExt.sex != ''">
            AND sex = #{userExt.sex}
        </if>
        <if test="userExt.username != null and userExt.username != ''">
            AND username LIKE '%${userExt.username}%'
        </if>
    </if>
</where>
</select>

Mapper接口

//通过包装类来进行复杂的用户信息综合查询

public List<UserExt> findUserList(UserQueryVO userQueryVO);

//综合查询用户总数

public int findUsersCount(UserQueryVO userQueryVO);

SQL片段

Mybatis提供了SQL片段的功能,可以提高SQL的可重用性。

定义SQL片段

使用sql标签来定义一个SQL片段:

<!-- 定义SQL片段 -->
<!-- 
[sql标签]:定义一个SQL片段
[id]:SQL片段的唯一标识
建议:
    1、SQL片段中的内容最好是以单表来定义
    2、如果是查询字段,则不要写上SELECT
    3、如果是条件语句,则不要写上WHERE
 -->
<sql id="select_user_where">
    <if test="userExt != null">
        <if test="userExt.sex != null and userExt.sex != ''">
            AND sex = #{userExt.sex}
        </if>
        <if test="userExt.username != null and userExt.username != ''">
            AND username LIKE '%${userExt.username}%'
        </if>
    </if>
</sql>

引用SQL片段

使用 来引用SQL片段:

<!-- 根据用户id来查询用户信息(使用SQL片段) -->
<!-- 
    [include标签]:引用已经定义好的SQL片段
    [refid]:引用的SQL片段id
-->
<select id="findUserList" parameterType="userQueryVO" resultType="userExt">

    SELECT * FROM USER 
<where>
        <include refid="select_user_where"/>
    </where>
</select>
<!-- 综合查询用户信息总数,需要传入查询条件复杂,比如(用户信息、订单信息、商品信息) -->
<select id="findUsersCount" parameterType="QueryUserVO"
        resultType="int">
    SELECT count(1) FROM USER 
    <where>
        <include refid="select_user_where"/>
    </where>
</select>

Foreach

向sql传递数组或List时,mybatis使用foreach解析数组里的参数并拼接到SQL中。

传递pojo对象中的List集合

需求

在用户查询列表和查询总数的statement中增加多个id输入查询。

SQL

SELECT * FROM user WHERE id IN (1,10,16)

定义pojo中的List属性

映射文件

<!-- [foreach标签]:表示一个foreach循环 -->
<!-- [collection]:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写[list]。 -->
<!-- [item]:每次遍历出来的对象 -->
<!-- [open]:开始遍历时拼接的串 -->
<!-- [close]:结束遍历时拼接的串 -->
<!-- [separator]:遍历出的每个对象之间需要拼接的字符 -->
<if test="idList != null and idList.size > 0">
<foreach collection="idList" item="id" open="AND id IN (" close=")" separator=",">
        #{id}
</foreach>
</if>

Mapper接口

//根据用户ID的集合查询用户列表(学习foreach标签之通过POJO对象传ID集合)

public List<UserExt> findUserList(UserQueryVO vo);

测试代码

@Test
public void testFindUserList() {
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过SqlSession,获取mapper接口的动态代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

// 构造QueryUserVO对象
QueryUserVO vo = new QueryUserVO();
// UserExt ext = new UserExt();
// ext.setUsername("小明");
// ext.setSex("1");
// vo.setUserExt(ext);

// 创建用户ID集合,然后设置到QueryUserVO对象中
List<Integer> idList = new ArrayList<Integer>();
idList.add(1);
idList.add(10);
idList.add(16);
vo.setIdList(idList);

// 调用mapper代理对象的方法
List<UserExt> list = mapper.findUserList(vo);
System.out.println(list);
// 关闭SqlSession
sqlSession.close();
}

需求

根据用户ID的集合查询用户列表

SQL

SELECT * FROM user WHERE id IN (1,10,16)

映射文件

<!-- 根据用户ID的集合查询用户列表(学习foreach标签之直接传ID集合) -->
<!-- 
    [foreach标签]:表示一个foreach循环
    [collection]:集合参数的名称,如果是直接传入集合参数,则该处的参数名称只能填写[list]。
    [item]:定义遍历集合之后的参数名称
    [open]:开始遍历之前需要拼接的SQL串
    [close]:结束遍历之后需要拼接的SQL串
    [separator]:遍历出的每个对象之间需要拼接的字符
 -->
<select id="findUsersByIdList" parameterType="java.util.List" resultType="user">
    SELECT * FROM USER
    <where>
        <if test="list[如果是直接传入集合参数,则该处的参数名称只能填写[list]] != null and list.size > 0">
            <foreach collection="list" item="id" open="AND id IN (" close=")" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

Mapper接口

//根据用户ID的集合查询用户列表(学习foreach标签之直接传ID集合)

public List<User> findUsersByIdList (List<Integer> idList);

测试代码

@Test
public void findUsersByIdListTest() {
    // 创建SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 通过SqlSession,获取mapper接口的动态代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 构造List<Integer>集合
    List<Integer> idList = new ArrayList<Integer>();
    idList.add(1);
    idList.add(10);
idList.add(16);

    // 调用mapper对象的方法
    List<User> list = userMapper.findUsersByIdList (idList);
    System.out.println(list);
    // 关闭SqlSession
    sqlSession.close();
}

mybatis与hibernate的区别及各自应用场景

Mybatis技术特点:

1、通过直接编写SQL语句,可以直接对SQL进行性能的优化;
2、学习门槛低,学习成本低。只要有SQL基础,就可以学习mybatis,而且很容易上手;
3、由于直接编写SQL语句,所以灵活多变,代码维护性更好。
4、不能支持数据库无关性,即数据库发生变更,要写多套代码进行支持,移植性不好。
5、需要编写结果映射。

Hibernate技术特点:

1、标准的orm框架,程序员不需要编写SQL语句。
2、具有良好的数据库无关性,即数据库发生变化的话,代码无需再次编写。
3、学习门槛高,需要对数据关系模型有良好的基础,而且在设置OR映射的时候,需要考虑好性能和对象模型的权衡。
4、程序员不能自主的去进行SQL性能优化。

Mybatis应用场景:

需求多变的互联网项目,例如电商项目。

Hibernate应用场景:

需求明确、业务固定的项目,例如OA项目、ERP项目等。

关联查询映射

分析数据模型

思路

1、每张表记录的数据内容    分模块对每张表记录的内容进行熟悉,相当于你学习系统需求(功能)的过程。
2、每张表重要的字段  主键、外键、非空字段
3、数据库级别表与表的关系   外键关系
4、表与表之间的业务关系    在分析表与表之间的业务关系时一定要建立 在某个业务意义基础上去分析。

数据库表之间有外键关系的业务关系

user和orders:
user---->orders:一个用户可以创建多个订单,一对多
orders--->user:一个订单只由一个用户创建,一对一

orders和orderdetail:

orders-orderdetail:一个订单可以包括 多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系
orderdetail--> orders:一个订单明细只能包括在一个订单中,一对一

orderdetail和itesm:

orderdetail---》itesms:一个订单明细只对应一个商品信息,一对一
items--> orderdetail:一个商品可以包括在多个订单明细 ,一对多

数据库表之间没有外键关系的业务关系’

Orders和items:

这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Orders orderdetail –>items:一个订单可以有多个订单明细,一个订单明细对应一个商品,所以一个订单对应多个商品
Items-orderdetailorders:一个商品可以对应多个订单明细,一个订单明细对应一个订单,所以一个商品对应多个订单

User和items:

这两张表没有直接的外键关系,通过业务及数据库的间接关系分析出它们是多对多的关系。
Userordersorderdetailitems:一个用户有多个订单,一个订单有多个订单明细、一个订单明细对应一个商品,所以一个用户对应多个商品
Itemsorderdetailordersuser:一个商品对应多个订单明细,一个订单明细对应一个订单,一个订单对应一个用户,所以一个商品对应多个用户

一对一查询

需求

查询订单信息,关联查询创建订单的用户信息
SQL语句

确定查询的主表:订单表
确定查询的关联表:用户表

关联查询使用内链接?还是外链接?

Select
    Orders.id,
    Orders.user_id,
    orders.number,
orders.createtime,
orders.note,
user.username,
user.address
from orders,user
where orders.user_id = user.id

resultType

复杂查询时,单表对应的po类已不能满足输出结果集的映射。
所以要根据需求建立一个扩展类来作为resultType的类型。

创建po类

//通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
public class OrdersExt extends Orders{

    //添加用户属性
    /*USER.username,
      USER.address */

    private String username;
    private String address;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }

}

编写mapper接口

创建OrdersMapper接口类,在类中添加以下内容:

//  进行订单信息查询,包括用户的名称和地址信息
public List<OrdersExt> findOrdersUser();

编写映射文件

<mapper namespace="cn.java.mybatis.mapper.OrdersMapper">

    <!-- 定义查询订单表列名的SQL片段 -->
    <sql id="select_orders">
        Orders.id,
        Orders.user_id,
        orders.number,
        orders.createtime,
        orders.note
    </sql>
    <!-- 定义查询用户表列名的SQL片段 -->
    <sql id="select_user">
        user.username,
        user.address
    </sql>
    <!-- 进行订单信息查询,包括用户的名称和地址信息 -->
    <select id="findOrdersUser" resultType="OrdersExt">
        Select
        <include refid="select_orders" />
        <include refid="select_user"></include>
        from orders,user
        where orders.user_id = user.id
    </select>
</mapper>

加载映射文件

<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<package name="cn.java.mybatis.mapper"/>

编写测试代码

@Test
public void testFindOrdersUser() {
    // 创建sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通过SqlSession构造usermapper的代理对象
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    // 调用usermapper的方法
    List<OrdersExt> list = ordersMapper.findOrdersUser();

    System.out.println(list);

    // 释放SqlSession
    sqlSession.close();
}

resultMap

修改po类

在Orders类中,添加User对象

public class Orders {
    private Integer id;

    private Integer userId;

    private String number;

    private Date createtime;

    private String note;

    //用户信息
    private User user;

编写mapper接口

//  进行订单信息查询,包括用户的名称和地址信息(resultMap)
public List<OrdersExt> findOrdersUserRstMap();

编写映射文件

<!-- 进行订单信息查询,包括用户的名称和地址信息 (ResultMap) -->
<select id="findOrdersUserRstMap" resultMap="OrdersUserRstMap">
    Select
    <include refid="select_orders" />
    ,
    <include refid="select_user"></include>
    from orders,user
    where orders.user_id = user.id
</select>

<!-- 定义orderUserResultMap -->
<resultMap type=" cn.java.mybatis.po.Orders" id="OrdersUserRstMap">
    <id column="id" property="id" />
    <result column="user_id" property="userId" />
    <result column="number" property="number" />
    <result column="createtime" property="createtime" />
    <result column="note" property="note" />
    <!-- 映射一对一关联关系的用户对象-->
    <!-- 
        property:指定关联对象要映射到Orders的哪个属性上 
        javaType:指定关联对象所要映射的java类型
      -->
    <!-- id标签:指定关联对象结果集的唯一标识,很重要,不写不会报错,但是会影响性能 -->
    <association property="user" javaType="cn.java.mybatis.po.User">
        <id column="user_id" property="id" />
        <result column="username" property="username" />
        <result column="address" property="address" />
    </association>
</resultMap>

编写测试代码

@Test
public void testFindOrdersUserRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersUserRstMap();

//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();
}

一对一小结

实现一对一查询:

resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。
如果没有查询结果的特殊要求建议使用resultType。

resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的对象属性中。

resultMap可以实现延迟加载,resultType无法实现延迟加载。

一对多查询

一对多查询和一对一查询的配置基本类似。只是如果使用resultMap的话,映射一对多关联关系要使用collection标签。
需求

查询订单信息及订单明细信息

SQL语句

确定主查询表:订单表
确定关联查询表:订单明细表
在一对一查询基础上添加订单明细表关联即可。

Select
    Orders.id,
    Orders.user_id,
    orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
from orders,user,orderdetail
where orders.user_id = user.id 
    and orders.id = orderdetail.orders_id

分析

使用resultType将上边的 查询结果映射到pojo中,订单信息将会重复。

要求:

对orders映射不能出现重复记录。

在orders.java类中添加List detailList属性。最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的detailList属性中。

映射成的orders记录数为两条(orders信息不重复)

每个orders中的detailList属性存储了该订单所对应的订单明细集合。

修改PO类

在Orders类中添加以下属性,并提供get/set方法:

//订单明细
private List<Orderdetail> detailList;

编写mapper接口

// 查询订单信息及订单明细信息(一对多映射之使用resultMap)
public List<Orders> findOrdersAndOrderdetailRstMap();

编写映射文件

<!-- 定义OrdersAndOrderdetailRstMap -->
<!-- extends:继承已有的ResultMap,值为继承的ResultMap的唯一标示 -->
<resultMap type="Orders" id="OrdersAndOrderdetailRstMap"
extends="OrdersUserRstMap">
    <!-- 映射关联关系(一对多) -->
    <!-- collection标签:定义一个一对多关系
        ofType:指定该集合参数所映射的类型
     -->
    <collection property="detailList" ofType="Orderdetail">
        <id column="detail_id" property="id" />
        <result column="items_id" property="itemsId" />
        <result column="items_num" property="itemsNum" />
    </collection>
</resultMap>

<!-- 查询订单信息,包括用户名称、用户地址,订单商品信息(嵌套结果) -->
<select id="findOrdersAndOrderdetailRstMap" resultMap="OrdersAndOrderdetailRstMap">

    Select
    <include refid="select_orders" />
    ,
    <include refid="select_user"/>
    ,
    orderdetail.id detail_id,
    orderdetail.items_id,
    orderdetail.items_num
    from orders,user,orderdetail
    where orders.user_id = user.id
    and
    orders.id = orderdetail.orders_id

</select>

resultMap的extends属性:可以用此属性来继承一个已有的resultmap。但是它继承的resultMap的type和它本身的type要保持一致

编写测试代码

@Test
public void testFindOrdersAndOrderdetailRstMap() {
// 创建sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

// 通过SqlSession构造usermapper的代理对象
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
// 调用usermapper的方法
List<Orders> list = ordersMapper.findOrdersAndOrderdetailRstMap();

//此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
System.out.println(list);
// 释放SqlSession
sqlSession.close();

}

一对多小结

mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。

使用resultType实现:

需要对结果集进行二次处理。将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

多对多查询

需求

查询用户信息及用户购买的商品信息,要求将关联信息映射到主pojo的pojo属性中

SQL语句

查询主表:user

查询关联表:orders、orderdetail、items

Select
    Orders.id,
    Orders.user_id,
    orders.number,
orders.createtime,
orders.note,
user.username,
user.address,
orderdetail.id detail_id,
orderdetail.items_id,
orderdetail.items_num
  items.name items_name,
  items.detail items_detail  
FROM
  orders,
USER,
  orderdetail,
  items 
WHERE user.`id` = orders.`user_id` 
  AND orders.`id` = orderdetail.`orders_id` 
  AND orderdetail.`items_id` = items.`id`

映射思路

将用户信息映射到user中。
在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist
在Orders中添加订单明细列表属性List<Orderdetail> detailList,将订单的明细映射到detailList
在Orderdetail中添加Items属性,将订单明细所对应的商品映射到Items

修改PO类

在user类中添加List ordersList 属性

// 订单信息
private List<Orders> ordersList;

在Orders类中添加List属性

//订单明细
private List<Orderdetail> detailList;

在Orderdetail类中添加Items属性

//商品信息
private Items items;

编写mapper接口

//查询用户及用户购买商品信息(多对多映射之使用resultMap)
public List<User> findUserAndItemsRstMap();

编写映射文件

<!-- 定义UserAndItemsRstMap -->
<resultMap type="User" id="UserAndItemsRstMap">
    <!-- 用户信息 -->
    <!-- id:关联查询用户的唯一标示 -->
    <id column="user_id" property="id" />
    <result column="username" property="username" />
    <result column="address" property="address" />
    <!-- 订单信息 (一个用户有多个订单) -->
    <collection property="ordersList" ofType="orders">
        <id column="id" property="id" />
        <result column="user_id" property="userId" />
        <result column="number" property="number" />
        <result column="createtime" property="createtime" />
        <result column="note" property="note" />
        <!-- 订单明细信息(一个订单有多个订单明细) -->
        <collection property="detailList" ofType="orderdetail">
            <id column="detail_id" property="id" />
            <result column="items_id" property="itemsId" />
            <result column="items_num" property="itemsNum" />
            <!-- 商品信息 (一个订单明细对应一个商品) -->
            <association property="items" javaType="cn.java.mybatis.po.Items">
                <id column="items_id" property="id" />
                <result column="items_name" property="name" />
                <result column="items_detail" property="detail" />
            </association>
        </collection>
    </collection>
</resultMap>

<!-- 查询用户及用户购买商品信息(多对多映射之使用resultMap) -->
<select id="findUserAndItemsRstMap" resultMap="UserAndItemsRstMap">
    Select
    <include refid="select_orders" />
    ,
    <include refid="select_user" />
    ,
    <include refid="select_orderdetail"></include>
    ,
    items.name items_name,
    items.detail items_detail
    from
    orders,user,orderdetail,items
    where orders.user_id = user.id
    and
    orders.id = orderdetail.orders_id
    and orderdetail.items_id = items.id
</select>

编写测试代码

@Test
public void testFindUserAndItemsRstMap() {
    // 创建sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通过SqlSession构造usermapper的代理对象
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    // 调用usermapper的方法
    List<User> list = ordersMapper.findUserAndItemsRstMap();

    // 此处我们采用debug模式来跟踪代码,然后验证结果集是否正确
    System.out.println(list);
    // 释放SqlSession
    sqlSession.close();
}

多对多查询小结

将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)

针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。

一对多是多对多的特例,如下需求:

查询用户购买的商品信息,用户和商品的关系是多对多关系。

查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
企业开发中常见明细列表,用户购买商品明细列表,
使用resultType将上边查询列映射到pojo输出。

查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
使用resultMap将用户购买的商品明细列表映射到user对象中。

总结:

使用resultMap是针对那些对查询结果映射有特殊要求的功能,,比如特殊要求映射成list中包括 多个list。

高级映射总结

resultType:

作用:

将查询结果按照sql列名pojo属性名一致性映射到pojo中。

场合:

常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。

resultMap:

使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

association:

作用:

将关联查询信息映射到一个pojo对象中。

场合:

为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

collection:

作用:

将关联查询信息映射到一个list集合中。

场合:

为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。
如果使用resultType无法将查询结果映射到list集合中。

延迟加载

什么是延迟加载

resultMap中的association和collection标签具有延迟加载的功能。

延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

设置延迟加载

Mybatis默认是不开启延迟加载功能的,我们需要手动开启。需要在SqlMapConfig.xml文件中,在标签中开启延迟加载功能.
lazyLoadingEnabled、aggressiveLazyLoading

设置项                      描述 允许值                                                  默认值
lazyLoadingEnabled  全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。   true | false    true
aggressiveLazyLoading   当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。  true | false    true

使用association进行延迟加载

需求

查询订单并且关联查询用户信息(对用户信息的加载要求是按需加载)

编写映射文件

需要定义两个mapper的方法对应的statement。

1、只查询订单信息

SELECT * FROM orders
在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)
    <!-- 定义OrdersUserLazyLoadingRstMap -->
    <resultMap type="cn.java.mybatis.po.Orders" id="OrdersUserLazyLoadingRstMap">
    <id column="id" property="id" />
    <result column="user_id" property="userId" />
    <result column="number" property="number" />
    <result column="createtime" property="createtime" />
    <result column="note" property="note" />

    <!-- 延迟加载用户信息 -->
    <!-- select:指定延迟加载需要执行的statement的id(是根据user_id查询用户信息的statement)
        我们使用UserMapper.xml中的findUserById完成根据用户ID(user_id)查询用户信息
        如果findUserById不在本mapper中,前边需要加namespace -->
    <!-- column:主信息表中需要关联查询的列,此处是user_id -->
    <association property="user" select="cn.java.mybatis.mapper.UserMapper.findUserById" column="user_id"></association>
</resultMap>

<!-- 查询订单信息,延迟加载关联查询的用户信息 -->
<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingRstMap">
    SELECT * FROM orders
</select>

2、关联查询用户信息
    通过上边查询到的订单信息中user_id去关联查询用户信息
    使用UserMapper.xml中的findUserById
<select id="findUserById" parameterType="int"
        resultType="cn.java.mybatis.po.User">
    SELECT * FROM user WHERE id = #{id}
</select>

上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。

加载映射文件

<!-- 批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
<package name="cn.java.mybatis.mapper"/>

编写mapper接口

// 查询订单信息,延迟加载关联查询的用户信息
public List<Orders> findOrdersUserLazyLoading();

编写测试代码

思路:

1、执行上边mapper方法(findOrdersUserLazyLoading),内部去调用cn.java.mybatis.mapper.OrdersMapper中的findOrdersUserLazyLoading只查询orders信息(单表)。

2、在程序中去遍历上一步骤查询出的List<Orders>,当我们调用Orders中的getUser方法时,开始进行延迟加载。

3、执行延迟加载,去调用UserMapper.xml中findUserbyId这个方法获取用户信息。

@Test
public void testFindOrdersUserLazyLoading() {
    // 创建sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 通过SqlSession构造usermapper的代理对象
    OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
    // 调用usermapper的方法
    List<Orders> list = ordersMapper.findOrdersUserLazyLoading();

    for(Orders orders : list){
        System.out.println(orders.getUser());
    }
    // 释放SqlSession
    sqlSession.close();

延迟加载思考

不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??

实现方法如下:

定义两个mapper方法:

1、查询订单列表
2、根据用户id查询用户信息
实现思路:
先去查询第一个mapper方法,获取订单信息列表
在程序中(service),按需去调用第二个mapper方法去查询用户信息。

总之:

使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。

查询缓存

mybatis缓存分析

mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

一级缓存

原理

第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。

如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。

Mybatis默认支持一级缓存。

测试
@Test
public void testOneLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
User user1 = mapper.findUserById(1);
System.out.println(user1);

    // 第二次查询ID为1的用户
    User user2 = mapper.findUserById(1);
    System.out.println(user2);

    sqlSession.close();
}

只输出一次SQL:

测试2

@Test
public void testOneLevelCache() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
    User user1 = mapper.findUserById(1);
    System.out.println(user1);

    User user = new User();
    user.setUsername("东哥1");
    user.setAddress("清河宝盛西里");
    //执行增删改操作,清空缓存
    mapper.insertUser(user);

    // 第二次查询ID为1的用户
    User user2 = mapper.findUserById(1);
    System.out.println(user2);

    sqlSession.close();
}

中间执行了commit操作,同样的查询SQL输出两次:

应用

正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。

service{
    //开始执行时,开启事务,创建SqlSession对象
    //第一次调用mapper的方法findUserById(1)

    //第二次调用mapper的方法findUserById(1),从一级缓存中取数据
    //方法结束,sqlSession关闭
}

如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为session方法结束,sqlSession就关闭,一级缓存就清空。\

二级缓存

原理

下图是多个sqlSession请求UserMapper的二级缓存图解。

二级缓存是mapper级别的。

第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。

开启二级缓存

Mybatis默认是没有开启二级缓存

1、在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:
<!-- 开启二级缓存总开关 -->
<setting name="cacheEnabled" value="true"/>

2、在UserMapper映射文件中,加入以下内容,开启二级缓存: 

<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache> 

实现序列化

由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,所以需要给缓存的对象执行序列化。
如果该类存在父类,那么父类也要实现序列化。

测试1
@Test
public void testTwoLevelCache() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
    // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
    User user1 = mapper1.findUserById(1);
    System.out.println(user1);
    // 关闭SqlSession1
    sqlSession1.close();

    // 第二次查询ID为1的用户
    User user2 = mapper2.findUserById(1);
    System.out.println(user2);
    // 关闭SqlSession2
    sqlSession2.close();
}

SQL输出结果:

Cache Hit Radio : 缓存命中率
第一次缓存中没有记录,则命中率0.0;
第二次缓存中有记录,则命中率0.5(访问两次,有一次命中)

测试2

@Test
public void testTwoLevelCache() {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();

    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
    UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
    // 第一次查询ID为1的用户,去缓存找,找不到就去查找数据库
    User user1 = mapper1.findUserById(1);
    System.out.println(user1);
    // 关闭SqlSession1
    sqlSession1.close();

    //修改查询出来的user1对象,作为插入语句的参数
    user1.setUsername("东哥1");
    user1.setAddress("清河宝盛西里");

    mapper3.insertUser(user1);

    // 提交事务
    sqlSession3.commit();
    // 关闭SqlSession3
    sqlSession3.close();

    // 第二次查询ID为1的用户
    User user2 = mapper2.findUserById(1);
    System.out.println(user2);
    // 关闭SqlSession2
    sqlSession2.close();
}

SQL输出结果:
根据SQL分析,确实是清空了二级缓存了。

禁用二级缓存

该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。

<select id="findUserById" parameterType="int"
        resultType="cn.java.mybatis.po.User" useCache="true">
    SELECT * FROM user WHERE id = #{id}
</select>

刷新二级缓存

该statement中设置flushCache=true可以刷新当前的二级缓存,默认情况下如果是select语句,那么flushCache是false。如果是insert、update、delete语句,那么flushCache是true。
如果查询语句设置成true,那么每次查询都是去数据库查询,即意味着该查询的二级缓存失效。
如果查询语句设置成false,即使用二级缓存,那么如果在数据库中修改了数据,而缓存数据还是原来的,这个时候就会出现脏读。

flushCache设置如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值