• 今天内容安排:
    • 1、高级结果映射(一对一、一对多、多对多)(重点)
    • 2、延迟加载
    • 3、查询缓存
    • 4、Spring 和 mybatis 的整合(重点)
    • 5、逆向工程

1、高级结果映射(即:关联查询映射)(重点)

1.1、分析数据模型

1.1.1、思路

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

1.1.2、图解分析

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

  • user和orders:
    • user –> orders:一个用户可以创建多个订单,一对多
    • orders –> user:一个订单只由一个用户创建,一对一
  • orders和orderdetail:
    • orders –> orderdetail:一个订单可以包括多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多
    • orderdetail –> orders:一个订单明细只能包括在一个订单中,一对一
  • orderdetail和ites:
    • orderdetail –> itesms:一个订单明细只对应一个商品信息,一对一
    • items –> orderdetail:一个商品可以包括在多个订单明细中 ,一对多

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

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

1.2、一对一查询

1.2.1、需求

  • 查询订单信息,关联查询创建订单的用户信息(用户名称和性别)

1.2.2、SQL语句

  • 确定查询的主表:订单表 orders
  • 确定查询的关联表:用户表 user
  • 关联查询使用内连接呢?还是外连接呢?到了企业里面,我们写sql时大多数的时候我们需要考虑,很多时候,我们会使用外连接,要先把主表信息查询出来,然后需要考虑是使用左外连接还是右外连接,记录为空的要不要,不考虑好的话,查询到的结果集数量会和我们预想的有很大出入。
  • 本例中我们使用的是等值连接,所以暂时我们不用考虑那么多。

sql语句如下:

SELECT
  orders.id,
  orders.user_id,
  orders.number,
  user.username,
  user.sex
FROM
  orders,
  user
WHERE orders.user_id = user.id;

1.2.3、resultType

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

(1)创建扩展PO类
  一般User.java类要和数据表表字段一致,最好不要在这里面添加其他字段,今天学习mybatis的逆向工程时,会根据表结构,生成po类,如果在po类中扩展字段,此时会被覆盖掉。
  所以针对要扩展的po类,我们需要创建一个扩展类,来继承它。

/**
 * 通过此类映射订单和用户查询的结果,让此类继承包括字段较多的pojo类
 * @author Bruce
 *
 */

public class OrdersExt extends Orders {

    // 添加用户属性
    // user.username
    // user.sex
    private String username;
    private String sex;

    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;
    }

}

(2)编写mapper接口
  创建OrdersMapper接口类,在类中添加以下内容:

public interface OrdersMapper {
    // 一对一映射之 resultType
    // 查询订单信息,关联查询创建订单的用户信息(用户名称和性别)
    public List<OrdersExt> findOrdersAndUser();
}

(3)编写映射文件
  创建OrdersMapper.xml映射文件,在映射文件中添加以下内容:

<mapper namespace="com.itheima.mybatis.mapper.OrdersMapper">
    <!-- 一对一映射之 resultType -->
    <!-- 查询订单信息,关联查询创建订单的用户信息(用户名称和性别) -->
    <select id="findOrdersAndUser" resultType="com.itheima.mybatis.po.OrdersExt">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex
        FROM
          orders,
          user
        WHERE orders.user_id = user.id
    </select>
</mapper>

(4)加载映射文件
  在config/SqlMapConfig.xml中,添加以下内容:

    <!-- 加载mapper,即加载映射文件 -->
    <mappers>
        <!-- 使用相对于类路径的资源,加载配置文件 -->
        <!--  
        <mapper resource="sqlmap/User.xml"/>
        <mapper resource="mapper/UserMapper.xml"/>
        <mapper resource="mapper/OrdersMapper.xml"/>
        -->

        <!-- 推荐使用:批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
        <package name="com.itheima.mybatis.mapper"/>
    </mappers>

(5)编写测试代码

    @Test
    public void testFindUserById() 
{
        // 根据SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 创建OrdersMapper对象
        // 由Mybatis通过sqlSession来创建动态代理对象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUser();
        System.out.println(list);

        sqlSession.close();
    }

(6)小结

  • 使用resultType来进行一对一结果映射,查询出的列的个数和映射的属性的个数要一致。而且映射的属性要存在与一个大的对象中,它是一种平铺式的映射,即:数据库查询出多少条记录,则映射成多少个对象。

1.2.4、resultMap

  • 使用resultMap来进行一对一结果映射,它是将关联对象添加到主信息的对象中,具体说是对象嵌套对象的一种映射方式。

(1)修改扩展PO类
  在OrdersExt类中,添加User对象

/**
 * 通过此类映射订单和用户查询的结果,让此类继承包括字段较多的pojo类
 * 在OrdersExt类中,添加User对象
 * @author Bruce
 *
 */

public class OrdersExt extends Orders {

    // 添加用户属性
    // user.username
    // user.sex
    private String username;

    private String sex;

    // 添加用户对象(用户信息)
    private User user;

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

    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;
    }

}

(2)编写mapper接口

    // 一对一映射之 resultMap
    // 查询订单信息,关联查询创建订单的用户信息(用户名称和性别)
    public List<OrdersExt> findOrdersAndUserResultMap();

(3)编写映射文件

    <!-- 声明/定义一个resultMap -->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrdersAndUserResultMap">
        <!-- 订单信息映射 -->
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <!-- 用户信息映射(一对一) -->
        <!-- 
            association标签:定义一个一对一关系
                property:指定关联对象要映射到OrdersExt的哪个属性上 
                javaType:指定关联对象所要映射的java类型

                id标签:指定关联对象结果集的唯一标识,很重要,建议在关联查询时必须写上,不写不会报错,但是会影响性能
        -->

        <association property="user" javaType="com.itheima.mybatis.po.User">
            <id column="user_id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
        </association>
    </resultMap>
    <!-- 一对一映射之 resultMap -->
    <!-- 查询订单信息,关联查询创建订单的用户信息(用户名称和性别) -->
    <select id="findOrdersAndUserResultMap" resultMap="OrdersAndUserResultMap">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex
        FROM
          orders,
          user
        WHERE orders.user_id = user.id
    </select>

(4)加载映射文件
  已配置,此处无需再次配置。
(5)编写测试代码

    @Test
    public void testFindOrdersAndUserResultMap() 
{
        // 根据SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 创建OrdersMapper对象
        // 由Mybatis通过sqlSession来创建动态代理对象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUserResultMap();
        System.out.println(list);

        sqlSession.close();
    }

(6)小结

  • 在一对一结果映射时,使用resultType更加简单方便,如果有特殊要求(对象嵌套对象)时,需要使用resultMap进行映射,比如:查询订单列表,然后在点击列表中的查看订单明细按钮,这个时候就需要使用resultMap进行结果映射。而resultType更适应于查询订单明细信息,比如,查询订单明细列表。

1.2.5、一对一查询小结

实现一对一查询:

  • resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,创建扩展PO类,即可完成映射。
    如果没有查询结果的特殊要求建议使用resultType。
  • resultMap:需要单独定义resultMap,实现上有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射到pojo的对象属性中。
  • resultMap可以实现延迟加载,resultType无法实现延迟加载。

1.3、一对多查询

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

1.3.1、需求

  • 查询订单信息,关联查询订单明细信息及用户信息。

1.3.2、SQL语句

  • 确定主查询表:订单表 orders
  • 确定关联查询表:订单明细表、用户表 orderdetail、user
  • 关联查询使用内连接呢?还是外连接呢?到了企业里面,我们写sql时大多数的时候我们需要考虑,很多时候,我们会使用外连接,要先把主表信息查询出来,然后需要考虑是使用左外连接还是右外连接,记录为空的要不要,不考虑好的话,查询到的结果集数量会和我们预想的有很大出入。
  • 本例中我们使用的是等值连接,所以暂时我们不用考虑那么多。

sql语句如下:

SELECT
  orders.id,
  orders.user_id,
  orders.number,
  user.username,
  user.sex,
  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;

1.3.3、分析

  • 使用resultType将上边的查询结果映射到扩展的pojo中,订单信息将会重复。
  • 要求:对OrdersExt映射不能出现重复记录。
    • 在OrdersExt.java类中添加`List detailList`属性。
    • 最终会将订单信息映射到OrdersExt中,订单所对应的订单明细映射到OrdersExt中的detailList属性中。
    • 映射成的OrdersExt记录数为两条(OrdersExt信息不重复)
    • 每个OrdersExt中的detailList属性存储了该订单所对应的订单明细集合。

(1)修改扩展PO类
  在OrdersExt类中添加以下属性,并提供get/set方法:

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

    public List<Orderdetail> getDetailList() {
        return detailList;
    }

    public void setDetailList(List<Orderdetail> detailList{
        this.detailList = detailList;
    }

(2)编写mapper接口

    // 一对多映射之 resultMap
    // 查询订单信息,关联查询订单明细信息及用户信息
    public List<OrdersExt> findOrdersAndOrderdetailResultMap();

(3)编写映射文件

    <!-- 声明/定义一个resultMap -->
    <!-- extends:继承已有的ResultMap,值为继承的ResultMap的唯一标示 -->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrdersAndOrderdetailResultMap" extends="OrdersAndUserResultMap">
        <!-- 订单信息映射 -->
        <!-- 用户信息映射(一对一) -->
        <!-- 订单明细信息映射(一对多) -->
        <!-- collection标签:定义一个一对多关系
                ofType:指定该集合参数所映射的类型
        -->

        <collection property="detailList" ofType="com.itheima.mybatis.po.Orderdetail">
            <id column="detail_id" property="id"/>
            <result column="items_id" property="itemsId"/>
            <result column="items_num" property="itemsNum"/>
        </collection>
    </resultMap>
    <!-- 一对多映射之 resultMap -->
    <!-- 查询订单信息,关联查询订单明细信息及用户信息 -->
    <select id="findOrdersAndOrderdetailResultMap" resultMap="OrdersAndOrderdetailResultMap">
        SELECT
          orders.id,
          orders.user_id,
          orders.number,
          user.username,
          user.sex,
          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要保持一致。
(4)编写测试代码

    @Test
    public void testFindOrdersAndOrderdetailResultMap() 
{
        // 根据SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 创建OrdersMapper对象
        // 由Mybatis通过sqlSession来创建动态代理对象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndOrderdetailResultMap();
        System.out.println(list);

        sqlSession.close();
    }

1.3.4、一对多查询小结

  • mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。
  • 如果使用resultType实现:
    • 需要对结果集进行二次处理。
    • 将订单明细映射到OrdersExt中的detailList中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在detailList中。很麻烦,不好。

1.4、多对多查询

  • 多对多映射是一对多映射的特例。

1.4.1、需求

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

1.4.2、SQL语句

  • 查询主表:user
  • 查询关联表:orders、orderdetail、items
  • 关联查询使用内连接呢?还是外连接呢?到了企业里面,我们写sql时大多数的时候我们需要考虑,很多时候,我们会使用外连接,要先把主表信息查询出来,然后需要考虑是使用左外连接还是右外连接,记录为空的要不要,不考虑好的话,查询到的结果集数量会和我们预想的有很大出入。
  • 本例中我们使用的是等值连接,所以暂时我们不用考虑那么多。

sql语句如下:

SELECT
  orders.id orders_id,
  orders.user_id,
  orders.number orders_number,
  user.username user_username,
  user.sex user_sex,
  orderdetail.id detail_id,
  orderdetail.items_id,
  orderdetail.items_num,
  items.name items_name,
  items.price items_price
FROM
  orders,
  user,
  orderdetail,
  items
WHERE user.id = orders.user_id
  AND orders.id = orderdetail.orders_id
  AND orderdetail.items_id = items.id;

1.4.3、映射思路

  • 将用户信息映射到UserExt中。
    • 在UserExt类中添加订单列表属性List ordersList,将用户创建的订单映射到ordersList
    • 在OrdersExt中添加订单明细列表属性List detailList,将订单的明细映射到detailList
    • 在OrderdetailExt中添加Items属性,将订单明细所对应的商品映射到Items

(1)修改扩展PO类
  在UserExt类中添加List<Orders>属性

    // 添加订单列表
    private List<Orders> ordersList;

  在OrdersExt类中添加List<Orderdetail>属性

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

  在OrderdetailExt类中添加Items属性

    // 添加商品信息
    private Items items;

(2)编写mapper接口
  在UserMapper.java中,添加以下内容:

    // 多对多映射之 resultMap
    // 查询用户信息,关联查询该用户购买的商品信息
    public List<UserExt> findUserAndItemsResultMap();

(3)编写映射文件
  在UserMapper.xml中,添加以下内容:

    <!-- 声明/定义一个resultMap -->
    <resultMap type="com.itheima.mybatis.po.UserExt" id="UserAndItemsResultMap">
        <!-- 用户信息映射-->
        <id column="user_id" property="id"/>
        <result column="user_username" property="username"/>
        <result column="user_sex" property="sex"/>
        <!-- 订单信息映射(一对多) -->
        <collection property="ordersList" ofType="com.itheima.mybatis.po.OrdersExt">
            <id column="orders_id" property="id"/>
            <result column="user_id" property="userId"/>
            <result column="orders_number" property="number"/>
            <!-- 订单明细信息映射(一对多) -->
            <collection property="detailList" ofType="com.itheima.mybatis.po.OrderdetailExt">
                <id column="detail_id" property="id"/>
                <result column="items_id" property="itemsId"/>
                <result column="items_num" property="itemsNum"/>
                <!-- 商品信息映射(一对一) -->
                <association property="items" javaType="com.itheima.mybatis.po.Items">
                    <id column="items_id" property="id"/>
                    <result column="items_name" property="name"/>
                    <result column="items_price" property="price"/>
                </association>
            </collection>       
        </collection>
    </resultMap>
    <!-- 多对多映射之 resultMap -->
    <!-- 查询用户信息,关联查询该用户购买的商品信息 -->
    <select id="findUserAndItemsResultMap" resultMap="UserAndItemsResultMap">
        SELECT
          orders.id orders_id,
          orders.user_id,
          orders.number orders_number,
          user.username user_username,
          user.sex user_sex,
          orderdetail.id detail_id,
          orderdetail.items_id,
          orderdetail.items_num,
          items.name items_name,
          items.price items_price
        FROM
          orders,
          user,
          orderdetail,
          items
        WHERE user.id = orders.user_id
          AND orders.id = orderdetail.orders_id
          AND orderdetail.items_id = items.id   
    </select>

(4)编写测试代码
  在UserMapperTest.java中,添加以下内容:

    @Test
    public void testFindUserAndItemsResultMap() 
{
        // 根据SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 创建UserMapper对象
        // 由Mybatis通过sqlSession来创建动态代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<UserExt> list = mapper.findUserAndItemsResultMap();
        System.out.println(list);

        sqlSession.close();
    }

1.4.4、多对多查询小结

  • 将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)
  • 针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。
  • 一对多是多对多的特例,如下需求:
  • 查询用户购买的商品信息,用户和商品的关系是多对多关系。
  • 需求1:
    • 查询字段:用户账号、用户名称、用户性别、商品名称、商品价格(最常见)
    • 企业开发中常见明细列表,用户购买商品明细列表,
    • 使用resultType将上边查询列映射到pojo输出。
  • 需求2:
    • 查询字段:用户账号、用户名称、购买商品数量、商品明细(鼠标移上显示明细)
    • 使用resultMap将用户购买的商品明细列表映射到user对象中。
  • 总结:
    • 使用resultMap是针对那些对查询结果映射有特殊要求的功能,比如特殊要求映射成list中包括多个list。

1.5、高级映射总结

resultType:
作用:
    将查询结果按照sql列名pojo属性名一致性映射到pojo中。
场合:
    常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历listlist中是pojo)即可。
------------------------------------------------------------
resultMap:
    使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。
------------------------------------------------------------
association:
作用:
    将关联查询信息映射到一个pojo对象中。
场合:
    为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。
    使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。
------------------------------------------------------------    
collection:
作用:
    将关联查询信息映射到一个list集合中。
场合:
    为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的做的目的也是方便对查询结果集进行遍历查询。
    如果使用resultType无法将查询结果映射到list集合中。

2、延迟加载

2.1、什么是延迟加载

  • 在mybatis中,resultMap中的association和collection标签具有延迟加载的功能。
  • 延迟加载的意思是说,在关联查询时,利用延迟加载,先加载主信息。需要关联信息时再去按需加载关联信息。这样会大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。
  • 延迟加载又叫懒加载,也叫按需加载。也就是说先加载主信息,在需要的时候,再去加载从信息。

2.2、设置延迟加载

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

配置图如下:

2.3、使用association进行延迟加载

2.3.1、需求

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

  • 1、创建一个statement来查询订单信息
  • 2、创建一个statement来查询用户信息

(1)编写mapper接口
  在OrdersMapper.xml文件中,添加以下内容:

    // 延时加载
    public List<OrdersExt> findOrderAndUserLazyLoading();

(2)编写映射文件
  编写查询订单信息的映射文件

    <!-- 定义一个 resultMap-->
    <resultMap type="com.itheima.mybatis.po.OrdersExt" id="OrderAndUserLazyLoading">
        <!-- 订单信息映射 -->
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <!-- 用户信息映射(一对一) -->
        <!-- select:指定关联查询的查询statement(即查询用户的statement的id),然后将查询结果,封装到property属性指定的变量中 -->
        <!-- column:通过column指定的列所查询出的结果,作为select指的statement的入参
                注意:如果select指定的statement,入参需要多个值,需要在column中{col1=prop1,col2=prop2} 
        -->

        <association property="com.itheima.mybatis.po.User" 
            select="com.itheima.mybatis.mapper.UserMapper.findUserById"
            column="user_id">

        </association>
    </resultMap>
    <!-- 延时加载 -->
    <select id="findOrderAndUserLazyLoading" resultMap="OrderAndUserLazyLoading">
        SELECT * FROM orders
    </select>

  编写查询用户信息的映射文件

    <!-- 根据用户ID,查询用户信息 -->
    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User">
        SELECT * FROM USER WHERE id = #{id}
    </select>

(3)编写测试代码

    @Test
    public void testFindOrdersAndUserLazyLoading() 
{
        // 根据SqlSessionFactory创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 创建OrdersMapper对象
        // 由Mybatis通过sqlSession来创建动态代理对象
        OrdersMapper mapper = sqlSession.getMapper(OrdersMapper.class);

        List<OrdersExt> list = mapper.findOrdersAndUserLazyLoading(); // n+1现象,是由于没有使用延时加载而导致的

        for (OrdersExt order : list) {
            System.out.println(order.getUser()); // 按需加载时,需要的时候再去查询数据库
        }

        sqlSession.close();
    }

(4)设置延迟加载
  在SqlMapConfig.xml中,配置settings标签,注意该标签的位置

    <settings>
        <!-- 开启延迟加载,默认值为true,即默认是立即加载 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 设置积极的懒加载,默认值是true,false的是按需加载 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

2.4、延迟加载思考

  • 不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??
  • 实现方法如下:
  • 定义两个mapper方法:
    • 1、查询订单列表
    • 2、根据用户id查询用户信息
  • 实现思路:
    • 先去查询第一个mapper方法,获取订单信息列表。
    • 在程序中(service),按需去调用第二个mapper方法去查询用户信息。
  • 总之:
    • 使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。

3、查询缓存

3.1、mybatis缓存分析

  • mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据库压力,提高系统性能。
  • Mybatis的缓存,包括一级缓存和二级缓存。
  • 一级缓存指的就是Sqlsession,在Sqlsession中有一个数据区域,是Map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值。一级缓存中的value,就是查询出的结果对象。
  • 二级缓存指的就是同一个namespace下的mapper,在二级缓存中,也有一个map结构,这个区域就是一级缓存区域。一级缓存中的key是由sql语句、条件、statement等信息组成的一个唯一值。一级缓存中的value,就是查询出的结果对象。
  • 一级缓存是默认使用的。
  • 二级缓存需要手动开启。
  • 一级缓存是Sqlsession级别的缓存。在操作数据库时需要构造Sqlsession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的Sqlsession之间的缓存数据区域(HashMap)是互相不影响的。
  • 二级缓存是mapper级别的缓存,多个Sqlsession去操作同一个mapper的sql语句,多个Sqlsession可以共用二级缓存,二级缓存是跨Sqlsession的。

3.2、一级缓存

3.2.1、原理


上图详解如下:

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

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

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

    Mybatis默认支持一级缓存。

3.2.2、测试1

    @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,如下图所示:

3.2.3、测试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("晓艺");
        user.setAddress("物资学院");
        // 执行增删改操作,清空缓存
        mapper.insertUser(user);
        sqlSession.commit();

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

        sqlSession.close();
    }

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

3.2.4、应用

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

例如:

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

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

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

3.3、二级缓存

3.3.1、原理

  • 下图是多个SqlSession请求UserMapper的二级缓存图解:

上图详解如下:

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

3.3.2、开启二级缓存

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

1、在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
在settings标签中添加以下内容:

    <!-- 开启二级缓存总开关 -->
    <setting name="cacheEnabled" value="true"/>

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

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

3.3.3、实现序列化

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

3.3.4、测试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,在close的时候,才会将数据写入到二级缓存中
        sqlSession1.close();

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

        // 关闭SqlSession3
        sqlSession3.close();
    }

  SQL输出结果,如下图所示:


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

3.3.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,在close的时候,才会将数据写入到二级缓存中
        sqlSession1.close();

        User user = new User();
        user.setUsername("晓艺");
        user.setAddress("物资学院");
        // 执行增删改操作,清空缓存
        mapper3.insertUser(user);
        sqlSession3.commit();

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

        // 关闭SqlSession3
        sqlSession3.close();
    }

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

3.3.6、禁用二级缓存

  • 该statement中设置userCache=false,可以禁用当前select语句的二级缓存,即每次查询都是去数据库中查询,默认情况下是true,即该statement使用二级缓存。如下代码所示:
    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User" useCache="true">
        SELECT * FROM USER WHERE id = #{id}
    </select>

3.3.7、刷新二级缓存

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

flushCache设置如下:

    <select id="findUserById" parameterType="int" resultType="com.itheima.mybatis.po.User" useCache="true" flushCache="false">
        SELECT * FROM USER WHERE id = #{id}
    </select>

3.3.8、整合ehcache(了解)

  • ehcache是一个分布式缓存。Mybatis本身是一个持久层框架,它不是专门的缓存框架,所以它对缓存的实现不够好,不能支持分布式缓存。

(1)什么是分布式缓存?

  • 系统为了提高性能,通常会对系统采用分布式部署(即:集群部署方式)
  • 不使用分布式缓存,缓存的数据在各个服务单独存储,不方便开发。所以要使用分布式缓存对缓存数据进行集中式管理。
  • Mybatis自身无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

(2)整合思路(重点)

  • Mybatis提供了一个Cache接口,同时它自己有一个默认的实现类PerpetualCache
  • 通过实现Cache接口可以实现mybatis缓存数据通过其他缓存数据库整合,mybatis的特长是sql,缓存数据管理不是mybatis的特长,为了提高mybatis的性能,所以需要mybatis和第三方缓存数据库整合,比如ehcache、memcache、redis等。
  • Mybatis提供接口如下:
  • Mybatis的默认实现类如下:

(3)整合ehcache的步骤

  • 1、引入ehcache的jar包
  • 2、在mapper映射文件中,配置cache标签的type为ehcache对cache接口的实现类类型
  • 3、加入ehcache的配置文件

第一步:引入ehcache的jar包,并添加至构建路径


第二步:在mapper映射文件中,配置cache标签的type为ehcache对cache接口的实现类类型

    <!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> 

第三步:添加ehcache的配置文件
在classpath下的config目录下,添加ehcache.xml,内容如下:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <!-- 缓存数据要存放的磁盘地址 -->
    <diskStore path="e:\others\develop\ehcache" />
    <!-- diskStore:指定数据在磁盘中的存储位置。
         defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略 
            以下属性是必须的:
                maxElementsInMemory - 在内存中缓存的element的最大数目
                maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
                eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false,那么还要根据timeToIdleSeconds,timeToLiveSeconds判断 
                overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 以下属性是可选的:
                timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大 
                timeToLiveSeconds - 缓存element的有效生命期,默认是0,也就是element存活时间无穷大 
                diskSpoolBufferSizeMB - 这个参数设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB。每个Cache都应该有自己的一个缓冲区
                diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false
                diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每隔120s,相应的线程会进行一次EhCache中数据的清理工作
                memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出) 
    -->

    <defaultCache maxElementsInMemory="1000"
        maxElementsOnDisk="10000000" 
        eternal="false" 
        overflowToDisk="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU">

    </defaultCache>
</ehcache>

第四步:测试ehcache的二级缓存,通过查看缓存命中率

3.3.9、应用场景

  • 使用场景:对于访问响应速度要求高,但是实时性不高的查询,可以采用二级缓存技术
  • 注意:在使用二级缓存的时候,要设置一下刷新间隔(cache标签中有一个flashInterval属性)来定时刷新二级缓存,这个刷新间隔根据具体需求来设置,比如设置30分钟、60分钟等,单位为毫秒。

3.3.10、局限性

  • Mybatis二级缓存细粒度的数据级别的缓存实现不好。
  • 场景:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次查询都是最新的商品信息,此时如果使用二级缓存,就无法实现当一个商品发生变化只刷新该商品的缓存信息而不刷新其他商品缓存信息,因为二级缓存是mapper级别的,当一个商品的信息发送更新,所有的商品信息缓存数据都会清空
  • 解决此类问题,需要在业务层根据需要对数据有针对性的缓存。
  • 比如:可以对经常变化的数据操作单独放到另一个namespace的mapper中

4、mybatis与spring集成

4.1、集成思路

  • 需要spring来管理数据源信息。
  • 需要spring通过单例方式管理SqlSessionFactory。
  • 使用SqlSessionFactory创建SqlSession(spring和mybatis整合自动完成)。
  • 持久层的mapper都需要由spring进行管理,spring和mybatis整合生成mapper代理对象。
    • 即:由spring来管理原始dao的实现类或者mapper代理的代理类。

4.2、集成步骤

  • 1、jar包集成
  • 2、配置文件集成(数据源)
  • 3、SqlSessionFactory集成
  • 4、Mapper接口集成

4.3、开始集成

4.3.1、搭建工程结构

4.3.2、jar包集成

Jar包如下(一共28个):
  Mybatis3.2.7 的jar包(mybatis核心包、依赖包)


  Spring3.2.0 的jar包

  Spring与mybatis的集成包

  Junit包

  数据库驱动包

  dbcp数据库连接池包

4.3.3、配置文件集成

  在config下,创建mybatis目录,然后创建SqlMapConfig.xml

<?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="com.itheima.mybatis.po.User" alias="user"/> -->
        <!-- 批量定义别名(推荐) -->
        <!-- name:指定批量定义别名的类包,别名为类名(首字母大小写都可) -->
        <package name="com.itheima.ms.po"/>
    </typeAliases>

    <!-- 注意:与spring集成后,数据源和事务交给spring来管理 -->

    <!-- 加载mapper,即加载映射文件 -->
    <mappers>
        <!-- 使用相对于类路径的资源,加载配置文件,后面讲课用:为了验证原始dao的实现方式 -->
        <mapper resource="mybatis/sqlmap/User.xml"/>
        <!-- 推荐使用:批量加载mapper文件,需要mapper接口文件和mapper映射文件名称相同且在同一个包下 -->
        <!-- 由spring来管理原始dao的实现类或者mapper代理的代理类,spring代理的时候,会自动将映射文件加载进去 -->
        <package name="com.itheima.ms.mapper"/> 
    </mappers>
</configuration>

  将db.properties和log4j.properties拷贝到config目录下。

4.3.4、Spring对SqlSessionFactory进行管理配置

  在config下,创建spring目录,然后创建applicationContext.xml,内容如下:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "
>

    <!-- 引用/加载java配置文件 -->
    <context:property-placeholder location="db.properties"/> <!-- java工程不需要指定classpath,web工程需要指定 classpath-->

    <!-- 配置数据源,使用dbcp数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
        <property name="driverClassName" value="${db.driver}"/>
        <property name="url" value="${db.url}"/>
        <property name="username" value="${db.username}"/>
        <property name="password" value="${db.password}"/>
        <property name="maxActive" value="10"/>
        <property name="maxIdle" value="5"/>
    </bean>

    <!-- 配置spring对SqlSessionFactory进行管理 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 指定mybatis的全局配置文件的路径 -->
        <property name="configLocation" value="mybatis/SqlMapConfig.xml"></property>
        <!-- SqlSessionFactory需要数据源信息,之前是写在sqlmapconfig.xml,现在需要重新指定 -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

4.4、Mybatis程序编写

4.4.1、原始dao方式

(1)编写dao接口

public interface UserDao {
    // 根据用户ID来查询用户信息
    public User findUserById(int id);
}

(2)编写dao实现类(继承SqlSessionDaoSupport)
  通过this.getSqlSession()获取sqlsession。

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {

    @Override
    public User findUserById(int id) {
        return this.getSqlSession().selectOne("test.findUserById", id);
    }

}

(3)编写Mapper映射文件
  在config/mybatis下创建sqlmap目录,然后创建User.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="test">
    <!-- 根据用户ID,查询用户信息 -->
    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

(4)在spring配置文件中配置UserDao实现类
  在applicationContext.xml中配置UserDao的实现类

    <!-- 配置spring管理原始dao的实现 -->
    <bean id="userDao" class="com.itheima.ms.dao.UserDaoImpl">
        <!-- 依赖注入sqlSessionFactory,因为UserDao的实现类继承 了SqlSessionDaoSupport-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

(5)编写测试代码

package com.itheima.ms.dao;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.ms.po.User;

public class UserDaoTest {

    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 读取spring的上下文,然后封装到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() {
        // 创建UserDao对象
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        // 调用UserDao对象的方法
        User user = userDao.findUserById(30);
        System.out.println(user);
    }

}

4.4.2、Mapper代理方式

(1)编写mapper接口

public interface UserMapper {

    // 根据用户ID来查询用户信息
    public User findUserById(int id);

}

(2)编写mapper映射文件
  将映射文件放到UserMapper接口的同包下
  UserMapper.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.itheima.ms.mapper.UserMapper">
    <!-- 根据用户ID,查询用户信息 -->
    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM USER WHERE id = #{id}
    </select>
</mapper>

(3)配置mapper代理类
  在spring中定义bean,Mapper代理开发方式有两种bean的定义方法,一种是MapperFactoryBean一种是MapperScannerConfigurer(推荐)

  • 通过MapperFactoryBean创建代理对象,即单个mapper代理类配置(了解)
    <!-- mapper代理开发方式之单个mapper配置 -->
    <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <!-- 设置代理类的接口 -->
        <property name="mapperInterface" value="com.itheima.ms.mapper.UserMapper"></property>
        <!-- 依赖注入sqlSessionFactory,因为 MapperFactoryBean也继承 了SqlSessionDaoSupport-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>
  • 存在问题:一个mapper定义一个bean,很麻烦。
  • 通过MapperScannerConfigurer批量扫描创建代理对象,即批量设置mapper代理类(掌握)
    <!-- mapper代理开发方式之批量mapper配置,默认bean的id为类名首字母小写 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 指定批量mapper配置的包名 -->
        <property name="basePackage" value="com.itheima.ms.mapper"></property>
        <!-- 当只有一个SqlSessionFactory时,默认是不需要配置SqlSessionFactory的 -->
        <!-- 当有多个SqlSessionFactory时,可以指定使用的SqlSessionFactory, -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

(4)编写测试代码

package com.itheima.ms.mapper;

import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.itheima.ms.po.User;

public class UserMapperTest {
    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 读取spring的上下文,然后封装到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testFindUserById() {
        // 创建UserMapper对象
        UserMapper userMapper = (UserMapper) ctx.getBean("userMapper");
        // 调用UserMapper对象的方法
        User user = userMapper.findUserById(30);
        System.out.println(user);
    }

}

5、Mybatis的逆向工程(会用就行)

5.1、什么是逆向工程

  • 简单点说,就是通过数据库中的单表,自动生成java代码。
  • Mybatis官方提供了逆向工程,可以针对单表自动生成mybatis代码(比如:mapper.java、mapper.xml、po类)
  • 企业开发中,逆向工程是个很常用的工具。

5.2、下载逆向工程

  • 下载链接:https://github.com/mybatis/generator/releases/tag/mybatis-generator-1.3.2

5.3、使用方法

  • 1、创建逆向工程(普通的java工程即可),导入jar包添加至构建路径
  • 2、创建generator配置文件
  • 3、使用java类来执行逆向工程
  • 4、把生成的代码拷贝到项目中
  • 5、在正式项目中使用逆向工程生成的代码

5.3.1、创建逆向工程(普通的java工程即可),导入jar包添加至构建路径

5.3.2、创建generator配置文件

  在config下,创建generatorConfig.xml配置文件:
  文件内容可以从逆向工程的jar包中docs目录下的index.html中找到相关代码
  文件内容位置:mybatis-generator-core-1.3.2/docs/configreference/xmlconfig.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--MySql数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection 
            driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/mybatis" 
            userId="root"
            password="root">

        </jdbcConnection>
        <!-- Oracle数据库连接的信息:驱动类、连接地址、用户名、密码  -->
        <!-- 
        <jdbcConnection 
            driverClass="oracle.jdbc.OracleDriver" 
            connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" 
            userId="yycg" 
            password="yycg"> 
        </jdbcConnection> 
        -->


        <!-- 默认 false时,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
             设置为true时,把JDBC DECIMAL 和 NUMERIC 类型解析为 java.math.BigDecimal 
        -->

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO类的位置 -->
        <javaModelGenerator targetPackage="com.itheima.ms.po" targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com.itheima.ms.mapper" targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.itheima.ms.mapper" targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>

        <!-- 指定要生成mybatis代码的数据库表 -->
        <table tableName="items"></table>
        <table tableName="orders"></table>
        <table tableName="orderdetail"></table>
        <table tableName="user"></table>
    </context>
</generatorConfiguration>

5.3.3、使用Generator类来执行逆向工程

  文件内容位置:mybatis-generator-core-1.3.2/docs/running/runningWithJava.html
Generator.java

public class Generator {
    public static void main(String[] args) throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("config/generatorConfig.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}

  小问题:通过逆向工程生成的代码会有一个报错,是因为缺少mybatis的jar包导致的,虽然不影响我们使用,但是为了好看,我们导入mybatis-3.2.7.jar,并添加至构建路径,即可解决问题。

5.3.4、将逆向工程生成的代码拷贝到指定项目中

  • 如果正式项目中已经有po类所在的包了,那么就只需要拷贝po类到指定包下就可以。
  • 如果正式项目中没有po包,那么就把逆向工程中整个po类的包拷贝过去。
  • Mapper.xml和mapper.java的拷贝与po类一样。

5.3.5、使用逆向工程生成的代码

  小需求:根据商品名称查询商品记录

public class ItemsMapperTest {
    // spring上下文
    private ApplicationContext ctx;

    @Before
    public void setUp() throws Exception {
        // 读取spring的上下文,然后封装到ctx
        ctx = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
    }

    @Test
    public void testSelectByExample() {
        // 创建ItemsMapper对象
        ItemsMapper itemsMapper = (ItemsMapper) ctx.getBean("itemsMapper");

        ItemsExample example = new ItemsExample();
        // 使用ItemsExample对象创建离线查询对象
        Criteria criteria = example.createCriteria();
        // 使用离线查询对象Criteria来封装查询条件
        criteria.andNameLike("%包%"); // 因为模糊查询的实现和等值查询的实现式一样的,用到的都是#{xxx},所以需要我们手动添加%
        // 调用ItemsMapper对象的方法
        List<Items> list = itemsMapper.selectByExample(example);

        System.out.println(list);
    }

}

  SQL输出结果,如下图所示:

5.4、注意事项

  • Mapper.xml文件已经存在时,如果进行重新生成则mapper.xml文件时,内容不被覆盖而是进行内容追加,结果导致mybatis解析失败。
  • 解决方法:删除原来已经生成的mapper.xml文件再进行生成。
  • Mybatis自动生成的po及mapper.java文件不是内容而是直接覆盖没有此问题。

版权声明:本文为chenmingjun原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/chenmingjun/p/9838546.html