当前位置: 首页 > article >正文

【Java_EE】Day04 MyBatis的关联映射和缓存机制

MyBatis的关联映射和缓存机制

一对一查询

主键数据表中的一条记录最多可以与另一个数据表的一条数据相关;例如一个人只能有一个身份证,同时一个身份证也只对应一个人。

在MyBatis中,通过<association>元素来处理一对一关联关系。<association>元素提供了一系列属性用于维护数据表之间的关系。属性如下:

属性说明
property用于指定映射到实体类对象的属性,要求与表字段一一对应
column用于指定表中对应的字段
javaType用于指定映射到实体对象的属性的类型
jdbcType用于指定数据表中对应字段的类型
fetchType用于指定在关联查询时是否启用延时加载,包含值:lazy(懒汉)和eager(饿汉),默认值为lazy(懒汉)(默认关联映射延迟加载)
select用于指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询
autoMapping用于指定是否自动映射
typeHandler用于指定一个类型处理器

<association>元素是<resultMap>的子元素,他有两种配置方式,分别是:嵌套查询和嵌套结果。

嵌套查询方式
  1. 先准备一下数据,在mybatis数据库中分别创建名称为tb_idcard的身份证数据表和名称为tb_person的个人数据表,同时预先插入两条数据,具体的SQL语句如下:
# 创建数据表 tb_idcard和tb_person同时插入数据
USE mybatis;
# 创建一个名称为tb_idcard的表
CREATE TABLE tb_idcard(
  id INT PRIMARY KEY AUTO_INCREMENT,
  CODE VARCHAR(18)
);
# 插入两条数据
INSERT INTO tb_idcard (CODE) VALUES ('152221198711020624');
INSERT INTO tb_idcard (CODE) VALUES ('152201199008150317');
# 创建一个名称为tb_person的表
CREATE TABLE tb_person (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(32),
  age INT,
  sex VARCHAR(8),
  card_id INT UNIQUE ,
  FOREIGN KEY (card_id) REFERENCES tb_idcard(id)
);
# 插入两条数据
INSERT INTO tb_person (name, age, sex, card_id) VALUES ('Rose', 22, '女', 1);
INSERT INTO tb_person (name, age, sex, card_id) VALUES ('jack', 23, '男', 2);
  1. 创建POJO对象,分别在com.itheima.pojo包下创建IdCard.java类及Person.java类:

IdCard.java

package com.itheima.pojo;

/**
 * @author Zhang
 */
public class IdCard {
    private Integer id;
    private String code;

    public Integer getId() {
        return id;
    }

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

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    @Override
    public String toString() {
        return "IdCard{" +
                "id=" + id +
                ", code='" + code + '\'' +
                '}';
    }
}

Person.java

package com.itheima.pojo;

/**
 * @author Zhang
 */
public class Person {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private IdCard card;

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", card=" + card +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public IdCard getCard() {
        return card;
    }

    public void setCard(IdCard card) {
        this.card = card;
    }
}

  1. com.itheima.mapper包中创建“身份证映射文件”IdCardMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。IdCardMapper.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.mapper.IdCardMapper">
<!--    根据id查询证件信息-->
    <select id="findCodeById" parameterType="Integer" resultType="com.itheima.pojo.IdCard">
        SELECT id, CODE
        FROM mybatis.tb_idcard
        WHERE id = #{id}
    </select>
</mapper>
  1. com.itheima.mapper包中创建“人员映射文件”PersonMapper.xml,并在映射文件中编写一对一关联映射查询的配置信息。PersonMapper.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.mapper.PersonMapper">
<!--    嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型-->
    <select id="findPersonById" parameterType="Integer" resultMap="IdCardWithPersonResult">
        SELECT *
        FROM mybatis.tb_person
        WHERE id = #{id}
    </select>
    <resultMap id="IdCardWithPersonResult" type="Person">
        <id property="id" column="id" />
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
<!--        一对一映射关系:association使用select属性引入另外一条SQL语句-->
        <association property="card" column="card_id" javaType="IdCard" select="com.itheima.mapper.IdCardMapper.findCodeById"/>
    </resultMap>
</mapper>
  1. 在核心配置文件mybatis-config.xml中,引入IdCardMapper.xml和PersonMapper.xml映射文件,并为com.itheima.pojo包下的所有实体类定义别名,代码如下:
<?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>
    <!-- 加载类路径下的属性文件 -->
    <properties resource="db.properties"/>
<!--    使用扫描包的形式定义别名-->
    <typeAliases>
        <package name="com.itheima.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!-- 使用db.properties文件中的配置 -->
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driver}"/>
                <property name="url" value="${mysql.url}"/>
                <property name="username" value="${mysql.username}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml" />
        <mapper resource="com/itheima/mapper/StudentMapper.xml"/>
        <mapper resource="com/itheima/mapper/EmployeeMapper.xml"/>
        <mapper resource="com/itheima/mapper/CustomerMapper.xml"/>
        <mapper resource="com/itheima/mapper/IdCardMapper.xml"/>
        <mapper resource="com/itheima/mapper/PersonMapper.xml"/>
    </mappers>
</configuration>
  1. 在测试类MyBatisTest.java中,编写方法findPersonByIdTest(),具体代码如下:
    // 嵌套查询
    @Test
    public void findPersonByIdTest() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 2.使用MyBatis嵌套查询的方式查询id为1的人的信息
        Person person = session.selectOne("com.itheima.mapper.PersonMapper.findPersonById", 1);
        // 3.输出查询信息
        System.out.println(person);
        // 4.关闭SqlSession
        session.close();
    }

但是MyBatis嵌套查询的方式要执行多条SQL语句,对于大型数据库集合和列表展示来说,这样可能会导致成百上千的SQL语句被执行,从而极大小号数据库性能并且会降低查询效率,这并不是开发人员期望的,接下来,我们通过Mybatis提供的嵌套结果方式进行关联查询:

  1. com.itheima.mapper包中PersonMapper.xml增加findPersonById2查询,并在映射文件中编写一对一关联映射查询的配置信息。PersonMapper.xml具体代码如下:
<!--    嵌套结果-使用嵌套结果映射来处理重复的联合结果的子集-->
    <select id="findPersonById2" parameterType="Integer" resultMap="IdCardWithPersonResult2">
        SELECT p.*, idcard.CODE
        FROM mybatis.tb_person p, mybatis.tb_idcard idcard
        WHERE p.card_id = idcard.id
        AND p.id = #{id}
    </select>
    <resultMap id="IdCardWithPersonResult2" type="Person">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sex" column="sex"/>
        <association property="card" javaType="IdCard">
            <id property="id" column="card_id"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
  1. 在测试类MyBatisTest.java中,编写方法findPersonByIdTest2(),具体代码如下:
    // 嵌套结果
    @Test
    public void findPersonByIdTest2() {
        // 1.通过工具类生成SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 2.使用MyBatis嵌套结果的方法查询id为1的人员信息
        Person person = session.selectOne("com.itheima.mapper.PersonMapper.findPersonById2", 1);
        // 3.输出查询信息
        System.out.println(person);
        // 4.关闭SqlSession
        session.close();
    }

MyBatis延迟加载

MyBatis延迟加载是指不需要在每个映射文件中单独配置延迟加载。具体来说,MyBatis延迟加载主要与一对一(association)和一对多(collection)的关联关系相关。默认情况下,MyBatis会立即加载所有关联的对象,但开启延迟加载后,这些关联对象仅在真正使用时才被加载。其目的是为了使MyBatis在一定程度上降低运行消耗并提高查询效率。

配置方式如下:

  1. 在核心配置文件mybatis-config.xml中的<configuration>根元素中,<properties>属性配置元素之后,增加或修改如下代码:
    <settings>
        <!--    打开延迟加载的开关-->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!--    将积极加载改为消息加载,即按需加载-->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

一对多查询

开发人员接触较多的关联关系使一对多(或多对一)关系。例如:一个用户可以有多笔订单,但是一个订单不可以由多个用户所有。

在MyBatis中,通过<collection>元素来处理一对多关联关系,<collection>元素的属性基本与<association>一样,只有一个特殊属性ofType。该属性与javaType属性相对应,用于指定实体类对象中集合类属性所包含的元素类型。<collection>元素也有嵌套查询和嵌套结果两种配置方式。

下面介绍如何在MyBatis中处理一对多关联关系,具体步骤如下:

  1. 先在mybatis数据库中,创建两个数据表,分别为tb_user(用户数据表)和tb_orders(订单表),同时在表中预先插入几条测试数据,具体SQL语句如下:
# 在`mybatis`数据库中,创建两个数据表,分别为`tb_user`(用户数据表)和`tb_orders`(订单表),同时在表中预先插入几条测试数据
USE mybatis;
# 创建一个名称为tb_user的表
CREATE TABLE tb_user (
    id INT(32) AUTO_INCREMENT PRIMARY KEY ,
    username varchar(32) ,
    address varchar(256)
);

# 插入三条数据
INSERT INTO tb_user VALUES ('1', '小明', '北京');
INSERT INTO tb_user VALUES ('2', '李华', '上海');
INSERT INTO tb_user VALUES ('3', '李刚', '上海');
# 创建一个名称为tb_orders的表
CREATE TABLE tb_orders (
    id INT(32) AUTO_INCREMENT PRIMARY KEY ,
    user_id INT(32) NOT NULL ,
    number VARCHAR(32) NOT NULL ,
    FOREIGN KEY (user_id) REFERENCES tb_user (id)
);
# 插入三条数据
INSERT INTO tb_orders (id, user_id, number) VALUES ('1', '1', '1000011');
INSERT INTO tb_orders (id, user_id, number) VALUES ('2', '1', '1000012');
INSERT INTO tb_orders (id, user_id, number) VALUES ('3', '2', '1000013');
  1. com.itheima.pojo包中,创建持久化类Orders,并定义属性,代码如下:
package com.itheima.pojo;

/**
 * 订单持久化类
 * @author Zhang
 */
public class Orders {
    // 订单id
    private Integer id;
    // 订单编号
    private String number;

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", number='" + number + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}

  1. com.itheima.pojo包下创建持久化类Users,并定义数据表中对应的所有属性,代码如下:
package com.itheima.pojo;

import java.util.List;

/**
 * 用户持久化类
 * @author Zhang
 */
public class Users {
    // 用户id
    private Integer id;
    // 用户名
    private String username;
    // 地址
    private String address;
    // 用户关联订单
    private List<Orders> ordersList;

    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", address='" + address + '\'' +
                ", ordersList=" + ordersList +
                '}';
    }

    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 getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public List<Orders> getOrdersList() {
        return ordersList;
    }

    public void setOrdersList(List<Orders> ordersList) {
        this.ordersList = ordersList;
    }
}

  1. com.itheima.mapper包下,创建用户实体映射文件UsersMapper.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.mapper.UsersMapper">
    <select id="findUserWithOrders" parameterType="Integer" resultMap="UserWithOrdersResult">
        SELECT u.*, o.id AS orders_id, o.number
        FROM mybatis.tb_user u , mybatis.tb_orders o
        WHERE u.id = o.user_id
        AND u.id = #{id}
    </select>
<!--    一对多:查看某用户及其关联的订单信息
        注意:当关联查询出的列名相同时,要使用别名进行区分-->
    <resultMap id="UserWithOrdersResult" type="Users">
        <id column="id" property="id"/>
        <result column="username" property="username"/>
        <result column="address" property="address"/>
<!--        一对多关联映射:collection
            ofType:表示属性集合中元素的类型
            List<Orders>属性即Orders类        -->
        <collection property="ordersList" ofType="Orders">
            <id column="orders_id" property="id"/>
            <result column="number" property="number"/>
        </collection>
    </resultMap>
</mapper>
  1. 在核心配置文件mybatis-config.xml中,引入UsersMapper.xml,代码如下:
<mapper resource="com/itheima/mapper/UsersMapper.xml"/>
  1. 在测试类MyBatisTest中编写测试方法findUserTest(),代码如下:
    /**
     * 一对多
     */
    @Test
    public void findUserTest() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 查询id为1的用户信息
        Users users = session.selectOne("com.itheima.mapper.UsersMapper.findUserWithOrders", 1);
        // 3.输出查询信息
        System.out.println(users);
        // 4.关闭SqlSession
        session.close();

    }

多对多查询

以订单和商品为例,一个订单可以包含多种商品,而一个商品又可以属于多个订单,订单和商品就属于多对多的关联关系。

在数据库中,多对多的关联关系通常使用一个中间表来维护,中间表的订单id作为外键关联订单表id,中间表中的商品id作为外键关联商品表的id。

下面以订单表和商品表之间的多对多关系为例来讲解如何使用MyBatis处理多对多关系,具体实现步骤如下:

  1. mybatis数据库中创建tb_product商品表和tb_ordersitem中间表,同时插入几条数据,代码如下:
## 在mybatis数据库中创建tb_product商品表和tb_ordersitem中间表,同时插入几条数据
USE mybatis;
# 创建一个名称为tb_product的表
CREATE TABLE tb_product (
  id INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(32) ,
  price DOUBLE
);
# 插入三条数据
INSERT INTO tb_product VALUES ('1', 'Java基础入门', '44.5');
INSERT INTO tb_product VALUES ('2', 'JavaWeb程序入门', '38.5');
INSERT INTO tb_product VALUES ('3', 'SSM框架整合实践入门', '50.0');
# 创建一个名称为tb_ordersitem的表
CREATE TABLE tb_ordersitem (
  id INT PRIMARY KEY AUTO_INCREMENT,
  orders_id INT(32) ,
  product_id INT(32) ,
  FOREIGN KEY (orders_id) REFERENCES tb_orders (id),
  FOREIGN KEY (product_id) REFERENCES tb_product (id)
);
# 插入三条数据
INSERT INTO tb_ordersitem VALUES ('1', '1', '1');
INSERT INTO tb_ordersitem VALUES ('2', '1', '3');
INSERT INTO tb_ordersitem VALUES ('3', '3', '3');
  1. com.itheima.pojo 包下创建一个Product类,封装商品属性,代码如下:
package com.itheima.pojo;

import java.util.List;

/**
 * 商品持久化类
 * @author Zhang
 */
public class Product {
    private Integer id;
    private String name;
    private Double price;
    private List<Orders> orders;

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                ", orders=" + orders +
                '}';
    }

    public List<Orders> getOrders() {
        return orders;
    }

    public void setOrders(List<Orders> orders) {
        this.orders = orders;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

  1. 在订单持久化类Orders.java中添加商品集合属性以及对应的getter/settertoString()方法,代码如下:
private List<Product> productList;

    @Override
    public String toString() {
        return "Orders{" +
                "id=" + id +
                ", number='" + number + '\'' +
                ", productList=" + productList +
                '}';
    }

    public List<Product> getProductList() {
        return productList;
    }

    public void setProductList(List<Product> productList) {
        this.productList = productList;
    }
  1. com.itheima.mapper包下,创建订单实体映射文件OrdersMapper.xml,用于编写订单信息查询的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.itheima.mapper.OrdersMapper">
    <!--    多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
    <select id="findOrdersWithProduct" parameterType="Integer" resultMap="OrdersWithProductResult">
        SELECT *
        FROM mybatis.tb_orders
        WHERE id = #{id}
    </select>
    <resultMap id="OrdersWithProductResult" type="Orders">
        <id property="id" column="id"/>
        <result property="number" column="number"/>
        <collection property="productList" column="id" ofType="Product" select="com.itheima.mapper.ProductMapper.findProductById"/>
    </resultMap>
</mapper>
  1. com.itheima.mapper包下,创建订单实体映射文件ProductMapper.xml,用于编写订单信息查询的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.itheima.mapper.ProductMapper">
    <select id="findProductById" parameterType="Integer" resultType="Product">
        SELECT * FROM mybatis.tb_product WHERE id IN (
            SELECT tb_ordersitem.product_id FROM tb_ordersitem WHERE orders_id = #{id}
            )
    </select>
</mapper>
  1. 将新创建的映射文件OrdersMapper.xmlProductMapper.xml的文件路径配置到核心配置文件mybatis-config.xml中,配置代码如下:
        <mapper resource="com/itheima/mapper/ProductMapper.xml"/>
        <mapper resource="com/itheima/mapper/OrdersMapper.xml"/>
  1. MyBatisTest中编写多对多关联查询的测试方法findOrdersTest(),其代码如下:
    /**
     * 多对多
     */
    @Test
    public void findOrdersTest() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 查询id为1的订单信息
        Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithProduct", 1);
        // 3.输出查询信息
        System.out.println(orders);
        // 4.关闭SqlSession
        session.close();
    }

这种方法来建立多对多映射查询过于麻烦,我们依然可以使用嵌套结果方法,代码如下:

  1. OrdersMapper.xml中添加多对多嵌套结果查询代码:
    <!--    多对多嵌套结果查询: 查询某订单及其关联的商品详情 -->
    <select id="findOrdersWithProduct2" parameterType="Integer" resultMap="OrdersWithProductResult2">
        SELECT o.*, p.id AS pid, p.name, p.price
        FROM mybatis.tb_orders o, mybatis.tb_product p , mybatis.tb_ordersitem oi
        WHERE oi.orders_id = o.id AND oi.product_id = p.id AND o.id = #{id}
    </select>
    <!--    自定义手动映射类型  -->
    <resultMap id="OrdersWithProductResult2" type="Orders">
        <id column="id" property="id"/>
        <result column="number" property="number"/>
        <collection property="productList" ofType="Product">
            <id property="id" column="pid"/>
            <result property="name" column="name"/>
            <result property="price" column="price"/>
        </collection>
    </resultMap>
  1. 编写测试方法findOrdersTest2(),代码如下:
    /**
     * 多对多-嵌套结果
     */
    @Test
    public void findOrdersTest2() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 2.查询id为1的订单信息
        Orders orders = session.selectOne("com.itheima.mapper.OrdersMapper.findOrdersWithProduct2", 1);
        // 3.输出查询信息
        System.out.println(orders);
        // 4.关闭SqlSession
        session.close();
    }

MyBatis的缓存机制

在实际业务开发中,通常对数据库的性能要求较高,MyBatis中通过缓存机制来提高数据库性能。

一级缓存

MyBatis的一级缓存是SqlSession级别的缓存。具体来说就是:如果同一个SqlSession对象多次执行同一个完全相同的SQL语句,在第一次完成操作后,MyBatis会将查询结果写入一级缓存,此后,如果程序没有执行插入、更新、删除等操作,第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不再去数据库中操作,从而提高数据库的查询效率。

下面通过一个案例对MyBatis一级缓存的应用进行解释,该案例要求根据图书id查询图书信息:

  1. mybatis数据库创建名称为tb_book的数据表,同时预先插入几条测试数据。代码如下:
## 创建tb_book数据表,插入几条测试数据
USE mybatis;
# 创建一个名称为tb_book的表
CREATE TABLE tb_book (
    id INT PRIMARY KEY AUTO_INCREMENT,
    bookName VARCHAR(255),
    price DOUBLE,
    author VARCHAR(40)
);
# 插入3条数据
INSERT INTO tb_book (bookName, price, author) VALUES ('Java基础入门', 45.0, '传智播客高教产品研发部'),
                                                     ('Java基础案例教程', 48.0, '黑马程序员'),
                                                     ('JavaWeb程序设计任务教程', 50.0, '黑马程序员');

  1. 在项目com.itheima.pojo包下创建持久化类Book,在Book类中定义相对应的属性和getter/setter方法及toString()方法,代码如下:
package com.itheima.pojo;

/**
 * 在项目的com.itheima.pojo包下创建持久化类Book,在Book类中定义对应的属性,相应的getter/setter、toString()方法,代码如下:
 * 图书持久化类
 * @author Zhang
 */
public class Book {
    // 图书id
    private Integer id;
    // 图书名称
    private String bookName;
    // 图书价格
    private Double price;
    // 图书作者
    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", bookName='" + bookName + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

  1. com.itheima.mapper包中,创建图书映射文件BookMapper.xml,并在该文件中编写图书id查询图书的SQL语句,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BookMapper">
    <!-- 根据id查询图书信息 -->
    <select id="findBookById" parameterType="Integer" resultType="com.itheima.pojo.Book">
        SELECT *
        FROM mybatis.tb_book
        WHERE id = #{id}
    </select>
</mapper>
  1. 在核心配置文件mybatis-config.xml中的<mappers>元素下,引入BookMapper.xml映射文件,代码如下:
        <mapper resource="com/itheima/mapper/BookMapper.xml"/>
  1. 由于需要通过log4j日志组件查看一级缓存的工作状态,所以需要在maven依赖仓库pom.xml文件中引入log4j的相关依赖,具体代码如下:
        <!-- 配置log4j日志包依赖 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
  1. src/main/resources目录下创建log4j.properties文件,用于配置MyBatis和控制台的日志。代码如下:
# Global Logging configuration
log4j.rootLogger=DEBUG, Console
# Console output configuration
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
# Log output level
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 在测试类MyBatisTest中编写测试方法findBookByIdTest1()方法,具体代码如下:
    /**
     * 根据id查询图书信息
     */
    @Test
    public void findBookByIdTest1() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session = MyBatisUtils.getSession();
        // 2.根据id查询图书信息
        Book book1 = session.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
        // 3.输出查询信息
        System.out.println(book1.toString());
        // 2.根据id查询图书信息
        Book book2 = session.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
        // 3.输出查询信息
        System.out.println(book2.toString());
        // 4.关闭SqlSession
        session.close();
    }

执行结果如下。在执行第二次查询时除了查询结果没有再输出任何信息,说明已经通过MyBatis的一级缓存存下了。:

2024-09-24 20:03:15,516 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==>  Preparing: SELECT * FROM mybatis.tb_book WHERE id = ?
2024-09-24 20:03:15,540 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Parameters: 1(Integer)
2024-09-24 20:03:15,555 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - <==      Total: 1
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}

二级缓存

由一级缓存的内容可知,相同的Mapper类使用相同的SQL语句,如果SqlSession不同,则如果两个SqlSession查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBaits的二级缓存。

MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession可以共用二级缓存,并且二级缓存可以自定义缓存资源。
在 MyBatis中,一个Mapper.xml文件通常被称为一个Mapper,MyBatis以namespace区分 Mapper,如果多个SqISession对象使用同一个 Mapper 的相同査询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写人二级缓存,此后,如果程序没有执行插人、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。MyBatis二级缓存的执行过程如下。

开启二级缓存步骤如下:

  1. 开启二级缓存的全局配置,在核心配置文件mybatis-config.xml<settings>中添加并启用二级缓存全局配置,代码如下:
		<!-- 开启二级缓存 -->
        <setting name="cacheEnabled" value="true"/>
  1. 修改映射文件BookMapper.xml,在映射文件的<mapper>元素下追加编写<cache>元素开启当前namespace的二级缓存,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- 在com.itheima.mapper中创建图书映射文件BookMapper.xml,并在该文件中编写根据id查询语句 -->
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.BookMapper">
    <cache/>
    <!-- 根据id查询图书信息 -->
    <select id="findBookById" parameterType="Integer" resultType="com.itheima.pojo.Book">
        SELECT *
        FROM mybatis.tb_book
        WHERE id = #{id}
    </select>
</mapper>
  1. **(先这么干,别问!)Book.java持久化类实现可序列化接口
package com.itheima.pojo;

import java.io.Serializable;

/**
 * 在项目的com.itheima.pojo包下创建持久化类Book,在Book类中定义对应的属性,相应的getter/setter、toString()方法,代码如下:
 * 图书持久化类
 * @author Zhang
 */
public class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    // 图书id
    private Integer id;
    // 图书名称
    private String bookName;
    // 图书价格
    private Double price;
    // 图书作者
    private String author;

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", bookName='" + bookName + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }
}

  1. 在测试类MyBatisTest中添加测试方法findBookByIdTest1(),代码如下:
	/**
     * 根据id查询图书信息
     */
    @Test
    public void findBookByIdTest1() {
        // 1.通过工具类获取SqlSession对象
        SqlSession session1 = MyBatisUtils.getSession();
        SqlSession session2 = MyBatisUtils.getSession();
        // 2.根据id查询图书信息
        Book book1 = session1.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
        // 3.输出查询信息
        System.out.println(book1.toString());
        // 4.关闭SqlSession
        session1.close();
        // 5.根据id查询图书信息
        Book book2 = session2.selectOne("com.itheima.mapper.BookMapper.findBookById", 1);
        // 6.输出查询信息
        System.out.println(book2.toString());
        // 7.关闭SqlSession
        session2.close();
    }

执行结果应该是这样的:

2024-09-24 20:44:56,021 [main] DEBUG [com.itheima.mapper.BookMapper] - Cache Hit Ratio [com.itheima.mapper.BookMapper]: 0.0
2024-09-24 20:44:56,166 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==>  Preparing: SELECT * FROM mybatis.tb_book WHERE id = ?
2024-09-24 20:44:56,194 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - ==> Parameters: 1(Integer)
2024-09-24 20:44:56,210 [main] DEBUG [com.itheima.mapper.BookMapper.findBookById] - <==      Total: 1
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}
2024-09-24 20:44:56,215 [main] WARN  [org.apache.ibatis.io.SerialFilterChecker] - As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
2024-09-24 20:44:56,220 [main] DEBUG [com.itheima.mapper.BookMapper] - Cache Hit Ratio [com.itheima.mapper.BookMapper]: 0.5
Book{id=1, bookName='Java基础入门', price=45.0, author='传智播客高教产品研发部'}

Cache Hit Ratio(缓存命中率)

缓存命中率(Cache Hit Ratio)是衡量缓存系统效率的指标。它表示请求的数据有多少直接从缓存中获得,而不需要访问更慢的外部存储。计算方法是用缓存命中的次数除以总请求次数,然后乘以100%,得到百分比。

简单来说:

  • 缓存命中:数据已经在缓存中,能快速提供。
  • 缓存未命中:缓存中没有数据,需要从外部存储获取,比较耗时。

缓存命中率越高,系统性能越好,因为减少了从外部存储读取数据的时间。

案例:商品的类别

现有一个商品表product和一个商品类别表category,其中,商品类别表category和商品表product是一对多的关系。商品表product和商品表category分别如下标所示:

商品编号(id)商品名称(goodsname)商品单价(price)商品类别(type)
1电视机50001
2冰箱40002
3空调30002
4洗衣机20002
商品类别编号(id)商品类别名称(typename)
1黑色家电
2白色家电

本案例具体要求如下:根据以上两表在数据库分别创建商品表product和一个商品类别表category,并通过MyBatis查询商品类别为白色家电的商品所有信息。

代码实现

  1. 首先创建商品表product和一个商品类别表category,并插入如上表的数据:
# 创建商品类别表product
CREATE TABLE product (
    id INT PRIMARY KEY AUTO_INCREMENT,
    goodsname VARCHAR(32),
    price DOUBLE,
    type INT
);

# 创建商品表category
CREATE TABLE category (
    id INT PRIMARY KEY AUTO_INCREMENT,
    typename VARCHAR(32)
);

# product表中插入数据
INSERT INTO product VALUES (1, '电视机', 5000, 1),
                           (2, '冰箱', 4000, 2),
                           (3, '空调', 3000, 2),
                           (4, '洗衣机', 2000, 2);

# category表中插入数据
INSERT INTO category VALUES (1, '黑色家电'),
                            (2, '白色家电');

  1. 写一下这两个数据表的实体类,并添加setter/getter方法及toString()方法:

Product02.java

package com.itheima.pojo;

/**
 * @author Zhang
 */
public class Product02 {
    private Integer id;
    private String goodsname;
    private Double price;
    private Integer type;

    @Override
    public String toString() {
        return "Product02{" +
                "id=" + id +
                ", goodsname='" + goodsname + '\'' +
                ", price=" + price +
                ", type=" + type +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getGoodsname() {
        return goodsname;
    }

    public void setGoodsname(String goodsname) {
        this.goodsname = goodsname;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }
}

Category.java

package com.itheima.pojo;

/**
 * @author Zhang
 */
public class Category {
    private Integer id;
    private String typename;

    @Override
    public String toString() {
        return "Category{" +
                "id=" + id +
                ", typename='" + typename + '\'' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getTypename() {
        return typename;
    }

    public void setTypename(String typename) {
        this.typename = typename;
    }
}

  1. MybatisTest.java中写一下测试方法,注意这里要获取多个值,所以选择selectList方法,并提前将Product02列表创建好:
    @Test
    public void findWhiteTest() {
        SqlSession session = MyBatisUtils.getSession();
        List<Product02> products = session.selectList("com.itheima.mapper.Product02Mapper.findWhite");
        for (Product02 product : products) {
            System.out.println(product);
        }
        session.close();
    }
  1. 然后我们去写一下查询的sql语句,创建一个Product02Mapper,并在其中创建一个findWhite方法:
<?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.mapper.Product02Mapper">
    <select id="findWhite" parameterType="com.itheima.pojo.Product02" resultType="com.itheima.pojo.Product02">
        SELECT *
        FROM mybatis.product p2, mybatis.category c
        WHERE p2.type = c.id
        AND c.typename = '白色家电'
    </select>
</mapper>
  1. 测试之前不要忘记在核心配置文件中添加mapper索引,我们进入mybatis-config.xml
	<mappers>
        <mapper resource="mapper/UserMapper.xml" />
        <mapper resource="com/itheima/mapper/StudentMapper.xml"/>
        <mapper resource="com/itheima/mapper/EmployeeMapper.xml"/>
        <mapper resource="com/itheima/mapper/CustomerMapper.xml"/>
        <mapper resource="com/itheima/mapper/IdCardMapper.xml"/>
        <mapper resource="com/itheima/mapper/PersonMapper.xml"/>
        <mapper resource="com/itheima/mapper/UsersMapper.xml"/>
        <mapper resource="com/itheima/mapper/ProductMapper.xml"/>
        <mapper resource="com/itheima/mapper/OrdersMapper.xml"/>
        <mapper resource="com/itheima/mapper/BookMapper.xml"/>
        <mapper resource="com/itheima/mapper/Product02Mapper.xml"/>
    </mappers>
  1. 回到MybatisTest.java测试类中,对mapper中定义的findWhite()方法进行测试:
2024-10-08 21:43:49,416 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - ==>  Preparing: SELECT * FROM mybatis.product p2, mybatis.category c WHERE p2.type = c.id AND c.typename = '白色家电'
2024-10-08 21:43:49,444 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - ==> Parameters: 
2024-10-08 21:43:49,459 [main] DEBUG [com.itheima.mapper.Product02Mapper.findWhite] - <==      Total: 3
Product02{id=2, goodsname='冰箱', price=4000.0, type=2}
Product02{id=3, goodsname='空调', price=3000.0, type=2}
Product02{id=4, goodsname='洗衣机', price=2000.0, type=2}

输出结果应该如上!

本章小结

本章内容主要设计数据表之间,以及对象之间的关联关系,由此引出了MyBatis框架中对关联关系的处理;通过一系列案例讲述了MyBatis在处理实体对象之间的3种关联关系。最后又说明了MyBatis的缓存机制,这一点十分重要,在面试中常常是面试官的心头好!需要清楚并能表述出MyBatis的一级缓存和二级缓存。希望您能通过对本章的学习,了解数据表之间及对象之间的3种关联关系,熟练使用MyBatis的缓存机制,并且能够在MyBatis框架中熟练运用3种关联关系进行查询,熟练配置MyBatis缓存,从而提高项目的开发效率。


http://www.kler.cn/news/339543.html

相关文章:

  • 《RabbitMQ篇》消息应答和发布确认
  • 使用PuTTY连接到Amazon Linux实例
  • Maven 父子模块的 pom.xml 文件编写
  • 代码随想录day23:贪心part1
  • 初学者如何快速入门人工智能
  • 基于深度学习的材料科学中的自动化实验设计
  • GO网络编程(七):海量用户通信系统5:分层架构
  • docker compose入门4—常用命令
  • SQL Server—了解数据库和数据库的创建
  • 河道垃圾数据集 水污染数据集——无人机视角数据集 共3000张图片,可直接用于河道垃圾、水污染功能检测 已标注yolo格式、voc格式,可直接训练;
  • Linux 系统网络配置
  • Linux中各种查看
  • 图像增强论文精读笔记-Low-Light Image Enhancement via a Deep Hybrid Network
  • Stm32的bootloader无法使用问题
  • Flume面试整理-Flume的核心组件
  • 力扣随机题
  • RNN(循环神经网络)简介及应用
  • 【Windows】在任务管理器中隐藏进程
  • IvorySQL 西安站活动回顾|一键了解IvorySQL新兼容性
  • Collection 和 Collections 有什么区别?