mybatis详解

mybatis:原来是apache的一个开源项目,叫ibatis。2010年转移谷歌,从3.0开始改名为mybatis
mybatis是一款优秀的持久层框架,是对jdbc功能进行轻量级的封装,提供了统一的数据库信息配置统一放在一个xml文件中,读取就行。将sql提取到一个xml文件中,提供了动态sql功能,提供了结果自动映射封装。
是一个orm(ORM Object Relational Mapping 对象关系映射)实现,orm指的是,将数据中的记录与java中的对象进行关系映射
对jdbc原生接口进行封装,提供了一些mybatis自己的接口和类来实现

MyBatis 环境搭建

1.创建一个maven项目, 把项目添加到git仓库
2.创建数据库表,创建对应的类

create database ssmdb charset utf8
create table admin(
	id int primary key auto_increment,
	account varchar(30),
	password varchar(50)
)
package com.ffyc.mybatisPro.model;

public class Admin {
    private int id;
    private String account;
    private String password;

    public int getId() {
        return id;
    }

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

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", account='" + account + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

3.添加mybatis相关的依赖 mybatis、mysql

<dependencies>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.2</version>
    </dependency>
</dependencies>

4.创建一个mybatis的核心配置文件

  • 数据库连接部分信息
  • sql映射文件
<!--自己用的配置文件在resources,New/File下创建一个文件mybatis_config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="config.properties"></properties>  <!--导入配置属性文件-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/> <!--数据事务管理配置-->
            <dataSource type="POOLED"> <!--数据源配置,type="POOLED"使用数据库连接池-->
                <property name="driver" value="${classDriverName}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${uname}"/>
                <property name="password" value="${pwd}"/>
            </dataSource>
        </environment>
     <!--   <environment id="work">
        </environment>-->
    </environments>
    <mappers> <!--配置SQL映射文件-->
        <mapper resource="mappers/AdminMapper.xml"/>
    </mappers>
</configuration>
//在resources/config.properties中
classDriverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/ssmdb?serverTimezone=Asia/Shanghai
uname=root
pwd=123456

5.开发了接口

package com.ffyc.mybatisPro.dao;

import com.ffyc.mybatisPro.model.Admin;
//通过id,查询之后返回Admin对象
public interface AdminDao {
    Admin findAdminById(int id);
}

6.创建了sql映射文件

/*在resources/mappers/adminMapper.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.ffyc.mybatisPro.dao.AdminDao"> <!--和接口名字一致-->
    <select id="findAdminById" parameterType="int" resultType="Admin">  /*可以直接写Admin*/
        select id,account,password from admin where id=#{id}
    </select>
</mapper>

MybatisX插件,可以切换接口SQL映射
在这里插入图片描述
在这里插入图片描述
7.测试

SqLSessionFactory用来封装配置信息,主要用来创建SqLSession对象。由于SqLSessionFactory对象创建开销较大,所以一个项目中只创建一个,不用关闭
SqLSession用来每次与数据库会话使用的,每次与数据库交互一次都需要创建一个独立的sqlSession

package com.ffyc.mybatisPro.test;
public class Test {
    public static void main(String[] args) throws IOException {
        Reader reader = Resources.getResourceAsReader("mybatis_config.xml");//读取配置文件
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);//创建SqlSessionFactory
        SqlSession sqlSession = sessionFactory.openSession();//创建SqlSession
        //AdminDao.class(拿到类被加载到内存中的CLass对象),getMapper创建了接口的代理对象
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin = adminDao.findAdminById(1);//调用接口中方法 对应的sql
        System.out.println(admin);
        sqlSession.close();//关闭了sqlSession,不用关闭sessionFactory
    }
}

xml配置

配置类型简称

<typeAliases> <!--配置类型简称-->
    <!--<typeAlias type="com.ffyc.mybatisPro.model" alias="Admin"></typeAlias>-->
    <package name="com.ffyc.mybatisPro.model"/>
</typeAliases>
<?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.ffyc.mybatisPro.dao.AdminDao">
    <select id="findAdminById" parameterType="int" resultType="Admin">  /*可以直接写Admin*/
        select id,account,password from admin where id=#{id}
    </select>
</mapper>

常用设置

<settings> <!--常用设置-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

junit单元测试

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>provided</scope> 
</dependency>
package com.ffyc.mybatisPro.test;

public class Test1 {
    @Test
    public void find(){
        System.out.println("aaa");
    }
    @Test
    public void save(){
        System.out.println("bbb");
    }
}

对Test代码进行封装

package com.ffyc.mybatisPro.util;

public class MybatisUtil {
    static SqlSessionFactory sessionFactory;
    //静态代码块,在类加载时执行一次
    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis_config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        sessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
    //创建并返回SqlSession
    public static SqlSession getSqlSession(){
        return sessionFactory.openSession();
    }
}
public class Test1 {
    @Test
    public void find(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin = adminDao.findAdminById(1);//调用接口中方法 对应的sql
        System.out.println(admin);
        sqlSession.close();
    }
}

参数传递

1.基本类型的单个参数,直接传递

public interface AdminDao {
    Admin findAdminById(int id);
}
<mapper namespace="com.ffyc.mybatisPro.dao.AdminDao"> <!--和接口名字一致-->
   <select id="findAdminById" parameterType="int" resultType="com.ffyc.mybatisPro.model.Admin">  /*可以直接写Admin*/
        select id,account,password from admin where id=#{id}
    </select>
</mapper>
public class Test1 {
    @Test
    public void find(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin = adminDao.findAdminById(1);//调用接口中方法 对应的sql
        System.out.println(admin);
        sqlSession.close();
    }
}

2.多个参数传递
方法一:多个参数、或引用类型使用注解标签@Param绑定

public interface AdminDao {
    Admin login(@Param("acc")String account, @Param("pwd") String password);
}
<mapper namespace="com.ffyc.mybatisPro.dao.AdminDao"> <!--和接口名字一致-->
    <select id="login" resultType="com.ffyc.mybatisPro.model.Admin">
        select * from admin where account=#{acc} and password=#{pwd}
    </select>
</mapper>
public class Test1 {
    @Test
    public void login(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin = adminDao.login("admin","admin");//调用接口中方法 对应的sql
        System.out.println(admin);
        sqlSession.close();
    }
}

方法二:多个参数封装到一个对象中(推荐)

public interface AdminDao {
    Admin login2(Admin admin);
}
<mapper namespace="com.ffyc.mybatisPro.dao.AdminDao"> <!--和接口名字一致-->
    <select id="login2" parameterType="com.ffyc.mybatisPro.model.Admin" resultType="com.ffyc.mybatisPro.model.Admin">
        select * from admin where account=#{account} and password=#{password} /*model里面的属性*/
    </select>
</mapper>
public class Test1 {
    @Test
    public void login2(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin=new Admin();
        admin.setAccount("admin");
        admin.setPassword("admin");
        Admin a= adminDao.login2(admin);//调用接口中方法 对应的sql
        System.out.println(a);
        sqlSession.close();
    }
}

3.字符串类型单独传递时,需要进行绑定处理
SQL中接收参数:#{} 和${}区别(面试必问)

  • #{参数名} 占位符,预编译方式传值,编译好 SQL 语句再取值,#方式能够防止 sql 注入,更安全
    select * from admin where account=#{account} and password=#{password}
    在这里插入图片描述
  • 参数名拼接符,主要用来动态的向 s q l 中传列名;传值时是直接将参数拼接到 s q l 中 ( 不建议 ) ,需要加单引号,会传入参数字符串,取值以后再去编译 S Q L 语句,无法防止 S q l 注入 s e l e c t ∗ f r o m a d m i n w h e r e a c c o u n t = ′ ¥ a c c o u n t ′ a n d p a s s w o r d = ′ {参数名} 拼接符,主要用来动态的向sql中传列名;传值时是直接将参数拼接到sql中(不建议),需要加单引号,会传入参数字符串,取值以后再去编译 SQL 语句,无法防止 Sql 注入 select * from admin where account='¥{account}' and password=' 参数名拼接符,主要用来动态的向sql中传列名;传值时是直接将参数拼接到sql(不建议),需要加单引号,会传入参数字符串,取值以后再去编译SQL语句,无法防止Sql注入selectfromadminwhereaccount=accountandpassword={password}’
    在这里插入图片描述
    select * from admin where ${column}=‘admin’
  • 注意:MyBatis 排序时使用 order by 动态参数时需要注意,用$而不是#
    select * from admin order by ${column} 动态传列名
public interface AdminDao {
    Admin find2(@Param("column")String column);
}
<select id="find2" parameterType="String" resultType="com.ffyc.mybatisPro.model.Admin">
    select * from admin where ${column}='admin'
</select>
@Test
public void find2(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    Admin admin = adminDao.find2("account");//调用接口中方法 对应的sql
    System.out.println(admin);
    sqlSession.close();
}

新增

public interface AdminDao {
    void saveAdmin(Admin admin);
}
<insert id="saveAdmin" parameterType="com.ffyc.mybatisPro.model.Admin">
    insert into admin(account,password) values (#{account},#{password})
</insert>

事务: 我们每次对数据库连接上,发送sql到执行完,数据库有一个机制进行控制开启事务
做一个转账操作

sql1 -100
java… 异常
sql2 +100
java…
提交事务:当我们做完所有的事情之后,提交事务,数据库真正的执行

jdbc默认是自动提交事务;mybatis把自动事务提交关闭了:Setting autocommit to false,需要在所有的业务代码执行成功后,手动提交事务

@Test
public void save(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    Admin admin=new Admin();
    admin.setAccount("zhangsan");
    admin.setPassword("zhangsan");
    adminDao.saveAdmin(admin);//调用接口中方法 对应的sql
    sqlSession.commit();//提交事务;新增,修改,删除操作尽量都在事务控制中进行
    sqlSession.close();
}

保存管理员后,立即拿到id
开启将生成的主键列,自动封装到对象中

<!--keyProperty="id"是类中的属性名,keyColumn="id"是数据库中的列名;把数据库生成的主键拿到赋给Admin对象中的id属性-->
<insert id="saveAdmin" parameterType="com.ffyc.mybatisPro.model.Admin" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
    insert into admin(account,password) values (#{account},#{password})
</insert>
@Test
    public void save(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        Admin admin=new Admin();
        admin.setAccount("zhangsan");
        admin.setPassword("zhangsan");
        adminDao.saveAdmin(admin);//调用接口中方法 对应的sql
        System.out.println(admin.getId());
        sqlSession.commit();//提交事务;新增,修改,删除操作尽量都在事务控制中进行
        sqlSession.close();
    }

修改

public interface AdminDao {
    void updateAdmin(Admin admin);
}
<update id="updateAdmin" parameterType="com.ffyc.mybatisPro.model.Admin">
    update admin set account=#{account},password=#{password} where id=#{id}
</update>
@Test
public void update(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    Admin admin=new Admin();
    admin.setId(4);
    admin.setAccount("lisi");
    admin.setPassword("lisi");
    adminDao.updateAdmin(admin);//调用接口中方法 对应的sql
    sqlSession.commit();//提交事务;新增,修改,删除操作尽量都在事务控制中进行
    sqlSession.close();
}

删除,通过id删除管理员

public interface AdminDao {
    void deleteAdminById(int id);
}
<delete id="deleteAdminById" parameterType="int">
    delete from admin where id=#{id}
</delete>
@Test
public void delete(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    adminDao.deleteAdminById(4);//调用接口中方法 对应的sql
    sqlSession.commit();//提交事务;新增,修改,删除操作尽量都在事务控制中进行
    sqlSession.close();
}

查询

1、单张表查询

1.查询管理员的总数

public interface AdminDao {
    //查询管理员的总数
    int adminCount();
}
<select id="adminCount" resultType="int">
    select count(*) from admin
</select>
@Test
    public void find(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
        int count=adminDao.adminCount();//调用接口中方法 对应的sql
        System.out.println(count);
        sqlSession.close();
    }
设置名描述有效值默认值
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN映射到经典 Java 属性名 aColumn。true | falseFalse

2.通过id查询

public interface AdminDao {
    Admin findAdminById(int id);
}

mybatis会将查询到的结果自动封装到一个对象中,会自己创建给定类型的类的对象
自动封装结果的条件:

  1. 开启了全局的自动结果映射,PARTIAL默认是单张表开启的
  2. 数据库列名与属性名名字相同;如果名字不一致,不能映射,可以定义别名使其一致
  3. mapUnderscoreToCamelCase = true;数据库中admin_gender可以映射到类中的adminGender
<!--mybatis会将查询到的结果自动封装到一个对象中,会自己创建给定类型的类的对象-->
<select id="findAdminById" parameterType="int" resultType="com.ffyc.mybatisPro.model.Admin">
    select id,account,password,admin_gender from admin where id=#{id}
</select>
@Test
public void find2(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    Admin admin=adminDao.findAdminById(1);//调用接口中方法 对应的sql
    System.out.println(admin);
    sqlSession.close();
}
<settings> <!--常用设置-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.查询多个管理员,先创建对象然后自动装入集合中

public interface AdminDao {
    List<Admin>  findAdmins();
}
<select id="findAdmins" resultType="com.ffyc.mybatisPro.model.Admin">
    select id,account,password,admin_gender from admin
</select>
@Test
public void find3(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    AdminDao adminDao = sqlSession.getMapper(AdminDao.class);
    List<Admin> list=adminDao.findAdmins();//调用接口中方法 对应的sql
    System.out.println(list);
    sqlSession.close();
}
<settings> <!--常用设置-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

在resultMap标签中,可以进行自定义的结果映射
column="admin_gender"是数据库中的, property="gender"是类中的

<!--在resultMap标签中,可以进行自定义的结果映射-->
<resultMap id="adminMap" type="com.ffyc.mybatisPro.model.Admin">
    <id column="id" property="id"></id>
    <result column="account" property="account"></result>
    <result column="password" property="password"></result>
    <result column="admin_gender" property="gender"></result>
</resultMap>
<select id="findAdmins" resultType="com.ffyc.mybatisPro.model.Admin" resultMap="adminMap">
    select id,account,password,admin_gender from admin
</select>

2、多张表查询

2.1关联查询

1.通过id查询学生
数据库

create table student(
	id int primary key auto_increment,
	num int,
	name varchar(10),
	gender char(1),
	birthday date,
	dormid int,
	adminid int,
	oper_time datetime
)

create table dorm(
	id int primary key auto_increment,
	num int
)

注意:
建议模型类中的属性基本类型都使用包装类型,因为引用类型默认值为null
一旦类中定义了有参构造方法,一定要把无参构造方法显示的定义出来,因为框架需要使用它
java中可以进行方法重载,mybatis中不行,因为.xml是通过id(接口的方法名)区分的
在这里插入图片描述

package com.ffyc.mybatisPro.model;
public class Student {
    private Integer id;//建议使用引用类型,默认值为null
    private Integer num;
    private String name;
    private String gender;
    private Date birthday;
    private Dorm dorm;//关联关系,一个类关联另一个类
    private Admin admin;
    private Date operTime;

    //get和set方法
}
package com.ffyc.mybatisPro.model;
public class Dorm {
    private int id;
    private int num;//宿舍号
    private  Admin admin;
    private List<Student> students;//一个宿舍对应多个学生

    public Admin getAdmin() {
        return admin;
    }

    public void setAdmin(Admin admin) {
        this.admin = admin;
    }

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

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

    public int getId() {
        return id;
    }

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

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Dorm{" +
                "id=" + id +
                ", num=" + num +
                '}';
    }
}
public interface StudentDao {
    Student findStudentById(int id);
}
<?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.ffyc.mybatisPro.dao.StudentDao">
    <resultMap id="studentMap" type="com.ffyc.mybatisPro.model.Student">
        <id column="id" property="id"></id>
        <result column="num" property="num"></result>
        <result column="name" property="name"></result>
        <result column="gender" property="gender"></result>
        <result column="birthday" property="birthday"></result>
        <result column="oper_time" property="operTime"></result>
       <!-- 用来封装关联的对象信息,property="dorm",就会创建一个关联的对象-->
        <!--column="dormNum"是学生中的, property="num"是dorm中的-->
        <association property="dorm" javaType="com.ffyc.mybatisPro.model.Dorm">
            <result column="dormNum" property="num"></result>
        </association>
        <association property="admin" javaType="com.ffyc.mybatisPro.model.Admin">
            <result column="account" property="account"></result>
        </association>
    </resultMap>
    <select id="findStudentById" parameterType="int" resultMap="studentMap">
        select
        s.id,
        s.num,
        s.name,
        s.gender,
        s.birthday,
        d.num dormNum,
        a.account,
        s.oper_time
    from student s left join dorm d on s.dormid=d.id
                left join admin a on s.adminid=a.id
                where s.id=#{id}
    </select>
</mapper>
public class TestStudent {
    @Test
    public void find(){
        SqlSession sqlSession= MybatisUtil.getSqlSession();
        StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
        Student student =studentDao.findStudentById(1);//调用接口中方法 对应的sql
        System.out.println(student.getNum());//拿到学号
        System.out.println(student.getDorm().getNum());//拿到宿舍号
        sqlSession.close();
    }
}

mybatis默认配置是:一旦出现了套关联查询,就不自动映射,可以开启(可以映射的都赋值);如果在resultMap标签中进行自定义的结果映射就不需要这个(把指定的赋值)

<settings> <!--常用设置-->
    <setting name="autoMappingBehavior" value="FULL"/>
</settings>

2.查询多个学生

public interface StudentDao {
    List<Student> findStudents();
}
<resultMap id="studentMap" type="com.ffyc.mybatisPro.model.Student">
    <id column="id" property="id"></id>
    <result column="num" property="num"></result>
    <result column="name" property="name"></result>
    <result column="gender" property="gender"></result>
    <result column="birthday" property="birthday"></result>
    <result column="oper_time" property="operTime"></result>
   <!-- 用来封装关联的对象信息,property="dorm",就会创建一个关联的对象-->
    <!--column="dormNum"是学生中的, property="num"是dorm中的-->
    <association property="dorm" javaType="com.ffyc.mybatisPro.model.Dorm">
        <result column="dormNum" property="num"></result>
    </association>
    <association property="admin" javaType="com.ffyc.mybatisPro.model.Admin">
        <result column="account" property="account"></result>
    </association>
</resultMap> 
<select id="findStudents" resultMap="studentMap">
    select
    s.id,
    s.num,
    s.name,
    s.gender,
    s.birthday,
    d.num dormNum,
    a.account,
    s.oper_time
    from student s left join dorm d on s.dormid=d.id
            left join admin a on s.adminid=a.id
</select>
@Test
public void find2(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    List<Student> list=studentDao.findStudents();//调用接口中方法 对应的sql
    System.out.println(list);
    sqlSession.close();
}
2.2嵌套查询

使用嵌套查询,把sql分成多次查询,先查询学生信息,然后再查询宿舍、管理员

public interface StudentDao {
    Student findStudentById1(int id);
}
<resultMap id="studentMap1" type="com.ffyc.mybatisPro.model.Student">
    <id column="id" property="id"></id>
    <result column="num" property="num"></result>
    <result column="name" property="name"></result>
    <result column="gender" property="gender"></result>
    <result column="birthday" property="birthday"></result>
    <result column="oper_time" property="operTime"></result>
    <association property="dorm" javaType="com.ffyc.mybatisPro.model.Dorm" column="dormid" select="findDormById"></association>
    <association property="admin" javaType="com.ffyc.mybatisPro.model.Admin" column="adminid" select="findAdminById"></association>
</resultMap>
<select id="findStudentById1" parameterType="int" resultMap="studentMap1">
    select id,num,name,gender,birthday,dormid,adminid from student where id=#{id}
</select>
<select id="findDormById" resultType="com.ffyc.mybatisPro.model.Dorm">
    select num from dorm where id=#{dormid}   /*dormid是column="dormid"传过来的*/
</select>
<select id="findAdminById" resultType="com.ffyc.mybatisPro.model.Admin">
    select account from admin where id=#{adminid}
</select>
@Test
public void find3(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    studentDao.findStudentById1(1);//调用接口中方法 对应的sql
    sqlSession.close();
}

2.3 查询宿舍下所有的学生
宿舍和学生关联的话,会有多条记录,但是对于宿舍只有一个记录;将关联多个学生封装到一个集合对象中,在宿舍里面放一个学生的集合

public interface DormDao {
    Dorm findDormById(int id);
}
<mapper namespace="com.ffyc.mybatisPro.dao.DormDao">
    <resultMap id="dormMap" type="com.ffyc.mybatisPro.model.Dorm">
        <id column="id" property="id"></id>
        <result column="num" property="num"></result>
        <association property="admin" javaType="com.ffyc.mybatisPro.model.Admin">
            <result column="account" property="account"></result>
        </association>
        <!--用collection最终返回一个对象;ofType是:list类型中装的Student类型-->
        <collection property="students" javaType="list" ofType="com.ffyc.mybatisPro.model.Student">
            <!--result column="name"是Dorm中, property="name"是Student类中-->
            <result column="snum" property="num"></result>
            <result column="name" property="name"></result>
        </collection>
    </resultMap>
    <select id="findDormById" parameterType="int" resultMap="dormMap">
        select
            d.id,
            d.num,
            s.num snum,
            s.name,
            a.account
        from dorm d left join admin a on d.adminid=a.id
                     left join student s on d.id=s.dormid
                     where d.id=#{id}
    </select>
</mapper>
@Test
public void find(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    DormDao dormDao = sqlSession.getMapper(DormDao.class);
    Dorm dorm =dormDao.findDormById(1);//调用接口中方法 对应的sql
    sqlSession.close();
}

注解方式

常用注解标签
@Insert:插入 sql , 和 xml insert sql 语法完全一样
@Select :查询 sql, 和 xml select sql 语法完全一样
@Update:更新 sql, 和 xml update sql 语法完全一样
@Delete :删除 sql, 和 xml delete sql 语法完全一样
@Param :入参
@Results:设置结果集合
@Result :结果

public interface StudentDao {
    @Delete("delete from student where id=#{id}")
    void deleteStudent(int id);
}
@Test
public void delete(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    studentDao.deleteStudent(1);//调用接口中方法 对应的sql
    sqlSession.commit();
    sqlSession.close();
}
@Update("update student set num = 103 where id = #{id}")
void updateState(int id);

@Select("select num,name from student where id = #{id}")
Student findStudent(int id);

Mybatis动态SQL

MyBatis 的一个强大的特性之一通常是它的动态 SQL 能力。 如果你有使用JDBC 或其他相似框架的经验,你就明白条件地串联 SQL 字符串在一起是多么的痛苦,确保不能忘了空格或在列表的最后省略逗号。动态 SQL 可以彻底处理这种痛苦。

where、if

if test=“条件”:可以对传入的条件进行判断
对于查询条件个数不确定的情况,可使用元素。where标签:当where标签内的if有成立的,就会动态的添加一个where关键字;如果where后面有and、or这种关键字,也会动态删除
在这里插入图片描述

public interface StudentDao {
    List<Student> findStudents1(Student student);
}
<select id="findStudents1" resultType="com.ffyc.mybatisPro.model.Student">
    select * from student
    <where>
        <if test="num!=null">
            and num=#{num}
        </if>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="gender!=null">
            and gender=#{gender}
        </if>
    </where>
</select>
@Test
public void query(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student student=new Student();
    //student.setNum(103);
    student.setName("李四");
    studentDao.findStudents1(student);//调用接口中方法 对应的sql
    sqlSession.close();
}

trim

trim中的prefix可以自定义指定关键字,如果if成立就动态的添加指定的关键字
prefixOverrides删除掉指定的开头的关键字

<select id="findStudents1" resultType="com.ffyc.mybatisPro.model.Student">
    select * from student
    <trim prefix="where" prefixOverrides="and|or">
        <if test="num!=null">
            and num=#{num}
        </if>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="gender!=null">
            and gender=#{gender}
        </if>
    </trim>
</select>

choose、when、otherwise

相当于if…else if…else;choose里面可以有when,可以没有otherwise

<select id="findStudents1" resultType="com.ffyc.mybatisPro.model.Student">
    select * from student
    <trim prefix="where" prefixOverrides="and|or">
        <choose>
            <when test="num!=null">
                num=#{num}
            </when>
            <when test="name!=null">
                and name=#{name}
            </when>
            <otherwise>
                and gender=#{gender}
            </otherwise>
        </choose>
    </trim>
</select>

set

set 可以动态的添加set关键字,可以去除最后的逗号;主要用于多次修改

public interface StudentDao {
    void updateStudent(Student student);
}
<update id="updateStudent" parameterType="com.ffyc.mybatisPro.model.Student">
    update student
        <set>
            <if test="num!=null">
                num=#{num},
            </if>
            <if test="name!=null">
                name=#{name},
            </if>
            <if test="gender!=null">
                gender=#{gender}
            </if>
        </set>
        where id=#{id}
</update>

也可以用trim实现

<update id="updateStudent" parameterType="com.ffyc.mybatisPro.model.Student">
    update student
    <trim prefix="set" suffixOverrides=",">
        <if test="num!=null">
            num=#{num},
        </if>
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="gender!=null">
            gender=#{gender}
        </if>
    </trim>
    where id=#{id}
</update>
@Test
public void update(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student student=new Student();
    student.setId(2);
    student.setNum(104);
    student.setName("李四44");
    studentDao.updateStudent(student);//调用接口中方法 对应的sql
    sqlSession.commit();
    sqlSession.close();
}

foreach

对集合进行遍历(尤其是在构建 IN 条件语句的时候)

public interface StudentDao {
    void deleteStudent(List<Integer> list);
}
<!--delete from student where id in (2,3)批量删除,对数组或集合进行遍历-->
<delete id="deleteStudent">
    delete from student where id in  
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
</delete>
@Test
public void delete1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    List<Integer> list=new ArrayList<>();
    list.add(2);
    list.add(3);
    studentDao.deleteStudent(list);//调用接口中方法 对应的sql
    sqlSession.commit();
    sqlSession.close();
}

对数组进行遍历

public interface StudentDao {
    void deleteStudent2(Integer[] array);
}
<delete id="deleteStudent2">
    delete from student where id in
        <foreach collection="array" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
</delete>
@Test
public void delete2(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Integer[] array={4,5};
    studentDao.deleteStudent2(array);//调用接口中方法 对应的sql
    sqlSession.commit();
    sqlSession.close();
}

标记语言中,特殊符号处理

方式一:转义符号处理。在 mybatis 中的 xml 文件中,存在一些特殊的符号,比如:<、>、"、&、<>等,正常书写 mybatis 会报错,需要对这些符号进行转义。具体转义如下所示:

特殊字符转义字符
<&lt ;
>&gt ;
"&quot ;
&apos ;
&&amp ;
public interface StudentDao {
    List<Student> findStudents2(int num);
}
select id="findStudents2" resultType="com.ffyc.mybatisPro.model.Student">
    select * from student where num&lt;#{num}
</select>
@Test
public void query1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    studentDao.findStudents2(103);//调用接口中方法 对应的sql
    sqlSession.close();
}

方式二:除了可以使用上述转义字符外,还可以使用<![CDATA[]]>来包裹特殊字符。如下所示:

<select id="findStudents2" resultType="com.ffyc.mybatisPro.model.Student">
    select * from student where num <![CDATA[ < ]]> #{num}
</select>
<![CDATA[ ]]>是 XML 语法。在 CDATA 内部的所有内容都会被解析器忽略。但是有个问题那就是 、 、 等这些标签都不会被解析,所以 我们只把有特殊字符的语句放在 <![CDATA[ ]]> ,**尽量缩小<![CDATA[ ]]> 的范围。**

缓存

缓存(cache)的作用是为了减去数据库的压力,提高查询性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不要销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存(缓存)中直接获取,不再向数据库执行select 语句,从而减少了对数据库的查询次数,因此提高了数据库的性能。

一级缓存

生:创建sqlsession
销毁:close()、clearCache()、执行update,delete,insert
Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据,不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。
在这里插入图片描述

@Test
public void cacheDemo1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student s1=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    Student s2=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();
}

在这里插入图片描述
查询了两次,但是sql语句执行了一次?因为两次查询的SQL语句一样,查询id为6 返回的对象会缓存到SqlSession对象中,SqlSession对象在内存中存,如果下一次还查询id为6的,就直接访问SqlSession 从缓存中拿就可以
一级缓存的生命周期
a、MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象;如果 SqlSession 调用了 close()方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用

@Test
public void cacheDemo1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student s1=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();//清除缓存
    SqlSession sqlSession2= MybatisUtil.getSqlSession();
    StudentDao studentDao2 = sqlSession2.getMapper(StudentDao.class);
    Student s2=studentDao2.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();
}

b、如果 SqlSession 调用了clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。

@Test
public void cacheDemo1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student s1=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.clearCache();//清除一级缓存中的数据
    Student s2=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();
}

在这里插入图片描述
c、SqlSession 中执行了任何一个 update 操作(update()、delete()、insert()) ,都会清空一级缓存的数据,但是该对象可以继续使用。

@Test
public void cacheDemo1(){
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student s1=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    Student student=new Student();
    student.setId(6);
    student.setNum(106);
    student.setName("李四");
    studentDao.updateStudent(student);//中间执行新增、删除、修改操作,一级缓存也会清除
    Student s2=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();
}

在这里插入图片描述

二级缓存

二级缓存是 SqlSessionFactory 级别的,SqlSessionFactory只有一个。
SqlSession1创建,查询到的数据放在SqlSessionFactory中,SqlSession1销毁;SqlSession2创建,仍然可以拿到SqlSessionFactory中数据
配置二级缓存
第一步:启用二级缓存
在 mybatis_config.xml中启用二级缓存,如下代码所示,当cacheEnabled 设置为 true 时启用二级缓存,设置为 false 时禁用二级缓存。

 <!--在mybatis_config.xml中配置-->
<settings> <!--全局开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

可以全局的开启缓存,也可以局部的开启或关闭缓存(useCache=“true”)
在这里插入图片描述
第二步:对象序列化
将所有的 model类实现序列化接口 Java.io. Serializable。

package com.ffyc.mybatisPro.model;

import java.io.Serializable;

public class Student implements Serializable {
    //......
}

第三步:配置映射文件
在 Mapper 映射文件中添加,表示此 mapper 开启二级缓存。
当 SqlSeesion 关闭时,会将数据存入到二级缓存.

<!--StudentMapper.xml中配置二级缓存-->
<cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"
/>
public class MybatisUtil {
    static SqlSessionFactory sessionFactory;
    //静态代码块,在类加载时执行一次
    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis_config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        sessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
    //创建并返回SqlSession
    public static SqlSession getSqlSession(){
        return sessionFactory.openSession();
    }
}
@Test
public void cacheDemo2(){
    //用的是同一个SqlSessionFactory
    SqlSession sqlSession= MybatisUtil.getSqlSession();
    StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
    Student s1=studentDao.findStudentById(6);//调用接口中方法 对应的sql
    sqlSession.close();//一级缓存销毁,毁时会将缓存写入到二级缓存

    SqlSession sqlSession2= MybatisUtil.getSqlSession();
    StudentDao studentDao2 = sqlSession2.getMapper(StudentDao.class);
    Student s2=studentDao2.findStudentById(6);
    //此时可以从二级缓存获取数据,因为共享的是同一个SqlSessionFactory
    sqlSession.close();
}

在这里插入图片描述
可以单独为某一个语句关闭二级缓存,加上useCache=“false”,即使配置过二级缓存,二级缓存也会失效

<select id="findStudentById" parameterType="int" resultMap="studentMap" useCache="false">
    select
        s.id,
        s.num,
        s.name,
        s.gender,
        s.birthday,
        d.num dormNum,
        a.account,
        s.oper_time
        from student s left join dorm d on s.dormid=d.id
                left join admin a on s.adminid=a.id
                where s.id=#{id}
</select>

在这里插入图片描述

MyBatis 架构

在这里插入图片描述

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

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

相关文章

C++初阶语法——static类成员

前言&#xff1a;本文将介绍类和对象中的static类成员——静态成员函数&#xff0c;静态成员变量的使用方法和注意点。在某些场景下&#xff0c;静态成员很有意义。 目录 一.概念二.特性静态成员不存在对象中 三.静态成员变量在类外初始化四.静态成员函数 一.概念 声明为stati…

记录一次arcgis engine开发版本引入问题

之前基于arcigs 10.1vs2013开发的程序&#xff0c;现在拿出来要改&#xff0c;但是目前版本是arcgis10.7vs2017/vs2019,打开后无论如何替换引用版本&#xff0c;都报错 &#xff08;具体版本对应可以看这&#xff1a;ArcGIS Engine 与 Visual Studio 版本对照表_vs2019对应啥版…

华为开源自研AI框架昇思MindSpore应用案例:基于MindSpore框架的UNet-2D案例实现

目录 一、环境准备1.进入ModelArts官网2.使用CodeLab体验Notebook实例 二、环境准备与数据读取三、模型解析Transformer基本原理Attention模块 Transformer EncoderViT模型的输入整体构建ViT 四、模型训练与推理模型训练模型验证模型推理 近些年&#xff0c;随着基于自注意&…

WinCC V7.5 中的C脚本对话框不可见,将编辑窗口移动到可见区域的具体方法

WinCC V7.5 中的C脚本对话框不可见&#xff0c;将编辑窗口移动到可见区域的具体方法 由于 Windows 系统更新或使用不同的显示器&#xff0c;在配置C动作时&#xff0c;有可能会出现C脚本编辑窗口被移动到不可见区域的现象。 由于该窗口无法被关闭&#xff0c;故无法进行进一步…

机器学习算法之-逻辑回归(1)

什么是回归 回归树&#xff0c;随机森林的回归&#xff0c;无一例外他们都是区别于分类算法们&#xff0c;用来处理和预测连续型标签的算法。然而逻辑回归&#xff0c;是一种名为“回归”的线性分类器&#xff0c;其本质是由线性回归变化而来的&#xff0c;一种广泛使用于分类问…

【傅里叶级数与傅里叶变换】数学推导——3、[Part4:傅里叶级数的复数形式] + [Part5:从傅里叶级数推导傅里叶变换] + 总结

文章内容来自DR_CAN关于傅里叶变换的视频&#xff0c;本篇文章提供了一些基础知识点&#xff0c;比如三角函数常用的导数、三角函数换算公式等。 文章全部链接&#xff1a; 基础知识点 Part1&#xff1a;三角函数系的正交性 Part2&#xff1a;T2π的周期函数的傅里叶级数展开 P…

SOLIDWORKS 2023中装配体配合的正确使用方法 硕迪科技

-SOLIDWORKS 装配体打开时是由不同的阶段和性能检查组成的。如果在创建装配体时未应用基本的配合方法&#xff0c;问题会随着时间的推移而累积&#xff0c;并且在使用时会出现明显的速度减慢。 如果您的装配体运行速度很慢&#xff0c;则很可能是在创建配合时出现了不良操作的症…

Nacos和GateWay路由转发NotFoundException: 503 SERVICE_UNAVAILABLE “Unable to find

问题再现&#xff1a; 2023-08-15 16:51:16,151 DEBUG [reactor-http-nio-2][CompositeLog.java:147] - [dc73b32c-1] Encoding [{timestampTue Aug 15 16:51:16 CST 2023, path/content/course/list, status503, errorService Unavai (truncated)...] 2023-08-15 16:51:16,17…

Vue的鼠标键盘事件

Vue的鼠标键盘事件 原生 鼠标事件(将v-on简写为) click // 点击 dblclick // 双击 mousedown // 按下 mousemove // 移动 mouseleave // 离开 mouseout // 移出 mouseenter // 进入 mouseover // 鼠标悬浮mousedown.left 键盘事件 keydown //键盘按下时触发 keypress …

SpringBoot3集成ElasticSearch

标签&#xff1a;ElasticSearch8.Kibana8&#xff1b; 一、简介 Elasticsearch是一个分布式、RESTful风格的搜索和数据分析引擎&#xff0c;适用于各种数据类型&#xff0c;数字、文本、地理位置、结构化数据、非结构化数据&#xff1b; 在实际的工作中&#xff0c;历经过Ela…

Azure存储账户

存储账户的概念 Azure存储账户是Azure提供的一种云存储解决方案&#xff0c;用于存储和访问各种类型的数据&#xff0c;包括文件、磁盘、队列、表格和Blob&#xff08;二进制大对象&#xff09;数据。存储账户可以基于访问模式和冗余需求来选择不同的类型&#xff0c;以满足应…

【【Verilog典型电路设计之FIFO设计】】

典型电路设计之FIFO设计 FIFO (First In First Out&#xff09;是一种先进先出的数据缓存器&#xff0c;通常用于接口电路的数据缓存。与普通存储器的区别是没有外部读写地址线&#xff0c;可以使用两个时钟分别进行写和读操作。FIFO只能顺序写入数据和顺序读出数据&#xff0…

Python “贪吃蛇”游戏,在不断改进中学习pygame编程

目录 前言 改进过程一 增加提示信息 原版帮助摘要 pygame.draw pygame.font class Rect class Surface 改进过程二 增加显示得分 改进过程三 增加背景景乐 增加提示音效 音乐切换 静音切换 mixer.music.play 注意事项 原版帮助摘要 pygame.mixer pygame.mix…

OSPF在广播类型的网络拓扑中DR和BDR的选举

指定路由器&#xff08;DR&#xff09;&#xff1a; 一个网段上的其他路由器都和指定路由器&#xff08;DR&#xff09;构成邻接关系&#xff0c;而不是它们互相之间构成邻接关系。 备份指定路由器&#xff08;BDR&#xff09;&#xff1a; 当DR出现问题&#xff0c;由BDR接…

如何通过MAT排查生产环境服务内存溢出

前言 前段时间&#xff0c;运维反馈生产环境翻译服务某个节点触发内存告警了。运维在重启节点之前&#xff0c;生成了dump快照&#xff0c;这里介绍下如何使用MAT内存分析工具来排查服务内存高占用问题。 MAT简介 MAT是Memory Analyzer的简称&#xff0c;它是一款功能强大的…

微信小程序卡片横向滚动竖图

滚动并不是使用swiper&#xff0c;该方式使用的是scroll-view实现 Swiper局限性太多了&#xff0c;对竖图并不合适 从左往右滚动图片示例 wxml代码&#xff1a; <view class"img-x" style"margin-top: 10px;"><view style"margin: 20rpx;…

【办公自动化】使用Python批量生成PPT版荣誉证书

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

PyMuPDF`库实现PDF旋转功能

本文介绍了一个简单的Python应用程序&#xff0c;用于将PDF文件转换为旋转90度的PDF文件。主要用于csdn网站中导出的博客pdf是横向的&#xff0c;看起来不是很方便&#xff0c;才想到用python编制一个将pdf从横向转为纵向的功能。 功能 该PDF转换工具具有以下功能&#xff1a…

国产之光:讯飞星火最新大模型V2.0

大家好&#xff0c;我是herosunly。985院校硕士毕业&#xff0c;现担任算法研究员一职&#xff0c;热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名&#xff0c;CCF比赛第二名&#xff0c;科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…

【linux基础(四)】对Linux权限的理解

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:Linux从入门到开通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学更多操作系统知识   &#x1f51d;&#x1f51d; Linux权限 1. 前言2. shell命…