Mybatis基础教程及使用细节

    本篇主要对Mybatis基础使用进行总结,包括Mybatis的基础操作,使用注解进行增删改查的练习;详细介绍xml映射文件配置过程并且使用xml映射文件进行动态sql语句进行条件查询;为了简化java开发提高效率,介绍一下依赖,例如lombok依赖等。后续会对Mybatisplus进行总结。

目录

一、什么是Mybatis:

二、小细节: 

 三、Mybatis基础操作:

使用注解实现增删改查操作:

删除操作:

 插入操作:

 更新操作:

查询操作:

四、xml映射文件的使用

如何配置xml映射文件:

第一条规范:

第二条规范:

五、动态sql语句

动态查询操作: 

动态更新操作:

批量删除操作:


一、什么是Mybatis:

  • Mybatis是持久层框架,也就是前面篇章讲的DAO层框架,用于简化JDBC的开发,前面介绍过jdbc,使用起来比较复杂:需要导入依赖然后创建连接,编写sql语句,创建载具(传输sql)然后对结果进行解析等操作,引入Mybatis极大简化了对数据的操作。
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 映射成数据库中的记录。

二、小细节: 

数据库连接池更换:

    springboot中自带的数据库连接池是HikariDataSource,如果想更换的话,例如Druid,只需要将这个依赖的坐标配置到pom.xml文件中即可;

lombok依赖:

    当我们获取数据库数据的时候,需要用一个实体类与数据库的数据去对应,然后将数据封装在实体类对象中,但是自己去实现这个类的时候发现代码量也比较多,get、set方法以及无参数的构造器和有参数的构造器等等需要自己去生成,整体上比较臃肿,所以可以音符一个依赖,然后使用相应注解去简化这个操作:

    lombok是一个实用的java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并且可以自动化生成日志变量,简化开发,提高效率。注解如下:

在pom.xml文件中引入如下依赖:

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
</dependency>

更改后:下面各种方法就不用自己手动实现了。

package com.springboot_mybatis.pojo;

import lombok.*;

//pojo这个包专门用来放实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Short age;
    private Short gender;
    private String phone;
//    public User() {
//    }
//
//    public User(Integer id, String name, Short age, Short gender, String phone) {
//        this.id = id;
//        this.name = name;
//        this.age = age;
//        this.gender = gender;
//        this.phone = phone;
//    }

//    public Integer getId() {
//        return id;
//    }
//
//    public String getName() {
//        return name;
//    }
//
//    public Short getAge() {
//        return age;
//    }
//
//    public Short getGender() {
//        return gender;
//    }
//
//    public String getPhone() {
//        return phone;
//    }
//
//    @Override
//    public String toString() {
//        return "User{" +
//                "id=" + id +
//                ", name='" + name + '\'' +
//                ", age=" + age +
//                ", gender=" + gender +
//                ", phone='" + phone + '\'' +
//                '}';
//    }
//
//    public void setId(Integer id) {
//        this.id = id;
//    }
//
//    public void setName(String name) {
//        this.name = name;
//    }
//
//    public void setAge(Short age) {
//        this.age = age;
//    }
//
//    public void setGender(Short gender) {
//        this.gender = gender;
//    }
//
//    public void setPhone(String phone) {
//        this.phone = phone;
//    }
}

 三、Mybatis基础操作:

使用注解实现增删改查操作:

准备工作:

  • 准备数据库表:emp。
  • 创建一个新的springboot工程,然后引入起步依赖:mybatis、mysql驱动、lombok依赖等。
  • application.properties中配置数据库连接信息,包括user,password,数据库名称等。
  • 创建实体类Emp用来封装数据库数据。
  • 准备Mapper接口EmpMapper对数据库进行操作,要知道Mapper中是写sql语句的,需要给里面传递参数,来进行数据库操作;
  • 创建好数据库表后,连接数据库:点击右侧Database然后点击+号:
  • 选择Mysql:
  • 填上对应的信息,user、password、数据库表名称等即可完成。

工程整体结构如下:

  

删除操作

EmpMapper代码:方法前要添加Delete注解,表示删除操作,然后里面写具体的sql代码。

@Mapper
public interface EmpMapper {
    //TODO 根据ID删除数据操作:此处删除的话需要指定id,所以此处需要传入一个id;
    @Delete("delete from emp where id=#{id}")
//    public void delete(Integer id);
    public int delete(Integer id);

在test文件中进行测试: 此处进行依赖注入EmpMapper类的对象,然后调用delete方法并传入参数,即可对该数据库进行操作。

//在定义好了接口以及数据库的数据以及对应的User类后,需要进行测试,测试需要在test文件中进行;
@SpringBootTest//这个就是springboot整合单元测试的注解
class SpringbootMybatisCrudApplicationTests {
    //注意在UserMapper接口中注解了Mapper,所以在容器中已经有了实现这个接口的对象,所以只需要依赖注入即可使用;
    @Autowired
    private EmpMapper empMapper;
    @Test
    public void testDelete(){
        int delete = empMapper.delete(17);//此处别忘了传递参数;
        System.out.println("删除的行数:"+delete);
    }

 在进行例如删除操作后,并不知道执行的过程,可以在配置文件中进行配置:在application.properties文件中配置日志输出,配置好后,可以在控制台中输出日志信息:

#配置mybatis的日志,指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

配置好后完成删除操作后会控制台会显示如下信息:

 插入操作:

主键回显:

为什么要获取主键:在一个表中添加数据之后,一般还需要维护其关系表数据,所以要获取到主键,然后再对其关系表数据进行操作。

在数据插入成功后,需要获取插入数据库数据的主键,例如,id为主键,刚插入的id为12,想获取12这个值,一般主键是自增的,所以不用手动写(下方代码也没有传入id这个值),所以想获取主键可以用这个注解的方法来实现;

获取方法:

在insert注解前面再添加一个注解:@Options(keyProperty=”id”,userGeneratedKeys=true),这样就会自动将生成的主键值赋值给emp对象的id属性,否则不会给id属性赋值;

EmpMapper中代码:会直接从对象中的属性取值:

//TODO 新增员工:注意如果在values里面添加值的话,这样就写死了,复用性差,所以想直接传递一个对象;注意这里的#{}里面的参数是属性,要和属性名一样
    @Options(useGeneratedKeys = true,keyProperty = "id")//主键回显,自动将主键值赋值给该对象的id属性;这样就可以拿到主键值了;
    @Insert("insert into emp(username,name,gender,image,job,entrydate,dept_id,create_time,update_time) values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
    public void insert(Emp emp);

test文件中测试:将对象属性赋值,然后调用insert方法

@Test
    public void testInsert(){
        Emp emp=new Emp();
        emp.setUsername("Tom1");
        emp.setName("汤姆1");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);

        empMapper.insert(emp);
        //测试是否能拿到主键值:
        System.out.println(emp.getId());
    }
 更新操作:

EmpMapper中代码:

@Update("update emp set username=#{username},name=#{name},gender=#{gender},image=#{image},job=#{job},entrydate=#{entrydate},dept_id=#{deptId},update_time=#{updateTime} where id=#{id}")
    public void update(Emp emp);

test测试:注意此处有点缺陷,是新创建了一个Emp对象,并且调用update方法并传入这个对象作为参数,所以更新的话需要全部属性都要更新,如果少对emp对象属性赋值的话,那么数据库中的相应属性也会为null。后续会采用动态sql。

@Test//测试更新方法
    public void testUpdate(){
        Emp emp=new Emp();
        emp.setId(22);
        emp.setUsername("Jerry");
        emp.setName("杰瑞");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        empMapper.update(emp);
    }
查询操作:

查询操作有一点坑:

如果采用第一种方法,在数据库中属性名为dept_id,而实体类对象中的属性名为deptId,二者名称不同,会导致mybatis从数据库查询到的数据不会自动封装到对应属性当中;采用第三种方法,在properties文件中进行配置,如下代码即可自动将dept_id封装到驼峰命名deptId:

#开启mybatis的驼峰命名自动映射开关---可以将数据库中属性a_dan自动封装到实体类的属性aDan;
mybatis.configuration.map-underscore-to-camel-case=true

EmpMapper中代码:

//TODO 查询指定数据:(查询所有数据在前面项目中已经展示,返回类型为对象集合)
    //下面这个方法是不合理的,因为如果数据库中的属性名和实体类的属性名不相同的话mybatis不会自动封装也就是这几个值不会自动给类的属性赋值,所以这几个属性的值为null;
//    @Select("select *from emp where id=#{id}")
//    public Emp select(Integer id);

    //通过@Result注解,手动进行映射封装:(不推荐这种方法,比较复杂)
//    @Results({
//            //一个@Result代表将一列映射到一个属性;有三个属性和数据库列不一致,所以要写三个:
//            @Result(column = "dept_id",property = "deptId"),
//            @Result(column = "create_time",property = "createTime"),
//            @Result(column = "update_time",property = "updateTime")
//    })
//    @Select("select*from emp where id=#{id}")
//    public Emp select(Integer id);
//!!!推荐的方法:开启mybatis的驼峰命名自动映射开关---可以将数据库中属性a_dan自动封装到实体类的属性aDan;
// 需要在resources资源文件中的配置文件中进行配置:配置好后,原来的代码怎么写现在就怎么写:
    @Select("select *from emp where id=#{id}")
    public Emp select(Integer id);

test测试:

public void testSelect(){
        Emp emp = empMapper.select(1);
        System.out.println(emp);
    }

四、xml映射文件的使用

通过完成上述增删改查操作后,发现有一个很大的缺陷就是更新的时候必须全部属性更新,无法对个别属性进行更新,查询操作也同样如此,无法随意指定属性作为条件进行查询,因为EmpMapper中查询的条件写死了,必须按照那几个属性进行查询,少传或者多传参数会报错,因此为了解决这个缺陷采用xml映射文件:

如何配置xml映射文件:

有三条规范:

第一条规范

首先根据mapper包创建配置文件:(必须要用/来分隔,这样就可以创建多级目录)注意名称需要和项目包名com.springboot_test1以及mapper包名mapper一致.

如下图:此处创建file名为com/springboot_test1/mapper,然后再创建一个名为EmpMapper.xml的文件即可。

 然后复制如下代码到刚创建的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">
第二条规范:

xml映射文件中的namespace属性与Mapper接口的全限定名一致:获取接口的全限定名:copy reference

 

 第三条规范:

    sql语句中的id与Mapper接口中的方法名一致,并且ResultType为单条记录所封装的类型;select中的id就是接口中的方法名,对于resultType将这个实体类copy reference即可;

   整体上就是当调用EmpMapper接口中的方法的时候,先去查找xml映射文件中namespace属性与这个接口全限定名相同的,然后找到id属性与方法名相同的sql语句,然后执行这条sql语句返回结果;

 这样就配置完成xml映射文件。

五、动态sql语句

像如下这种的sql语句,直接就写死了,必须要传进去这些参数。但是如果想传递部分参数然后进行条件查询的话,此时就是动态sql语句(在xml映射文件中编写sql语句);

 可以用以下方式来实现:<if>  <where>(查询)   <set>(更新)标签的使用:<where>是用于去除冗余的逗号等,可以进行自动判断;

<if>标签用于判断条件是否成立,使用test属性进行条件判断,如果条件为true那么则拼接sql。

动态查询操作: 

EmpMapper中代码:注意有了xml映射文件,EmpMapper中方法前面就无需加响应注解了,只需要在xml映射文件中对应上方法名称以及resultType即可。想根据哪些属性查询就传入哪些属性。

//TODO 通过xml映射文件配置条件查询:就不用带注解了!!!;
    public List<Emp> list1(String name, Short gender, LocalDate begin,LocalDate end);

xml映射文件中代码: 

<select id="list1" resultType="com.springboot_test1.pojo.Emp">
         select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
         from emp
        <where>
            <if test="name!=null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender!=null">
                and gender=#{gender}
            </if>
            <if test="begin!=null and end!=null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc

    </select>

 test测试:用<where>标签的原因如下:

@Test
    public void testSelect_tiaojian1() {
//        List<Emp> list=empMapper.list1("张",null,null,null);
        //但是如果将name设置为null之后,就会报错,因为用if语句,有了就拼接,没有就舍弃,
        // 这样name舍弃之后where后面多了一个and:select *from emp where and gender=? order by update_time desc
        //为了解决这个问题,引入mybatis中的标签:<where>;将xml映射文件中的select语句里面的where替换为<where>,会自动删除and;
//        List<Emp> list=empMapper.list1(null,(short)1,null,null);
//此时相当于查询全部数据:
        List<Emp> list=empMapper.list1(null,null,null,null);
        System.out.println(list);

    }

动态更新操作:

EmpMapper中:

//TODO 动态更新员工信息,在xml映射文件中配置
    public void update2(Emp emp);//可以Alt加回车然后点击generate自动在xml文件中配置;

 xml映射文件中:

<update id="update2">
# update emp where id=1,name="tom"......
        update emp
        <set>
            <if test="username!=null">
                username=#{username},
            </if>
            <if test="name!=null">
            name=#{name},
            </if>
        <if test="gender!=null">
            gender=#{gender},
        </if>
        <if test="image!=null">
            image=#{image},
        </if>
        <if test="job!=null">
            job=#{job},
        </if>
        <if test="entrydate!=null">
            entrydate=#{entrydate},
        </if>
        <if test="deptId!=null">
            dept_id=#{deptId},
        </if>
        <if test="updateTime!=null">
            update_time=#{updateTime}
        </if>
        </set>
        where id = #{id}
    </update>

test测试:

@Test//测试更新方法,要求是id为22号数据username更新为Jerrygood,name更新为杰瑞棒,gender更新为2;
    // 采用xml映射文件以及动态更新sql语句,可以发现后面不需要更改的属性没有变成null;
    public void testUpdate2(){
        Emp emp=new Emp();
        emp.setId(22);
        emp.setUsername("Jerrygood");
        emp.setName("杰瑞棒");
        emp.setGender((short)2);
        emp.setUpdateTime(LocalDateTime.now());//这个是要更新的,每次改动数据要改成现在的时间;
        empMapper.update2(emp);

    }

批量删除操作:

此处使用<foreach>标签:

EmpMapper中:

//TODO 批量删除操作:是根据id号来删除,因为是多个id,所以一般用数组或者集合来接收;
    public void deleteByIds(List<Integer> ids);

xml映射文件中:

<!--批量删除员工操作:(18,19,20)
collection:所要遍历的集合:ids要和接口中定义的方法的参数相同
item:遍历出来的元素
separator:分隔符:用,分隔
open:遍历开始前拼接的sql片段:(
close:遍历开始后拼接的sql片段:)
-->
    <delete id="deleteByIds">
delete from emp where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
    </delete>
</mapper>

test测试:

@Test //测试批量删除方法:
    public void testDeleteByIds(){
        List<Integer>ids=Arrays.asList(13,14,15);
        empMapper.deleteByIds(ids);

    }

以上就是mybatis基础用法以及细节。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/368600.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安科瑞电气火灾监控系统在海尔(合肥)创新产业园一期厂房改扩建项目的设计与应用

摘要&#xff1a;介绍海尔&#xff08;合肥&#xff09;创新产业园一期厂房改扩建项目采用安科瑞剩余电流式电气火灾探测器&#xff0c;就地组网方式&#xff0c;通过现场总线通讯远传至后台&#xff0c;从而实现剩余电流式电气火灾监控系统的搭建&#xff0c;完成对现场配电回…

万户 ezOFFICE wpsservlet SQL注入漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

微信小程序应用商店源码系统 带完整的安装代码包以及搭建教程

随着微信小程序的普及&#xff0c;越来越多的企业和开发者开始关注小程序的开发与运营。为了满足市场需求&#xff0c;小编给大家分享一款微信小程序应用商店源码系统。该系统集成了完整的安装代码包&#xff0c;方便用户快速搭建自己的小程序应用商店。 以下是部分代码示例&a…

typedef

typedef typedef &#xff0c;type表示类型&#xff0c; def就是define&#xff0c; 定义的意思。所以&#xff0c;根据名字我们就可以知道typedef就是类型定义的意思。可以对一个类型进行重新定义。 一般对一个类型重新定义都是这种形式: typedef 类型 重定义 如&#xff…

web学习笔记(十九)

目录 1.作用域 1.1作用域的概念 1.2作用域的分类 1.2.1全局作用域 1.2.2局部作用域 1.2.3块级作用域&#xff08;ES6新增 &#xff09; 2.变量作用域 2.1全局变量 2.2局部变量 3.作用域链 3.1作用域链的定义 4.垃圾回收机制 4.1定义 4.2如何避免内存泄漏 5.预…

1895_分离进程的能力

1895_分离进程的能力 全部学习汇总&#xff1a; g_unix: UNIX系统学习笔记 (gitee.com) 有些理念可能在控制类的嵌入式系统中不好实施&#xff0c;尤其是没有unix这样的系统搭载的情况下。如果是考虑在RTOS的基础上看是否有一些理念可以做尝试&#xff0c;我觉得还是可以有一定…

Android: 深入理解 ‘companion object {}‘

Android: 深入理解 ‘companion object {}’ Kotlin是一种现代的、静态类型的编程语言&#xff0c;它在设计时充分考虑了开发者的生产力和代码的可读性。其中一个独特的特性就是companion object。在本篇博客中&#xff0c;我们将深入探讨这个特性&#xff0c;理解它的工作原理…

一款轻量级、高性能、功能强大的内网穿透代理服务器

简介 nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。目前支持tcp、udp流量转发&#xff0c;可支持任何tcp、udp上层协议&#xff08;访问内网网站、本地支付接口调试、ssh访问、远程桌面&#xff0c;内网dns解析等等……&#xff09;&#xff0c;此外还支持内网htt…

Node需要了解的知识

Node能执行javascript的原因。 浏览器之所以能执行Javascript代码&#xff0c;因为内部含有v8引擎。Node.js基于v8引擎封装&#xff0c;因此可以执行javascript代码。Node.js环境没有DOM和BOM。DOM能访问HTML所有的节点对象&#xff0c;BOM是浏览器对象。但是node中提供了cons…

网络攻防模拟与城市安全演练 | 图扑数字孪生

在数字化浪潮的推动下&#xff0c;网络攻防模拟和城市安全演练成为维护社会稳定的不可或缺的环节。基于数字孪生技术我们能够在虚拟环境中进行高度真实的网络攻防模拟&#xff0c;为安全专业人员提供实战经验&#xff0c;从而提升应对网络威胁的能力。同时&#xff0c;在城市安…

【Android】二级分类双列表联动Demo

先上图&#xff1a; Demo解释 demo使用的是双列表展示&#xff08;准确的说是三个&#xff0c;二级分类那里嵌套了一个&#xff09;&#xff0c;点击左边的条目&#xff0c;右边的列表会跳转相应的条目&#xff0c;滑动右边的列表&#xff0c;左边的列表也会相应的滑动。 代…

2024 年 10 款最佳免费无限的数据恢复软件工具

十大无限的数据恢复软件工具 数据丢失可能是一场噩梦&#xff0c;无论是由于意外删除、系统崩溃还是硬件故障。值得庆幸的是&#xff0c;有多种数据恢复软件工具可以帮助您检索珍贵的文件和文档。在本文中&#xff0c;我们将探讨可以拯救世界的十大最佳免费无限数据恢复软件工…

第二百零五回

文章目录 1. 概念介绍2. 实现方法2.1 文字信息2.2 红色边框 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何实现密码输入框"相关的内容&#xff0c;本章回中将介绍如何在在输入框中提示错误.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们…

RT-Thread线程管理(使用篇)

layout: post title: “RT-Thread线程管理” date: 2024-1-26 15:39:08 0800 tags: RT-Thread 线程管理(使用篇) 之后会做源码分析 线程是任务的载体&#xff0c;是RTT中最基本的调度单位。 线程执行时的运行环境称为上下文&#xff0c;具体来说就是各个变量和数据&#xff0c…

stable diffusion学习笔记——高清修复

ai画图中通常存在以下痛点&#xff1a; 受限于本地设备的性能&#xff08;主要是显卡显存&#xff09;&#xff0c;无法跑出分辨率较高的图片。生图的时候分辨率一调大就爆显存。即便显存足够。目前主流的模型大多基于SD1.0和SD1.5&#xff0c;这些模型在训练的时候通常使用小…

【Java】IoC五大注解和@Bean注解

1、用于类的五大注解 Controller 控制器存储 Service 服务存储 Component 组件存储 Configuration 配置存储 Repository 仓库存储 从功能上看&#xff0c;除了Controller&#xff0c;其他效果一样&#xff1a;接口的入口必须为Controller ​几者的关系如下 2、用于方法的…

Leetcode92:反转链表II(区间反转链表)

一、题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4 输出&#xff1a…

一篇文章解决你的无线AP选型难题:从入门到精通

无线网络覆盖项目中&#xff0c;无线AP的合理选型和部署非常重要。今天给大家安排。 这篇文章&#xff0c;给你总结了6类典型的无线组网场所&#xff0c;针对每种场景的特点&#xff0c;给出相应的设备选型和部署的方案&#xff0c;同时整理了一些部署无线AP过程中容易忽略的细…

阿里云服务器一键部署幻兽帕鲁专属服务器,图形化可视化修改游戏参数是如此简单!

幻兽帕鲁这款游戏最近很火&#xff0c;在线玩家太多了&#xff0c;官方服务器也是承压巨大&#xff0c;很多玩家现在都是搭建自己的专属幻兽帕鲁服务器来玩。搭建教程现在也是越来越简单了&#xff0c;可以说是真正的零基础&#xff0c;一看就会&#xff0c;并且开箱即玩&#…

GMT绘图笔记

(1)图框设置。在利用GMT绘制图件时&#xff0c;需要设置边框的类型&#xff0c;字体的大小&#xff0c;标记距离边框的距离。主要涉及的参数有&#xff1a; gmt set MAP_FRAME_TYPE plain/fancy 可以调整边框为火车轨道或者线段。 (2)调整图框的粗细&#xff1a;主要是包含有…