[spring] Spring JPA - Hibernate 多表联查 1

news2025/3/19 5:48:42

[spring] Spring JPA - Hibernate 多表联查

之前在 [spring] spring jpa - hibernate 名词解释&配置 和 [spring] spring jpa - hibernate CRUD 简单的学习了一下怎么使用 Hibernate 实现 CRUD 操作,不过涉及到的部分都是逻辑上比较简单的实现——只在一张表上进行操作

关于多表关联的部分,只是在关键字中提到了有提到过 Relationship 的注解,之后这两章内容就会对 Relationship 进行学习

基础概念

这里回顾一下数据库的基础概念,具体的使用方式在后面的笔记中会被提到,所以这里也就不 cv 代码了

  • 主键(Primary Key)& 外键(Foreign Key)

    主键是一个表中的一个或多个列,能够表示当前数据在当前表中的唯一性,每个表只能有一个主键

    比较常见用一个列作为主键的情况有学生 id,课程 id,书籍 id,图书馆 id 等

    用多个列作为主键的情况,一般代表单独的一个列无法完整表达对应信息,如学生选修的课程,这种情况下主键可以使用学生 id+课程 id+年份,或者图书的出借记录,可以使用学生 id+书籍 id+出借日期等

    外键用于引用另一张表的主键,如上面多个列作为主键的情况中,学生 id、书籍 id 都是外键

  • 级联(Cascade)

    Cascade 是一种为了维护数据库完整性的机制,这里不会过多的设计到数据库的 Cascade,而是提一下 hibernate 中的 Cascade 类型,也就是 CascadeType

    • PERSIST 代表当持久化一个实体时,其关联的实体也会被实体化

    • MERGE 代表当更新一个实体时,其关联的实体也会被更新

      准确的说是 merge 操作,不过大多数情况下 merge 操作被用来当做更新

    • REMOVE 代表当删除一个实体时,其关联的实体也会被删除

    • REFRESH 代表当更新一个实体时,其关联的实体也会被更新

    • DETACH 代表当更新一个分离时,其关联的实体也会被分离

      这个主要代表当前的 entity 不再被持久层所管理

    • ALL 代表上述所有的操作都会被执行

    默认情况下,hibernate 不会 cascade 任何操作

  • 数据加载——eager & lazy

    这代表的是两种数据的获取方式,eager 代表当 entity 被获取时,其关联的所有实体也会被同时获取

  • 关系

    • one to one

      即一对一的关系,比如说 国家首都用户用户信息 这种都是比较常见的一对一的关系

    • one to many

      一对多,也是多对一的关系,比较常见的有 作者书籍学生学校

    • many to many

      多对多的关系,这应该是最常见的情况了,比如说 作者出版商学生

    需要注意的是,不同的关系在不同的系统中都会有些微的差异,并非不可改变。比如说以电话系统为例,虽然日常生活中,常见的案例为 一号一人,设计上可能会偏好将 电话号码 以 一对多 的关系进行构建。但是在 ToB 的业务中就需要考虑到公司号码其实会被分拨到不同的客服手上的情况,相对于 ToC 端 one-to-many 的设计,toB 端可能就要进行 many-to-many 的设计修正

  • 实体生命周期

    hibernate session 中的生命周期有 4 个:

    • transient

      new instance,如果没有引用就会被 GC 回收

    • persist

      与 persistence context 关联,这个情况下,hibernate 会对数据的更新进行管理和同步

    • detached

      hibernate 不再对数据进行管理

    • removed

      准备彻底被删除

    geeksforgeeks 上找到了个图描述了一下生命周期:

    [外链图片转存中…(img-U6GRZ8j5-1742269905179)]

    整体来说这个部分还是比较复杂的,简单的几句话很难描述整个生命周期的流程,图里也缺少了一些必要的 op,比如 rollback 之类的,有空再补充一下 hibernate 好了

这章笔记主要过一遍 1-1 的数据关联,以及原始笔记是很久之前写的了,我重新走了一下流程补充了点内容,所以项目名称会有点差异……

one-to-one uni-relational

instructor details
instructor

uni-relational 的关系如上面图所示,即 A 能够找到 B 的关联,但是 B 无法回溯到 A

设置数据库

脚本如下:

DROP SCHEMA IF EXISTS `hb-01-one-to-one-uni`;

CREATE SCHEMA `hb-01-one-to-one-uni`;

use `hb-01-one-to-one-uni`;

SET FOREIGN_KEY_CHECKS = 0;

CREATE TABLE `instructor_detail` (
  `id` int NOT NULL AUTO_INCREMENT,
  `youtube_channel` varchar(128) DEFAULT NULL,
  `hobby` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;


CREATE TABLE `instructor` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) DEFAULT NULL,
  `last_name` varchar(45) DEFAULT NULL,
  `email` varchar(45) DEFAULT NULL,
  `instructor_detail_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_DETAIL_idx` (`instructor_detail_id`),
  CONSTRAINT `FK_DETAIL` FOREIGN KEY (`instructor_detail_id`) REFERENCES `instructor_detail` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;

SET FOREIGN_KEY_CHECKS = 1;

ER 图如下:

在这里插入图片描述

ER 图是 dbeaver(有免费版)根据对应的数据库自动生成的,有图就可以证明 ER 图生成的没问题了

配置项目

新建项目

依旧用 spring initializer 实现:

在这里插入图片描述

main 文件如下:

package com.example.demo;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@Bean
	public CommandLineRunner commandLineRunner(String[] args) {
		return runner -> {
			System.out.println("Hello World!");
		};
	}
}

这样就创建了一个终端 app

properties 文件修改

如下:

# JDBC properties
spring.datasource.url=jdbc:mysql://localhost:3306/hb-01-one-to-one-uni
spring.datasource.username=springstudent
spring.datasource.password=springstudent
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect

# Disable Hibernate usage of JDBC metadata
# not having this can resolve error
spring.jpa.properties.hibernate.boot.allow_jdbc_metadata_access=false

# turn of the banner and lower the logging level
spring.main.banner-mode=off
logging.level.root=warn

运行结果如下:

2025-03-17T17:58:18.452-04:00  WARN 62459 --- [demo-one-to-one-uni] [           main] org.hibernate.orm.deprecation            : HHH90000025: MySQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
Hello World!

Process finished with exit code 0

创建 entity

主要就是两个,InstructorDetail 对应 instructor_detail, Instructor 对应 instructor

  • instructor details

    package com.example.demo.entity;
    
    import jakarta.persistence.*;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    
    @Entity
    @Table(name = "instructor_detail")
    @Data
    @NoArgsConstructor
    @ToString
    public class InstructorDetail {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private int id;
    
        @Column(name = "youtube_channel")
        private String youtubeChannel;
    
        @Column(name = "hobby")
        private String hobby;
    
        public InstructorDetail(String youtubeChannel, String hobby) {
            this.youtubeChannel = youtubeChannel;
            this.hobby = hobby;
        }
    }
    
    
  • instructor

    package com.example.demo.entity;
    
    import jakarta.persistence.*;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.ToString;
    
    @Entity
    @Table(name = "instructor")
    @Data
    @NoArgsConstructor
    @ToString
    public class Instructor {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private int id;
    
        @Column(name = "first_name")
        private String firstname;
    
        @Column(name = "last_name")
        private String lastname;
    
        @Column(name = "email")
        private String email;
    
        // set up mapping to InstructorDetail
        @OneToOne(cascade = CascadeType.ALL)
        @JoinColumn(name = "instructor_detail_id")
        private InstructorDetail instructorDetail;
    
        public Instructor(String firstname, String lastname, String email) {
            this.firstname = firstname;
            this.lastname = lastname;
            this.email = email;
        }
    }
    
    

基本上没什么新的东西,除了下面这段:

      @OneToOne(cascade = CascadeType.ALL)
      @JoinColumn(name = "instructor_detail_id")
      private InstructorDetail instructorDetail;

具体内容最后补充

DAO & DAOimpl

这个的实现就比较简单了,因为只是做 demo,所以没打算分成几个文件去写,所有数据库的操作都会放在这里,并且调用 entityManager 中的方法去实现,而不会用 extend JpaRepository 的方法去实现

  • AppDAO

    package com.example.demo.dao;
    
    import com.example.demo.entity.Instructor;
    
    public interface AppDAO {
        void save (Instructor instructor);
    }
    
    
  • AppDAOImpl

      package com.example.demo.dao;
    
      import com.example.demo.entity.Instructor;
      import jakarta.persistence.EntityManager;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Repository;
    
    
      @Repository
      public class AppDAOImpl implements AppDAO{
          private final EntityManager entityManager;
    
          @Autowired
          public AppDAOImpl(EntityManager entityManager) {
              this.entityManager = entityManager;
          }
    
          @Override
          @Transactional
          public void save(Instructor instructor) {
              // it will also save instructor detail due to cascade
              entityManager.persist(instructor);
          }
      }
    
    

    ⚠️:这个 @Transactional 导入 spring 的就好,导入 jakara 的话,spring boot 就不会管理了

更新一下 main

主要是增加一下 logging 以及输出结果

package com.example.demo;

import com.example.demo.dao.AppDAO;
import com.example.demo.entity.Instructor;
import com.example.demo.entity.InstructorDetail;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@Bean
	public CommandLineRunner commandLineRunner(AppDAO appDAO) {
		return runner -> {
			createInstructor(appDAO);
		};
	}

	private void createInstructor(AppDAO appDAO) {
		// create the instructor
		Instructor instructor = new Instructor("John", "Doe", "johndoe@gmail.com");
		InstructorDetail instructorDetail = new InstructorDetail("http://www.example.com", "Coding");

		// associate the objects
		instructor.setInstructorDetail(instructorDetail);

		// NOTE: this will ALSO save the details object because of CascadeType.ALL
		System.out.println("Saving instructor: " + instructor);
		appDAO.save(instructor);
		System.out.println("Done!");
	}
}

运行结果:

在这里插入图片描述

可以看到,这里具体执行了 2 条 queries,一个是写入 instructor,另一个是写入 instructor_details

数据库截图:

在这里插入图片描述

查询实例

这个操作很简单,主要修改三个文件:

  • DAO

    public interface AppDAO {
        Instructor findInstructorById(int id);
    }
    
  • DAOImpl

    @Repository
    public class AppDAOImpl implements AppDAO{
        @Override
        public Instructor findInstructorById(int id) {
            return entityManager.find(Instructor.class, id);
        }
    }
    
    
  • main

        @Bean
        public CommandLineRunner commandLineRunner(AppDAO appDAO) {
            return runner -> {
                findInstructor(appDAO);
            };
        }
    
        private void findInstructor(AppDAO appDAO) {
            int id = 1;
            System.out.println("Finding instructor id: " + id);
            Instructor instructor = appDAO.findInstructorById(id);
    
            System.out.println("Instructor: " + instructor);
            System.out.println("Associated Instructor Details: " + instructor.getInstructorDetail());
        }
    

效果如下:

在这里插入图片描述

删除实例

也是一样的操作,修改 3 个文件:

  • DAO

    public interface AppDAO {
    
        void deleteInstructorById(int id);
    }
    
  • DAOImpl

        @Override
        @Transactional
        public void deleteInstructorById(int id) {
            Instructor instructor = this.findInstructorById(id);
    
            if (instructor != null) {
                entityManager.remove(instructor);
            }
        }
    
  • main

        @Bean
        public CommandLineRunner commandLineRunner(AppDAO appDAO) {
            return runner -> {
                deleteInstructor(appDAO);
            };
        }
    
        private void deleteInstructor(AppDAO appDAO) {
            int id = 2;
            System.out.println("Deleting instructor id: " + id);
            appDAO.deleteInstructorById(id);
            System.out.println("Done!");
        }
    

结果如下:

在这里插入图片描述

one-to-one bi-directional

这个修改其实没必要动数据库,等到之后捋一遍就明白为什么了

更新查询实例

public class InstructorDetail {
    // ...

    // updated code
    @OneToOne(mappedBy = "instructorDetails", cascade = CascadeType.ALL)
    private Instructor instructor;

    // ...

    // remove @ToString annotation as null field(private Instructor instructor;) will throw error
    @Override
    public String toString() {
        return "InstructorDetail: id = " + this.getId() + ", youtubeChannel: " + this.getYoutubeChannel()
                + ", hobby: " + this.getHobby() + ".";
    }
}

@toString 空值会造成 lombok 的一些问题,在这个 ticket 有提到:@ToString formatting ‘language’. #1297。这里用 getId() 比较合适,否则的话会造成循环调用,形成无止尽的递归

DAO 和 DAOImpl 更新

主要是获取新的 InstructorDetails,代码比较简单:

public interface AppDAO {
    InstructorDetail findInstructorDetailById(int id);
}

    @Override
    public InstructorDetail findInstructorDetailById(int id) {
        return entityManager.find(InstructorDetail.class, id);
    }

更新 main 方法

其实就是新增一个方法,去获取输出通过 findInstructorDetailById 获取的实例:

	@Bean
	public CommandLineRunner commandLineRunner(AppDAO appDAO) {
		return runner -> {
			findInstructorDetail(appDAO);
		};
	}

	private void findInstructorDetail(AppDAO appDAO) {
		// find the instructor detail object
		int id = 1;
		InstructorDetail instructorDetail = appDAO.findInstructorDetailById(id);
		System.out.println("Instructor Detail:" + instructorDetail);

		// print the associated instructor
		System.out.println("Associated instructor: " + instructorDetail.getInstructor());
		System.out.println("Done.");
	}

输出结果

在这里插入图片描述

删除实例更新

  • DAO

    void deleteInstructorDetailById(int id);
    
  • DAO Impl

        @Override
        @Transactional
        public void deleteInstructorDetailById(int id) {
            InstructorDetail instructorDetail = this.findInstructorDetailById(id);
    
            if (instructorDetail != null) {
                entityManager.remove(instructorDetail);
            }
        }
    
  • main

      @Bean
      public CommandLineRunner commandLineRunner(AppDAO appDAO) {
        return runner -> {
          deleteInstructorDetail(appDAO);
        };
      }
    
      private void deleteInstructorDetail(AppDAO appDAO) {
        int id = 1;
        System.out.println("Deleting instructor id: " + id);
        appDAO.deleteInstructorDetailById(id);
        System.out.println("Done!");
      }
    

结果:

在这里插入图片描述

只删除 instructor detail 但是不删除 instructor

这个方式可以通过修改 CascadeType 去实现,如:

    @OneToOne(mappedBy = "instructorDetail", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
    private Instructor instructor;

修改 DAO Impl 去手动移除关联:

    @Override
    @Transactional
    public void deleteInstructorDetailById(int id) {
        InstructorDetail instructorDetail = this.findInstructorDetailById(id);

        // remove the associated object reference, break the bi-directional link
        instructorDetail.getInstructor().setInstructorDetail(null);

        entityManager.remove(instructorDetail);
    }

最后运行结果:

在这里插入图片描述

在这里插入图片描述

建立关联的注解

@JoinColumn

这个代表的是当前属性为 foreign key,并且可以通过当前的 foreign key 寻找到对应的实例

需要注意的是, @JoinColumn 无法单独使用,必须要搭配对应的关系——@OneToOne@OneToMany@ManyToMany 才能够正确工作

@OneToOne

这个注解只代表了当前的属性与当前的 entity 存在 1-to-1 的对应关系,参考两种用法:

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "instructor_detail_id")
    private InstructorDetail instructorDetail;

// =====================================================

    @OneToOne(mappedBy = "instructorDetails", cascade = CascadeType.ALL)
    private Instructor instructor;

前者可以直接通过 foreign key 寻找对应的关系,写成 query 大体如下:

select * from instructor_detail where id = 1;

后者的 query 大体如下:

select * from instructor i join instructor_detail d on i.instructor_detail_id  = d.id where i.id = 1;

对比起来的话,前者因为直接用 foreign key 去找,不用调用一个 join,所以表现上会稍微快一些

reference

  • mysql 权限问题

    这个取决于 properties 文件中使用的用户,如果不是 root,那么可能就会有无法访问的问题,这个时候跑一下下面的脚本就行了,用户名和 db 名称用数据库中的代替:

    mysql -u root -p
    USE dbname;
    
    # 如果用户不存在,直接 grant 会报错
    CREATE USER 'username'@'%' IDENTIFIED BY 'your_password';
    GRANT ALL PRIVILEGES ON `hb-01-one-to-one-uni`.* TO 'username'@'%';
    
    FLUSH PRIVILEGES;
    
    SHOW GRANTS FOR 'username'@'%';
    
  • properties 文件配置

    来自官方的 repo: spring-lifecycle-smoke-tests

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

鸿蒙Next开发实战教程—电影app

最近忙忙活活写了不少教程,但是总感觉千篇一律,没什么意思,大家如果有感兴趣的项目可以私信给幽蓝君写一写。 今天分享一个电影App。 这个项目也比较简单,主要是一些简单页面的开发和本地视频的播放以及横竖屏切换。 页面搭建以…

停车场停车位数据集,标注停车位上是否有车,平均正确识别率99.5%,支持yolov5-11, coco json,darknet,xml格式标注

停车场停车位数据集,标注停车位上是否有车,平均正确识别率98.0%,支持yolov5-11, coco json,darknet,xml格式标注 数据集-识别停车场所有车辆的数据集 数据集分割 一共184张图片 训练组 89&am…

ssm框架之mybatis框架讲解

1,Mybatis 1.1 Mybatis概述 1.1.1 Mybatis概念 MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2…

CEF 多进程模式时,注入函数,获得交互信息

CEF 控制台添加一函数,枚举 注册的供前端使用的CPP交互函数有哪些-CSDN博客 上篇文章,是在模拟环境,单进程中设置的,这篇文章,将其改到正常多进程环境中设置。 对应于工程中的 CEF_RENDER项目 一、多进程模式中,改写 修改步骤 1、注入函数 client_app_render.cpp 在…

Androidstudio出现警告warning:意外的元素

这些警告信息通常与 Android SDK 或系统镜像的配置文件有关,可能是由于 SDK 工具或系统镜像的版本不兼容或配置文件格式发生了变化。以下是解决这些警告的步骤: 1. 更新 Android SDK 工具 确保你使用的是最新版本的 Android SDK 工具: 打开…

深入了解Linux —— git三板斧

版本控制器git 为了我们方便管理不同版本的文件,就有了版本控制器; 所谓的版本控制器,就是能够了解到一个文件的历史记录(修改记录);简单来说就是记录每一次的改动和版本迭代的一个管理系统,同…

【软件系统架构】单体架构

一、引言 在软件开发的漫长历程中,架构的选择一直是至关重要的决策。单体架构作为一种经典的架构模式,曾经在许多项目中发挥着不可替代的作用。虽然如今微服务等架构逐渐流行,但理解单体架构对于深入掌握软件架构体系仍然有着重要意义。 二、…

【求助】【建议放弃】【谷粒商城版】Kubernetes

本文作者: slience_me 文章目录 Kubernetes【谷粒商城版】【建议放弃】1. docker安装2. kubernetes安装前3. kubeadm,kubelet,kubectl3.1 简介kubeadmkubeletkubectl常用指令 3.2 安装3.3 kubeadm初始化3.4 加入从节点(工作节点)3.5 安装Pod网络插件(CNI…

C# Unity 唐老狮 No.10 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: Unity课程 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho C# 1. 内存中,堆和…

第十五届蓝桥杯2024JavaB组省赛试题A:报数游戏

简单的找规律题目。题目给得数列,第奇数项是20的倍数,第偶数项时24的倍数。题目要求第n 202420242024 项是多少。这一项是偶数,所以答案一定是24的倍数,并且偶数项的个数和奇数项的个数各占一半,所以最终的答案ans( n…

Matlab 汽车二自由度转弯模型

1、内容简介 Matlab 187-汽车二自由度转弯模型 可以交流、咨询、答疑 2、内容说明 略 摘 要 本文前一部分提出了侧偏角和横摆角速度作为参数。描述了车辆运动的运动状态,其中文中使用的参考模型是二自由度汽车模型。汽车速度被认为是建立基于H.B.Pacejka的轮胎模…

学c++的人可以几天速通python?

学了俩天啊,文章写纸上了 还是蛮有趣的

Rocky Linux 9.x 基于 kubeadm部署k8s 1.32

一、部署说明 1、主机操作系统说明 序号操作系统及版本备注1Rocky Linux release 9下载链接:https://mirrors.163.com/rocky/9.5/isos/x86_64/Rocky-9.5-x86_64-minimal.iso 2、主机硬件配置说明 作用IP地址操作系统配置关键组件k8s-master01192.168.234.51Rocky…

解决git init 命令不显示.git

首先在自己的项目代码右击 打开git bash here 输入git init 之后自己的项目没有.git文件,有可能是因为.git文件隐藏了,下面是解决办法

利用AI让数据可视化

1. 从问卷星上下载一份答题结果。 序号用户ID提交答卷时间所用时间来源来源详情来自IP总分1、《中华人民共和国电子商务法》正式实施的时间是()。2、()可以判断企业在行业中所处的地位。3、()是指店铺内有…

解决qt中自定插件加载失败,不显示问题。

这个问题断断续续搞了一天多,主要是版本不匹配问题。 我们先来看下 Based on Qt 6.6.0 → 说明 Qt Creator 本身 是基于 Qt 6.6.0 框架构建的。MSVC 2019, 64-bit → 说明 Qt Creator 是使用 Microsoft Visual C 2019 编译器(64 位) 编译的。…

智慧社区3.0

项目介绍: 此项目旨在推动成都市探索**超大城市社区发展治理新路**,由三个实验室负责三大内容 1、**研发社区阵地空间管理模块**:AI算法实现态势感知(如通过社区图片和视频、文本,对环境 空间质量、绿视率、安全感分…

Springboot+Vue登录、注册功能(含验证码)(后端!)

我们首先写一个接口,叫login!然后对传入一个user,因为我们前端肯定是要传过来一个user,然后我们后端返回一个user,因为我们要根据这个去校验!我们还引入了一个hutool的一个东西,在pom文件里面引…

搞定python之八----操作mysql

本文是《搞定python》系列文章的第八篇,讲述利用python操作mysql数据库。相对来说,本文的综合性比较强,包含了操作数据库、异常处理、元组等内容,需要结合前面的知识点。 1、安装mysql模块 PyMySql模块相当于数据库的驱动&#…

LVGL 中设置 UI 层局部透明,显示下方视频层

LVGL层次 LVGL自上而下分别是layer_sys > layer_top > lv_sreen_active > layer_bottom 即 系统层、顶层、活动屏幕、底层 原理 如果将UI设置为局部透明,显示下方的视频层,不仅仅需要将当前活动屏幕的背景设置为透明,还需要将底层…