Mybatis工作中常见关系映射总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Weixiaohuai/article/details/89035663

一、简介

在工作中,如果持久层框架使用mybatis的话,基本上都会涉及到关联关系映射处理,本文将对Mybatis中的一对多、一对一、递归查询树结构数据等做一个比较系统的总结,加深自己对Mybatis高级映射的理解。下面我们通过几个示例详细说明用法。

二、准备工作

在进行创建项目之前,我们首先要准备几张表,tbl_class(班级表)、tbl_teacher(教师表)、tbl_student(学生表)、tbl_item(测评项表)。具体的sql如下:

/*
SQLyog Ultimate v11.24 (32 bit)
MySQL - 5.5.44 : Database - springboot-mybatis
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`springboot-mybatis` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `springboot-mybatis`;

/*Table structure for table `tbl_class` */

DROP TABLE IF EXISTS `tbl_class`;

CREATE TABLE `tbl_class` (
  `c_id` int(11) NOT NULL AUTO_INCREMENT,
  `c_name` varchar(20) DEFAULT NULL,
  `teacher_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`c_id`),
  KEY `fk_teacher_id` (`teacher_id`),
  CONSTRAINT `fk_teacher_id` FOREIGN KEY (`teacher_id`) REFERENCES `tbl_teacher` (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `tbl_class` */

insert  into `tbl_class`(`c_id`,`c_name`,`teacher_id`) values (1,'科技一班',1),(2,'科技二班',2),(3,'科技三班',3);

/*Table structure for table `tbl_item` */

DROP TABLE IF EXISTS `tbl_item`;

CREATE TABLE `tbl_item` (
  `item_id` int(11) NOT NULL AUTO_INCREMENT,
  `item_name` varchar(20) DEFAULT NULL,
  `parent_item_id` int(11) DEFAULT NULL COMMENT '父ID',
  `zt` int(11) DEFAULT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;

/*Data for the table `tbl_item` */

insert  into `tbl_item`(`item_id`,`item_name`,`parent_item_id`,`zt`) values (1,'测评项1',NULL,1),(2,'测评项1-1',1,1),(3,'测评项1-2',1,1),(4,'测评项1-3',1,1),(5,'测评项1-2-1',3,1),(6,'测评项1-2-2',3,1),(7,'测评项1-3-1',4,1),(8,'测评项1-3-2',4,1),(9,'测评项2',NULL,1),(10,'测评项2-1',9,1),(11,'测评项2-2',9,1),(12,'测评项2-1-1',10,1),(13,'测评项2-1-2',10,NULL);

/*Table structure for table `tbl_student` */

DROP TABLE IF EXISTS `tbl_student`;

CREATE TABLE `tbl_student` (
  `s_id` int(11) NOT NULL AUTO_INCREMENT,
  `s_name` varchar(20) DEFAULT NULL,
  `class_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`s_id`),
  KEY `fk_class_id` (`class_id`),
  CONSTRAINT `fk_class_id` FOREIGN KEY (`class_id`) REFERENCES `tbl_class` (`c_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

/*Data for the table `tbl_student` */

insert  into `tbl_student`(`s_id`,`s_name`,`class_id`) values (1,'学生1',1),(2,'学生2',1),(3,'学生3',2),(4,'学生4',2),(5,'学生5',2),(6,'学生6',3);

/*Table structure for table `tbl_teacher` */

DROP TABLE IF EXISTS `tbl_teacher`;

CREATE TABLE `tbl_teacher` (
  `t_id` int(11) NOT NULL AUTO_INCREMENT,
  `t_name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

/*Data for the table `tbl_teacher` */

insert  into `tbl_teacher`(`t_id`,`t_name`) values (1,'张三'),(2,'李四'),(3,'王五');

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

三、一对一

在mybatis中要实现一对一关联查询,主要是通过<association/>标签来实现。

【案例】查询所有的班级以及关联的教师信息(假设一个教师只带一个班级)

【a】首先,创建教师实体类Teacher.java:

package com.wsh.springboot.springbootmybatis.entity;

/**
 * @Description: 教师实体类
 * @Author: weishihuai
 * @Date: 2019/3/31 08:56
 */
public class Teacher {
    /**
     * 教师编号
     */
    private Integer tid;
    /**
     * 教师姓名
     */
    private String tname;

    public Teacher() {
    }

    public Teacher(String tname) {
        this.tname = tname;
    }

    public Integer getTid() {
        return tid;
    }

    public void setTid(Integer tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    @Override
    public String toString() {
        return "Teacher{" +
                "tid=" + tid +
                ", tname='" + tname + '\'' +
                '}';
    }

    public void setTname(String tname) {
        this.tname = tname;

    }

}

【b】创建班级实体类ClassRoom.java:

/**
 * @Description: 班级实体类
 * @Author: weishihuai
 * @Date: 2019/3/31 09:06
 */
public class ClassRoom {

    /**
     * 班级ID
     */
    private Integer cid;
    /**
     * 班级名称
     */
    private String cname;
    /**
     * 班级的教师信息(假设一个老师只带一个班级)
     */
    private Teacher teacher;

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    @Override
    public String toString() {
        return "ClassRoom{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}

注意,在ClassRoom中加入了教师实体属性teacher.,下面我们讲解三种一对一关系映射的方法:

【c】定义mapper查询接口:

/**
 * @Description: Mapper接口
 * @Author: weishihuai
 * @Date: 2019/3/31 09:11
 */
@Repository
public interface ClassRoomMapper {

    /**
     * 查询所有的班级以及关联的教师信息(嵌套结果方式)
     *
     * @return 班级以及关联的教师信息
     */
    List<ClassRoom> getAllClassRoomAndTeacherInfo01();

    /**
     * 查询所有的班级以及关联的教师信息(嵌套查询方式)
     *
     * @return 班级以及关联的教师信息
     */
    List<ClassRoom> getAllClassRoomAndTeacherInfo02();

    /**
     * 查询所有的班级以及关联的教师信息(拓展VO方式)
     *
     * @return 班级以及关联的教师信息
     */
    List<ClassRoomTzVO> getAllClassRoomAndTeacherInfo03();

}

【d】定义mapper.xml文件:

第一种方法:嵌套结果方式

<!--             一对一关联查询: 查询所有的班级以及关联的教师信息     -->

    <!--第一种方式:嵌套结果-->
    <resultMap id="oneToOne01" type="com.wsh.springboot.springbootmybatis.entity.ClassRoom">
        <!--
            column: 对应的是查询的别名,而不是表字段名(当然这里没取别名就是表字段名)
            property: 实体类中的属性名称
        -->
        <id column="c_id" property="cid"/>
        <result column="c_name" property="cname"/>
        <!--
            javaType: 指定返回结果集中的对象属性类型(全限定名)
        -->
        <association property="teacher" javaType="com.wsh.springboot.springbootmybatis.entity.Teacher">
            <id column="t_id" property="tid"/>
            <result column="t_name" property="tname"/>
        </association>
    </resultMap>

    <select id="getAllClassRoomAndTeacherInfo01" resultMap="oneToOne01">
        SELECT *
        FROM tbl_class t1
                 LEFT JOIN tbl_teacher t2
                           ON t1.teacher_id = t2.t_id
    </select>

第二种方法:分步查询方式

 <!--第二种方式:嵌套查询/分步查询-->
    <resultMap id="oneToOne02" type="com.wsh.springboot.springbootmybatis.entity.ClassRoom">
        <id column="c_id" property="cid"/>
        <result column="c_name" property="cname"/>
        <!--
            property: ClassRoom类中教师类的属性名称
            column: 所对应的外键字段名称
            select: 根据第一步查询出的教师ID查询教师信息
         -->
        <association property="teacher" select="getTeacherById" column="teacher_id"/>
    </resultMap>

    <select id="getAllClassRoomAndTeacherInfo02" resultMap="oneToOne02">
        SELECT *
        FROM tbl_class t1
    </select>

    <select id="getTeacherById" resultType="com.wsh.springboot.springbootmybatis.entity.Teacher">
        SELECT t3.t_id   AS tid,
               t3.t_name AS tname
        FROM tbl_teacher t3
        WHERE t3.t_id = #{tid}
    </select>

第三种方式:拓展VO方式

这种方式需要定义个包含教师类的属性以及班级类的属性:

/**
 * @Description: 班级信息拓展VO
 * @Author: weishihuai
 * @Date: 2019/3/31 10:52
 */
public class ClassRoomTzVO {
    /**
     * 班级ID
     */
    private Integer cid;
    /**
     * 班级名称
     */
    private String cname;
    /**
     * 教师ID
     */
    private Integer tid;
    /**
     * 教师信息
     */
    private String tname;

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public Integer getTid() {
        return tid;
    }

    public void setTid(Integer tid) {
        this.tid = tid;
    }

    public String getTname() {
        return tname;
    }

    public void setTname(String tname) {
        this.tname = tname;
    }

    @Override
    public String toString() {
        return "ClassRoomTzVO{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", tid=" + tid +
                ", tname='" + tname + '\'' +
                '}';
    }
}
 <!--第三种方式:使用拓展VO实现-->
    <select id="getAllClassRoomAndTeacherInfo03" resultType="com.wsh.springboot.springbootmybatis.vo.ClassRoomTzVO">
        SELECT t1.c_id   AS cid,
               t1.c_name AS cname,
               t2.t_id   AS tid,
               t2.t_name AS tname
        FROM tbl_class t1
                 LEFT JOIN tbl_teacher t2
                           ON t1.teacher_id = t2.t_id
    </select>

【e】测试:

 //测试 一对一关系映射  第一种方式
    @Test
    public void testGetAllClassRoomAndTeacherInfo01() {
        List<ClassRoom> allClassRoomAndTeacherInfo = classRoomMapper.getAllClassRoomAndTeacherInfo01();
        for (ClassRoom classRoom : allClassRoomAndTeacherInfo) {
            System.out.println(classRoom);
        }
    }

    //测试 一对一关系映射  第二种方式
    @Test
    public void testGetAllClassRoomAndTeacherInfo02() {
        List<ClassRoom> allClassRoomAndTeacherInfo = classRoomMapper.getAllClassRoomAndTeacherInfo02();
        for (ClassRoom classRoom : allClassRoomAndTeacherInfo) {
            System.out.println(classRoom);
        }
    }

    //测试 一对一关系映射  第三种方式
    @Test
    public void testGetAllClassRoomAndTeacherInfo03() {
        List<ClassRoomTzVO> allClassRoomAndTeacherInfo = classRoomMapper.getAllClassRoomAndTeacherInfo03();
        for (ClassRoomTzVO classRoomTzVO : allClassRoomAndTeacherInfo) {
            System.out.println(classRoomTzVO);
        }
    }

【f】查询结果:

运行junit测试,发现三种方式的查询结果都是一致的,具体项目中可以根据自己的喜好选择合适的方法。

四、一对多

mybatis中实现一对多关系映射,主要是通过<Colleaction/>标签来实现。

【案例】: 查询所有的班级以及班级的所有学生信息、教师信息

【a】定义Student.java:

/**
 * @Description: 学生实体类
 * @Author: weishihuai
 * @Date: 2019/3/31 20:51
 */
public class Student {
    /**
     * 学生ID
     */
    private Integer sid;
    /**
     * 学生姓名
     */
    private String sname;

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }


    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                '}';
    }
}

【b】在ClassRoom类中加入 private List<Student> students;

package com.wsh.springboot.springbootmybatis.entity;

import java.util.List;

/**
 * @Description: 班级实体类
 * @Author: weishihuai
 * @Date: 2019/3/31 09:06
 */
public class ClassRoom {

    /**
     * 班级ID
     */
    private Integer cid;
    /**
     * 班级名称
     */
    private String cname;
    /**
     * 班级的教师信息(假设一个老师只带一个班级)
     */
    private Teacher teacher;
    /**
     * 学生集合
     */
    private List<Student> students;

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }

    public List<Student> getStudents() {
        return students;
    }

    public void setStudents(List<Student> students) {
        this.students = students;
    }

    @Override
    public String toString() {
        return "ClassRoom{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", teacher=" + teacher +
                ", students=" + students +
                '}';
    }
}

【c】编写mapper.java接口

 /**
     * 查询所有班级的所有学生(嵌套结果方式)
     *
     * @return 班级所有学生信息
     */
    List<ClassRoom> getAllStudents01();

    /**
     * 查询所有班级的所有学生(分步查询方式)
     *
     * @return 班级所有学生信息
     */
    List<ClassRoom> getAllStudents02();

【d】定义mapper.xml映射文件:下面也是讲解两种实现方法。

第一种方法:嵌套结果方式

<!--             一对多关联查询: 查询所有的班级以及班级的所有学生信息、教师信息     -->

    <!--第一种方式: 嵌套结果方式 -->
    <resultMap id="oneToMany01" type="com.wsh.springboot.springbootmybatis.entity.ClassRoom">
        <id column="c_id" property="cid"/>
        <result column="c_name" property="cname"/>
        <!--教师信息-->
        <association property="teacher" javaType="com.wsh.springboot.springbootmybatis.entity.Teacher">
            <id column="t_id" property="tid"/>
            <result column="t_name" property="tname"/>
        </association>
        <!--学生集合信息-->
        <!--
            property: 指定ClassRoom类声明的学生集合的属性名称
            ofType: 指定返回集合中的对象类型(全限定名)
        -->
        <collection property="students" ofType="com.wsh.springboot.springbootmybatis.entity.Student">
            <id column="s_id" property="sid"/>
            <result column="s_name" property="sname"/>
        </collection>
    </resultMap>

    <select id="getAllStudents01" resultMap="oneToMany01">
        SELECT *
        FROM tbl_class t1
                 LEFT JOIN tbl_teacher t2
                           ON t1.teacher_id = t2.t_id
                 LEFT JOIN tbl_student t3
                           ON t1.c_id = t3.class_id
    </select>

第二种方法:分步查询方式

<!--第二种方式: 嵌套查询(分步查询) -->
    <select id="getTeacherById2" resultType="com.wsh.springboot.springbootmybatis.entity.Teacher">
        SELECT t3.t_id   AS tid,
               t3.t_name AS tname
        FROM tbl_teacher t3
        WHERE t3.t_id = #{tid}
    </select>

    <select id="getStudentsByCId" resultType="com.wsh.springboot.springbootmybatis.entity.Student">
        SELECT t.s_id as sid, t.s_name as sname
        from tbl_student t
        where t.class_id = #{cid}
    </select>

    <resultMap id="oneToMany02" type="com.wsh.springboot.springbootmybatis.entity.ClassRoom">
        <id column="c_id" property="cid"/>
        <result column="c_name" property="cname"/>
        <!--
            select: 指定上一步骤查询出来的结果传到下一步该执行的SQL
        -->
        <association property="teacher" column="teacher_id"
                     javaType="com.wsh.springboot.springbootmybatis.entity.Teacher" select="getTeacherById2"/>
        <collection property="students" column="c_id" ofType="com.wsh.springboot.springbootmybatis.entity.Student"
                    select="getStudentsByCId"/>
    </resultMap>

    <select id="getAllStudents02" resultMap="oneToMany02">
        SELECT t.*
        FROM tbl_class t
    </select>

【e】测试用例

 //测试 一对多关系映射  第一种方式
    @Test
    public void testGetAllStudents01() {
        List<ClassRoom> allStudents = classRoomMapper.getAllStudents01();
        for (ClassRoom classRoom : allStudents) {
            System.out.println(classRoom);
        }
    }

    //测试 一对多关系映射  第二种方式
    @Test
    public void testGetAllStudents02() {
        List<ClassRoom> allStudents = classRoomMapper.getAllStudents02();
        for (ClassRoom classRoom : allStudents) {
            System.out.println(classRoom);
        }
    }

【f】查询结果:

可见,以上两种方式都能实现一对多关系映射。

五、递归一对多

【案例】递归查找测评项信息以及子测评项信息(树结构数据)

【a】创建测评项实体类Item.java:

package com.wsh.springboot.springbootmybatis.entity;

import java.util.List;

/**
 * @Description: 测评项实体类
 * @author: weishihuai
 * @Date: 2019/4/3 16:15
 */
public class Item {

    private Integer itemId;
    private String itemName;
    private Integer zt;
    private List<Item> children;

    public List<Item> getChildren() {
        return children;
    }

    public void setChildren(List<Item> children) {
        this.children = children;
    }

    public Integer getItemId() {
        return itemId;
    }

    public void setItemId(Integer itemId) {
        this.itemId = itemId;
    }

    public String getItemName() {
        return itemName;
    }

    public void setItemName(String itemName) {
        this.itemName = itemName;
    }

    public Integer getZt() {
        return zt;
    }

    public void setZt(Integer zt) {
        this.zt = zt;
    }

    @Override
    public String toString() {
        return "Item{" +
                "itemId=" + itemId +
                ", itemName='" + itemName + '\'' +
                ", zt=" + zt +
                ", children=" + children +
                '}';
    }
}

【b】编写mapper.java接口

 /**
     * 递归查找测评项以及测评项下面的子测评项
     *
     * @param id 测评项ID
     * @return 测评项以及测评项下面的子测评项
     */
    List<Item> getAllItemsAndChildItems(@Param("id") Integer id);

【c】编写mapper.xml映射文件

 <!--             一对多关联查询扩展: 递归查找测评项信息以及子测评项信息(树结构数据)     -->

    <!--递归查找所有的测评项以及子测评项-->
    <select id="getAllItemsAndChildItems" resultMap="oneToMany03">
        SELECT * FROM tbl_item t where 1 = 1
        <choose>
            <when test="id == null ">
                and t.parent_item_id is null
            </when>
            <otherwise>
                and t.item_id = #{id}
            </otherwise>
        </choose>
    </select>

    <resultMap id="oneToMany03" type="com.wsh.springboot.springbootmybatis.entity.Item">
        <id column="item_id" property="itemId"/>
        <result column="item_name" property="itemName"/>
        <result column="zt" property="zt"/>
        <!--
            ofType: 指定返回结果集的类型
            column:
            多列的值传递的方式如下: column="{key1=column1,key2=column2}",key1是方法的参数名,column1是字段名, key2是方法的参数名,column2是字段名
        -->
        <collection property="children" ofType="com.wsh.springboot.springbootmybatis.entity.Item"
                    javaType="java.util.List" column="{id=item_id}"
                    select="getItemById"/>
    </resultMap>

    <!--根据父级测评项查找子测评项-->
    <!--
        注意: 如果是递归查询,这里别忘了指定resultMap哦
    -->
    <select id="getItemById" resultMap="oneToMany03">
        SELECT *
        FROM tbl_item t
        WHERE t.parent_item_id = #{id}
    </select>
    

【d】测试用例

 //测试 递归查找测评项信息以及子测评项信息(树结构数据)
    @Test
    public void testGetAllItemsAndChildItems() {
        List<Item> items = classRoomMapper.getAllItemsAndChildItems(null);
        System.out.println(JSON.toJSONString(items));
        for (Item item : items) {
            System.out.println(item.getItemName());
            System.out.println(item.getChildren());
        }
    }

【e】查询结果

[
    {
        "children":[
            {
                "children":[

                ],
                "itemId":2,
                "itemName":"测评项1-1",
                "zt":1
            },
            {
                "children":[
                    {
                        "children":[

                        ],
                        "itemId":5,
                        "itemName":"测评项1-2-1",
                        "zt":1
                    },
                    {
                        "children":[

                        ],
                        "itemId":6,
                        "itemName":"测评项1-2-2",
                        "zt":1
                    }
                ],
                "itemId":3,
                "itemName":"测评项1-2",
                "zt":1
            },
            {
                "children":[
                    {
                        "children":[

                        ],
                        "itemId":7,
                        "itemName":"测评项1-3-1",
                        "zt":1
                    },
                    {
                        "children":[

                        ],
                        "itemId":8,
                        "itemName":"测评项1-3-2",
                        "zt":1
                    }
                ],
                "itemId":4,
                "itemName":"测评项1-3",
                "zt":1
            }
        ],
        "itemId":1,
        "itemName":"测评项1",
        "zt":1
    },
    {
        "children":[
            {
                "children":[
                    {
                        "children":[

                        ],
                        "itemId":12,
                        "itemName":"测评项2-1-1",
                        "zt":1
                    },
                    {
                        "children":[

                        ],
                        "itemId":13,
                        "itemName":"测评项2-1-2"
                    }
                ],
                "itemId":10,
                "itemName":"测评项2-1",
                "zt":1
            },
            {
                "children":[

                ],
                "itemId":11,
                "itemName":"测评项2-2",
                "zt":1
            }
        ],
        "itemId":9,
        "itemName":"测评项2",
        "zt":1
    }
]

这种数据结构常见于树形结构展示数据的时候用到, 可以使用json查看器观察其数据结构,大家在项目中有这样的需求可以使用这种方式实现,比较方便快捷。

文中对一些需要注意的地方已经写了一些注释,如果大家需要源码的话,可以到这里 https://gitee.com/weixiaohuai/springboot-mybatis.git去下载参考一下。

六、总结

以上就是关于mybatis常见一对一、一对多关系映射的多种实现方式,大家可以根据自己的需求使用最合适的方式。本文仅仅是笔者的一些总结和使用经验总结,如有不对之处,希望大家指点一二,希望能对大家有所帮助。

猜你喜欢

转载自blog.csdn.net/Weixiaohuai/article/details/89035663