• SpringDataJPA(三):多表操作,复杂查询

SpringDataJPA(三):多表操作,复杂查询

2025-04-26 06:00:17 2 阅读

一、Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

/**
 *	JpaSpecificationExecutor中定义的方法列表
 **/
 public interface JpaSpecificationExecutor {
   	// 根据条件查询一个对象
 	T findOne(Specification spec);
   	// 根据条件查询集合
 	List findAll(Specification spec);
   	// 根据条件分页查询
 	Page findAll(Specification spec, Pageable pageable);
   	// 排序查询
 	List findAll(Specification spec, Sort sort);
   	// 统计查询
 	long count(Specification spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

Specification接口中只定义了如下一个方法:

 	//构造查询条件
    /**
    *	root	:Root接口,代表查询的根对象,可以通过root获取实体中的属性
    *	query	:代表一个顶层查询对象,用来自定义查询方式(一般不用)
    *	cb		:用来构建查询,此对象里封装有很多查询条件方法
    **/
    public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
栗子

srcmainjava oponefinedaoCustomerDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.Customer;

public interface CustomerDao extends JpaRepository, JpaSpecificationExecutor {
}

src estjava oponefinedaoCustomerDaoTest.java:

package top.onefine.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.onefine.domain.Customer;

import javax.persistence.criteria.*;

import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerDaoTest {

    @SuppressWarnings("All")
    @Autowired
    private CustomerDao customerDao;

    /**
     * 单条件精确查询demo——自定义查询条件
     *      1. 实现Specification接口,需要提供的泛型是查询的对象类型
     *      2. 实现接口中的toPredicate方法,用于构造查询条件
     *      3. 需要借助方法参数中的两个参数
     *          - Root: 获取需要查询的对象属性
     *          - CriteriaBuilder:用于构造查询条件,内部封装了很多的查询条件(模糊匹配,精准匹配)
     */
    @Test
    public void testFindOne() {
        // 匿名内部类
//        Specification specification = new Specification() {
//            @Override
//            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
//                // 根据客户名称查询
//                // 1. 获取比较的属性
//                Path custName = root.get("custName");  // Root对象中获取用于比较的属性名称
//                // 2. 构造查询条件    select * from cst_customer where cust_name = "one fine"
//                //      equal表示进行精准匹配,第一个参数表示需要比较的属性(Path对象),第二个参数表示比较属性的取值
//                @SuppressWarnings("")
//                Predicate predicate = criteriaBuilder.equal(custName, "one fine");// CriteriaBuilder对象中构造查询方式
//                return predicate;
//            }
//        };
//        Customer customer = customerDao.findOne(specification);

        // Lambda简化
        Customer customer = customerDao.findOne((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("custName"), "one fine"));
        System.out.println(customer);
    }

    /** 多条件精确查询demo——根据客户名和客户所属行业查询
     */
    @Test
    public void testFindOne_() {
        Customer customer = customerDao.findOne((root, criteriaQuery, criteriaBuilder) -> {
            // 1. 构造客户名的精准匹配查询
            Predicate predicate1 = criteriaBuilder.equal(root.get("custName"), "one fine");
            // 2. 构造所属行业的精准匹配查询
            Predicate predicate2 = criteriaBuilder.equal(root.get("custIndustry"), "软件");
            // 3. 将以上两个查询联系起来:组合
            //  - 与关系:and   交集
            //  - 或关系:or    并集
            return criteriaBuilder.and(predicate1, predicate2);
        });
        System.out.println(customer);
    }

    /**
     * 模糊匹配,根据客户名称模糊匹配,返回客户列表
     *  - equal:直接得到Path对象(属性),然后进行比较即可
     *  - gt, lt, ge, le, like...:得到Path对象,根据Path指定需要比较的参数类型,再去进行比较
     *      指定参数类型: path.as(类型的字节码对象)
     */
    @Test
    public void testFindAll() {
//        Specification specification = new Specification() {
//            @Override
//            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
//                Path custName = root.get("custName");
//                Expression custNameExpression = custName.as(String.class);
//                @SuppressWarnings("")
//                Predicate predicate = criteriaBuilder.like(custNameExpression, "one fine");  // 查询方式:模糊匹配
//                return predicate;
//            }
//        };
//        List customers = customerDao.findAll(specification);

//        List customers = customerDao.findAll((Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) ->
//            criteriaBuilder.like(root.get("custName").as(String.class), "one fine"));
        List customers = customerDao.findAll((root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.like(root.get("custName").as(String.class), "%fine%"));  // 包含"fine"的
        for (Customer customer : customers)
            System.out.println(customer);
    }

    /**
     * 排序
     */
    @Test
    public void testFindAll_desc() {

        // 创建排序对象
        // 第一个参数:排序的顺序
        // - Sort.Direction.DESC 倒序
        // - Sort.Direction.ASC 升序
        // 第二个参数:排序的属性名称
        Sort sort = new Sort(Sort.Direction.DESC, "custId");  // 查询结果按照id倒序
        // 添加排序
        List customers = customerDao.findAll((root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.like(root.get("custName").as(String.class), "one fine"), sort);
        for (Customer customer : customers)
            System.out.println(customer);
    }

    /**
     * 分页查询
     *  - findAll(Specification, Pageable)  带条件的分页
     *  - findAll(Pageable) 不带条件的分页
     * 返回Page对象,是SpringDataJPA封装好的pageBean对象,可以从中获取到数据列表和总条数等
     */
    @Test
    public void testFindAll_page() {
        Specification specification = null;  // 无查询条件
        // Pageable:分页参数
        // PageRequest对象是Pageable接口的实现类
        //  - 第一个参数:查询的页码(从0开始)
        //  - 第二个参数:每页查询的记录条数

        Pageable pageable = new PageRequest(1, 2);  // 第2页(0开始编号),每页显示2个数据

        Page customerPage = customerDao.findAll(specification, pageable);  // 无查询条件
        System.out.println("" + customerPage.getTotalElements() + "
" +  // 记录总数
                            "" + customerPage.getTotalPages() + "
" +  // 总页数
                            "" + customerPage.getSize() + "
" +  // 分页大小
                            "" + customerPage.getContent());  // 结果列表

    }

    /**
     * 统计查询
     */
    @Test
    public void testCount() {

        long count = customerDao.count((root, criteriaQuery, criteriaBuilder) ->
                criteriaBuilder.like(root.get("custName").as(String.class), "%fine"));  // 以"fine"结束的
        System.out.println(count);
    }
}
 

二、多表设计

2.1 表之间关系的划分

数据库中多表之间存在着三种关系,如图所示。

从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一(一般不使用)关系。注意:一对多关系可以看为两种: 即一对多,多对一;所以说四种更精确。

一对多关系中:习惯将一的一方称作主表,将多的一方称之为从表。用外键描述这种关系,需要在从表中新建一个属性作为外键,其取值来源于主表的主键。

多对多关系中:用中间表(第三章表)来描述这种关系,中间表中至少应该由两个字段组成,这两个字段作为外键指向两张表的主键,且这两个字段又组成了联合主键。

明确:
这里只涉及实际开发中常用的关联关系,一对多和多对多。而一对一的情况,在实际开发中几乎不用。

实体类中的关系:

  • 包含关系:可以通过实体类中的包含关系描述表关系(一对一、一对多、多对一、多对多)
  • 继承关系
2.2 在JPA框架中表关系的分析步骤

在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以今天我们的学习重点是:掌握配置实体之间的关联关系。

第一步:首先确定两张表之间的关系。

如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系(用外键或中间表描述)

第三步:在实体类中描述出两个实体的关系(实体类的包含关系)

第四步:配置出实体类和数据库表的关系映射(重点)

三、JPA中的一对多

3.1 示例分析

我们采用的示例为客户和联系人。

客户:指的是一家公司,我们记为A。

联系人:指的是A公司中的员工。

在不考虑兼职的情况下,公司和员工的关系即为一对多。即一个客户具有多个联系人,一个联系人从属于一家公司。

3.2 表关系建立

在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。

什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。

一对多数据库关系的建立,如下图所示:

   

这里一对多的栗子中,主表是客户表,从表是联系人表(需要添加外键)。

/*创建客户表*/
CREATE TABLE cst_customer (
  cust_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
  cust_name varchar(32) NOT NULL COMMENT '客户名称(公司名称)',
  cust_source varchar(32) DEFAULT NULL COMMENT '客户信息来源',
  cust_industry varchar(32) DEFAULT NULL COMMENT '客户所属行业',
  cust_level varchar(32) DEFAULT NULL COMMENT '客户级别',
  cust_address varchar(128) DEFAULT NULL COMMENT '客户联系地址',
  cust_phone varchar(64) DEFAULT NULL COMMENT '客户联系电话',
  PRIMARY KEY (`cust_id`)
) ENGINE=InnoDB AUTO_INCREMENT=94 DEFAULT CHARSET=utf8;

/*创建联系人表*/
CREATE TABLE cst_linkman (
  lkm_id bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)',
  lkm_name varchar(16) DEFAULT NULL COMMENT '联系人姓名',
  lkm_gender char(1) DEFAULT NULL COMMENT '联系人性别',
  lkm_phone varchar(16) DEFAULT NULL COMMENT '联系人办公电话',
  lkm_mobile varchar(16) DEFAULT NULL COMMENT '联系人手机',
  lkm_email varchar(64) DEFAULT NULL COMMENT '联系人邮箱',
  lkm_position varchar(16) DEFAULT NULL COMMENT '联系人职位',
  lkm_memo varchar(512) DEFAULT NULL COMMENT '联系人备注',
  lkm_cust_id bigint(32) NOT NULL COMMENT '客户id(外键)',
  PRIMARY KEY (`lkm_id`),
  KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
  CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
3.3 实体类关系建立以及映射配置

要点:

客户:在客户的实体类中包含一个联系人的集合
联系人:在联系人的实体类中包含有一个客户的对象

在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息,代码如下

package top.onefine.domain;

import lombok.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * 1. 实体类和表的映射关系
 *      -@Eitity
 *      -@Table
 * 2. 类中属性和数据库表中字段的映射关系
 *      -@Id 主键
 *      -@GeneratedValue 主键生成策略
 *      -@Column
 */
@Entity
@Table(name = "cst_customer")
@Data  // 使用@Getter和@Setter,但是要自己生成toString方法
//@Getter
//@Setter
@NoArgsConstructor
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;
    @Column(name = "cust_address")
    private String custAddress;
    @Column(name = "cust_industry")
    private String custIndustry;
    @Column(name = "cust_level")
    private String custLevel;
    @Column(name = "cust_name")
    private String custName;
    @Column(name = "cust_phone")
    private String custPhone;
    @Column(name = "cust_source")
    private String custSource;

    // 配置客户和联系人之间的关系(一对多关系)
    /*
        使用注解的形式配置多表关系:
            1. 声明关系
                - @OneToMany:配置一对多关系
                    targetEntity:对方对象的字节码对象
            2. 配置外键(或中间表)
                - @JoinColumn:配置外键
                    name:从表 外键字段名称
                    referencedColumnName:参照的 主表 的主键字段名称

        注:在客户实体类上(一的一方)添加了外键的配置,所以对于客户而言,也具备了维护外键的作用

     */
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    /*
        放弃外键维护权
            mappedBy: 对方配置关系的属性名称
                表示参照对方的属性来做
     */
//    @OneToMany(mappedBy = "customer")
    private Set linkMans = new HashSet<>();

    // 一定要自己写,不要使用lombok提供的
//    @Override
//    public String toString() {
//        return "Customer{" +
//                "custId=" + custId +
//                ", custAddress='" + custAddress + ''' +
//                ", custIndustry='" + custIndustry + ''' +
//                ", custLevel='" + custLevel + ''' +
//                ", custName='" + custName + ''' +
//                ", custPhone='" + custPhone + ''' +
//                ", custSource='" + custSource + ''' +
//                ", linkMans=" + linkMans +
//                '}';
//    }
}

由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户,代码如下:

package top.onefine.domain;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Entity
@Table(name = "cst_linkman")
@Data
@NoArgsConstructor
public class LinkMan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId;  // 联系人编号
    @Column(name = "lkm_name")
    private String lkmName;  // 联系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender;  // 联系人性别  // 对应数据库表中字段char(1)
    @Column(name = "lkm_phone")
    private String lkmPhone;  // 联系人办公电话
    @Column(name = "lkm_mobile")
    private String lkmMobile;  // 联系人手机
    @Column(name = "lkm_email")
    private String lkmEmail;  // 联系人邮箱
    @Column(name = "lkm_position")
    private String lkmPosition;  // 联系人职位
    @Column(name = "lkm_memo")
    private String lkmMemo;  // 联系人备注

    // 配置联系人到客户的多对一关系
    /*
        使用注解的形式配置多对一关系
            1. 配置表关系
                - @ManyToOne:配置多对一关系
                    targetEntity:对方对象的字节码对象
            2. 配置外键(或中间表)

        注:配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     */
    @ManyToOne(targetEntity = Customer.class)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    private Customer customer;
}
其他配置

pom.xml:



    4.0.0

    top.onefine
    jpa_day3_one2mant
    1.0-SNAPSHOT
    jar

    
        UTF-8


        5.1.17.Final
        5.2.5.RELEASE
        1.7.30
        1.2.17
        0.9.5.5
        8.0.19
    

    
        
        
            junit
            junit
            4.13
            test
        

        
        
        
            org.aspectj
            aspectjweaver

            1.6.8
        

        
        
            org.springframework
            spring-aop
            ${project.spring.version}
        

        
        
            org.springframework
            spring-context
            ${project.spring.version}
        

        
        
            org.springframework
            spring-context-support
            ${project.spring.version}
        

        
        
        
            org.springframework
            spring-orm
            ${project.spring.version}
        

        
        
            org.springframework
            spring-beans
            ${project.spring.version}
        

        
        
            org.springframework
            spring-core
            ${project.spring.version}
        

        
        
            org.hibernate
            hibernate-core
            ${project.hibernate.version}
        

        
        
            org.hibernate
            hibernate-entitymanager
            ${project.hibernate.version}
        

        
        
            org.hibernate
            hibernate-validator
            
            5.4.3.Final
        

        
        
            log4j
            log4j
            ${project.log4j.version}
        

        
        
            org.slf4j
            slf4j-api
            ${project.slf4j.version}
        

        
        
            org.slf4j
            slf4j-log4j12
            ${project.slf4j.version}
            test
        

        
        
            com.mchange
            c3p0
            ${project.c3p0.version}
        

        
        
        
            mysql
            mysql-connector-java
            ${project.mysql.version}
        

        
        
        
            org.springframework.data
            spring-data-jpa
            
            
            1.11.23.RELEASE
        

        
        
        
            org.springframework
            spring-test
            ${project.spring.version}
            test
        

        
        
        
            javax.el
            javax.el-api
            2.2.4
        

        
        
            org.glassfish.web
            javax.el
            2.2.4
        

        
        
            org.projectlombok
            lombok
            1.18.12
            provided
        

    


srcmain esourcespplicationContext.xml:




    
    
    
        
        

        
        

        
        
            
        

        
        
            
                
                
                
                
                
                
                
                
            
        

        
        
            
        

        
        
            
                create
            
        
    

    
    
        
        
        
        
        

        
        
        
        
    

    
    

    
    
        
    

    
    
        
            
            
            
            
            
            
            
        
    

    
    
        
        
    

    

    
    

srcmainjava oponefinedaoCustomerDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.Customer;

public interface CustomerDao extends JpaRepository, JpaSpecificationExecutor {
}

srcmainjava oponefinedaoLinkManDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.LinkMan;

/**
 * 联系人dao接口
 */
public interface LinkManDao extends JpaRepository, JpaSpecificationExecutor {
}
3.4 映射的注解说明
	@OneToMany:
   	作用:建立一对多的关系映射
    属性:
    	targetEntityClass:指定多的多方的类的字节码
    	mappedBy:指定从表实体类中引用主表对象的名称。
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	orphanRemoval:是否使用孤儿删除

	@ManyToOne
    作用:建立多对一的关系
    属性:
    	targetEntityClass:指定一的一方实体类字节码
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

	@JoinColumn
	作用:用于定义主键字段和外键字段的对应关系。
	属性:
		name:指定外键字段的名称
		referencedColumnName:指定引用主表的主键字段名称
		unique:是否唯一。默认值不唯一
		nullable:是否允许为空。默认值允许。
		insertable:是否允许插入。默认值允许。
		updatable:是否允许更新。默认值允许。
		columnDefinition:列的定义信息。
3.5 一对多的操作
3.5.1 添加
1 客户和联系人作为独立的数据保存到数据库中:
package top.onefine.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import top.onefine.domain.Customer;
import top.onefine.domain.LinkMan;



@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class One2ManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     */
    @Test
    @Transactional  // 配置事务
    @Rollback(value = false)  // 设置不自动回滚
    public void testAdd() {
        Customer customer = new Customer();
        customer.setCustName("测试");
        customer.setCustLevel("重要");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("one fine");
       
        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
}

效果:

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
Hibernate: drop table if exists cst_customer
Hibernate: drop table if exists cst_linkman
Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

2 配置了客户到联系人的关系:

@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class One2ManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     */
    @Test
    @Transactional  // 配置事务
    @Rollback(value = false)  // 设置不自动回滚
    public void testAdd() {
        Customer customer = new Customer();
        customer.setCustName("测试");
        customer.setCustLevel("重要");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("one fine");
        /*
            配置了客户到联系人的关系:
                从客户的角度上,发送两条insert语句,发送一条更新语句更新数据库(更新外键)
                    由于配置了客户到联系人的关系:客户可以对外键进行维护
         */
        customer.getLinkMans().add(linkMan);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

}

效果

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
Hibernate: drop table if exists cst_customer
Hibernate: drop table if exists cst_linkman
Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

3 配置了联系人到客户的关系:

@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class One2ManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     */
    @Test
    @Transactional  // 配置事务
    @Rollback(value = false)  // 设置不自动回滚
    public void testAdd() {
        Customer customer = new Customer();
        customer.setCustName("测试");
        customer.setCustLevel("重要");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("one fine");
        /*
            配置联系人到客户的关系(多对一)
                只发送了两条insert语句:由于配置了联系人到客户的映射关系(多对一)
         */
        linkMan.setCustomer(customer);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }

}

效果:

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
Hibernate: drop table if exists cst_customer
Hibernate: drop table if exists cst_linkman
Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

4 双向绑定:

@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class One2ManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     */
    @Test
    @Transactional  // 配置事务
    @Rollback(value = false)  // 设置不自动回滚
    public void testAdd() {
        Customer customer = new Customer();
        customer.setCustName("测试");
        customer.setCustLevel("重要");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("one fine");

        /*
            由于一的一方可以维护外键,会发送一条多余update语句
                解决此问题,只需要在一的一方放弃维护权即可
                    @OneToMany(mappedBy = "customer")
         */
        customer.getLinkMans().add(linkMan);  // 发送一条update语句
        linkMan.setCustomer(customer);

        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
}

直接执行:

这里踩了个坑:

在进行一对多配置后,在测试方法中尝试使用获取一方信息,结果出现了内存溢出的错误。

总结一下原因以及解决方案:

原因一:为了方便看信息,在两类中分别重写了 toString 方法,导致查询加载时两类在互相调用对方的toString,形成递归,造成内存溢出。
解决方案: 在 toString 方法中任意一方去除打印的对方信息。

原因二: 为了编写方便简洁,代码更加优雅,使用了 lombok 插件中的@Data以及@ToString注解来标注类,让 lombok 来代替生成 gettet/setter 以及 toString,但是 lombok 在生成时会出现循环比较两类中的 hashcode,导致内存溢出。
解决方案: 不要使用 lombok ,自己手写。

参考: https://blog.csdn.net/weixin_43464964/article/details/90669843

更改:srcmainjava oponefinedomainCustomer.java

package top.onefine.domain;

import lombok.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * 1. 实体类和表的映射关系
 *      -@Eitity
 *      -@Table
 * 2. 类中属性和数据库表中字段的映射关系
 *      -@Id 主键
 *      -@GeneratedValue 主键生成策略
 *      -@Column
 */
@Entity
@Table(name = "cst_customer")
//@Data  // 使用@Getter和@Setter,但是要自己生成toString方法
@Getter
@Setter
@NoArgsConstructor
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;
    @Column(name = "cust_address")
    private String custAddress;
    @Column(name = "cust_industry")
    private String custIndustry;
    @Column(name = "cust_level")
    private String custLevel;
    @Column(name = "cust_name")
    private String custName;
    @Column(name = "cust_phone")
    private String custPhone;
    @Column(name = "cust_source")
    private String custSource;

    // 配置客户和联系人之间的关系(一对多关系)
    /*
        使用注解的形式配置多表关系:
            1. 声明关系
                - @OneToMany:配置一对多关系
                    targetEntity:对方对象的字节码对象
            2. 配置外键(或中间表)
                - @JoinColumn:配置外键
                    name:从表 外键字段名称
                    referencedColumnName:参照的 主表 的主键字段名称

        注:在客户实体类上(一的一方)添加了外键的配置,所以对于客户而言,也具备了维护外键的作用

     */
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    /*
        放弃外键维护权
            mappedBy: 对方配置关系的属性名称
                表示参照对方的属性来做
     */
//    @OneToMany(mappedBy = "customer")
    private Set linkMans = new HashSet<>();

    // 一定要自己写,不要使用lombok提供的
    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custAddress='" + custAddress + ''' +
                ", custIndustry='" + custIndustry + ''' +
                ", custLevel='" + custLevel + ''' +
                ", custName='" + custName + ''' +
                ", custPhone='" + custPhone + ''' +
                ", custSource='" + custSource + ''' +
                ", linkMans=" + linkMans +
                '}';
    }
}

重新执行,效果:

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
Hibernate: drop table if exists cst_customer
Hibernate: drop table if exists cst_linkman
Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: update cst_linkman set lkm_cust_id=? where lkm_id=?

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权,在srcmainjava oponefinedomainCustomer.java中:

	// 放弃外键维护权的配置将如下配置改为
//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    /*
        放弃外键维护权
            mappedBy: 对方配置关系的属性名称
                表示参照对方的属性来做
     */
     // 设置为
    @OneToMany(mappedBy = "customer")
    private Set linkMans = new HashSet<>();

重新执行效果:

Hibernate: alter table cst_linkman drop foreign key FKh9yp1nql5227xxcopuxqx2e7q
Hibernate: drop table if exists cst_customer
Hibernate: drop table if exists cst_linkman
Hibernate: create table cst_customer (cust_id bigint not null auto_increment, cust_address varchar(255), cust_industry varchar(255), cust_level varchar(255), cust_name varchar(255), cust_phone varchar(255), cust_source varchar(255), primary key (cust_id))
Hibernate: create table cst_linkman (lkm_id bigint not null auto_increment, lkm_email varchar(255), lkm_gender varchar(255), lkm_memo varchar(255), lkm_mobile varchar(255), lkm_name varchar(255), lkm_phone varchar(255), lkm_position varchar(255), lkm_cust_id bigint, primary key (lkm_id))
Hibernate: alter table cst_linkman add constraint FKh9yp1nql5227xxcopuxqx2e7q foreign key (lkm_cust_id) references cst_customer (cust_id)
Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
3.5.2 删除
	@Autowired
	private CustomerDao customerDao;
	
	@Test
	@Transactional
	@Rollback(false)//设置为不回滚
	public void testDelete() {
		customerDao.delete(1l);
	}

删除操作的说明如下:

  • 删除从表数据:可以随时任意删除。

  • 删除主表数据:

    有从表数据
    1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
    2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
    3、如果还想删除,使用级联删除引用

    没有从表数据引用:随便删

在实际开发中,级联删除请慎用!(在一对多的情况下)

3.5.3 级联操作

级联操作:指操作一个对象同时操作它的关联对象。有级联删除、级联更新、级联添加…

  • 级联添加:保存客户的时候同时保存联系人
  • 级联删除:删除客户的时候同时删除此客户的所有联系人

使用方法:只需要在操作主体的实体类上添加级联属性——需要添加到多表映射关系的注解上,即注解上配置cascade(作用:配置级联)

注意区分操作主体

/**
 * cascade:配置级联操作
 * 		CascadeType.MERGE	级联更新
 * 		CascadeType.PERSIST	级联保存:
 * 		CascadeType.REFRESH 级联刷新:
 * 		CascadeType.REMOVE	级联删除:
 * 		CascadeType.ALL		包含以上所有
 */
@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)
级联添加:

srcmain esourcespplicationContext.xml中:

	
    	
            
                update
            
        
    

srcmainjava oponefinedomainCustomer.java中:

	/*
        放弃外键维护权
            mappedBy: 对方配置关系的属性名称
                表示参照对方的属性来做

            cascade:配置级联操作,可以配置到设置多表的映射关系的注解上
            * 		CascadeType.MERGE	级联更新
            * 		CascadeType.PERSIST	级联保存:
            * 		CascadeType.REFRESH 级联刷新:
            * 		CascadeType.REMOVE	级联删除:
            * 		CascadeType.ALL		包含以上所有,推荐配置
     */
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private Set linkMans = new HashSet<>();
	// 级联添加:保存一个客户的同时,保存该客户的所有联系人
    // 需要在操作主体的实体类上,配置casacde属性
    @Test
    @Transactional  // 配置事务
    @Rollback(false)  // 不自动回滚
    public void testCascadeAdd() {
        Customer customer = new Customer();
        customer.setCustName("百度1");
        LinkMan linkMan1 = new LinkMan();
        linkMan1.setLkmName("小王1");
        LinkMan linkMan2 = new LinkMan();
        linkMan2.setLkmName("小王2");

        customer.getLinkMans().add(linkMan1);
        customer.getLinkMans().add(linkMan2);
        linkMan1.setCustomer(customer);
        linkMan2.setCustomer(customer);

        customerDao.save(customer);  // 操作的主体是Customer
    }

效果:

Hibernate: insert into cst_customer (cust_address, cust_industry, cust_level, cust_name, cust_phone, cust_source) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into cst_linkman (lkm_cust_id, lkm_email, lkm_gender, lkm_memo, lkm_mobile, lkm_name, lkm_phone, lkm_position) values (?, ?, ?, ?, ?, ?, ?, ?)

级联删除:

	// 级联删除:删除2号客户(上个级联添加添加的数据)的同时,删除2号客户的所有联系人
    // 需要在操作主体的实体类上,配置casacde属性
    @Test
    @Transactional  // 配置事务
    @Rollback(false)  // 不自动回滚
    public void testCascadeRemove() {
        // 1. 查询客户
        Customer customer = customerDao.findOne(2L);
        // 2. 删除2号客户
        customerDao.delete(customer);
    }

效果:

Hibernate: select customer0_.cust_id as cust_id1_0_0_, customer0_.cust_address as cust_add2_0_0_, customer0_.cust_industry as cust_ind3_0_0_, customer0_.cust_level as cust_lev4_0_0_, customer0_.cust_name as cust_nam5_0_0_, customer0_.cust_phone as cust_pho6_0_0_, customer0_.cust_source as cust_sou7_0_0_ from cst_customer customer0_ where customer0_.cust_id=?
Hibernate: select linkmans0_.lkm_cust_id as lkm_cust9_1_0_, linkmans0_.lkm_id as lkm_id1_1_0_, linkmans0_.lkm_id as lkm_id1_1_1_, linkmans0_.lkm_cust_id as lkm_cust9_1_1_, linkmans0_.lkm_email as lkm_emai2_1_1_, linkmans0_.lkm_gender as lkm_gend3_1_1_, linkmans0_.lkm_memo as lkm_memo4_1_1_, linkmans0_.lkm_mobile as lkm_mobi5_1_1_, linkmans0_.lkm_name as lkm_name6_1_1_, linkmans0_.lkm_phone as lkm_phon7_1_1_, linkmans0_.lkm_position as lkm_posi8_1_1_ from cst_linkman linkmans0_ where linkmans0_.lkm_cust_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_linkman where lkm_id=?
Hibernate: delete from cst_customer where cust_id=?

四、JPA中的多对多

4.1 示例分析

采用的示例为用户和角色。

  • 用户:指的是咱们班的每一个同学。

  • 角色:指的是咱们班同学的身份信息。

比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。

同时B同学,它也具有学生和子女的身份。

那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。

所以我们说,用户和角色之间的关系是多对多。

分析步骤

1.明确表关系:多对多关系
2.确定表关系(描述 外键|中间表):中间间表
3.编写实体类,再实体类中描述表关系(包含关系)

  • 用户:包含角色的集合
  • 角色:包含用户的集合

4.配置映射关系

4.2 表关系建立

多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:

4.3 实体类关系建立以及映射配置

一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:

package top.onefine.domain;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_user")
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "user_age")
    private Integer age;

    // 配置用户到角色的多对多关系
    /**
     * 配置多对多的映射关系
     *      1. 声明表关系的配置
     *          @ ManyToMany(targetEntity = Role.class)  // 多对多
     *              targetEntity 代表对方的实体类字节码
     *      2. 配置中间表(包含两个外键)
     *           @ JoinTable
     *              name 配置中间表名称
     *             joinColumns的数组 配置当前对象在中间表的外键
     *                  @ JoinColumn
     *                      name外键名
     *                      referencedColumnName参照主表的主键名
     *             inverseJoinColumns的数组 配置对方对象在中间表的外键
     *                  @ JoinColumn
     *                      name外键名
     *                      referencedColumnName参照主表的主键名
     */
    @ManyToMany(targetEntity = Role.class)
    @JoinTable(name = "sys_user_role", // 配置中间表名称
            joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}, // 配置当前对象在中间表中的外键,name随便,referencedColumnName参照当前对象主键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}  // 配置对方对象在中间表的外键,name随便,referencedColumnName参照当前对象主键
    )
    private Set roles = new HashSet<>();

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + ''' +
                ", age=" + age +
                '}';
    }
}
package top.onefine.domain;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "sys_role")
@Getter
@Setter
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "role_id")
    private Long roleId;
    @Column(name = "role_name")
    private String roleName;

    // 配置角色到用户的多对多关系
    @ManyToMany(targetEntity = User.class)  // 声明多对多的关系
    @JoinTable(name = "sys_user_role", // 配置中间表名称
            joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}, // 配置当前对象在中间表中的外键,name随便,referencedColumnName参照当前对象主键
            inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}  // 配置对方对象在中间表的外键,name随便,referencedColumnName参照当前对象主键
    )
    // 放弃主键维护权
//    @ManyToMany(mappedBy = "roles")
    private Set users = new HashSet<>();

    @Override
    public String toString() {
        return "User{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + ''' +
                '}';
    }
}
4.4 映射的注解说明

@ManyToMany
    作用:用于映射多对多关系
    属性:
        cascade:配置级联操作。
        fetch:配置是否采用延迟加载。
        targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable
    作用:针对中间表的配置
    属性:
        nam:配置中间表的名称
        joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段                          
        inverseJoinColumn:中间表的外键字段关联对方表的主键字段
        
@JoinColumn
    作用:用于定义主键字段和外键字段的对应关系。
    属性:
        name:指定外键字段的名称
        referencedColumnName:指定引用主表的主键字段名称
        unique:是否唯一。默认值不唯一
        nullable:是否允许为空。默认值允许。
        insertable:是否允许插入。默认值允许。
        updatable:是否允许更新。默认值允许。
        columnDefinition:列的定义信息。

4.5 多对多的操作
4.5.1 保存

srcmain esourcespplicationContext.xml中:


	
        
            create
        
    

srcmainjava oponefinedaoUserDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.User;

public interface UserDao extends JpaRepository, JpaSpecificationExecutor {
}

srcmainjava oponefinedaoRoleDao.java:

package top.onefine.dao;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import top.onefine.domain.Role;

public interface RoleDao extends JpaRepository, JpaSpecificationExecutor {
}

栗子1:

package top.onefine.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import top.onefine.dao.RoleDao;
import top.onefine.dao.UserDao;
import top.onefine.domain.Role;
import top.onefine.domain.User;

@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Many2ManyTest {

    @Autowired
    private UserDao userDao;

    @Autowired
    private RoleDao roleDao;

    // 保存一个用户,保存一个角色
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        User user = new User();
        user.setUserName("one fine");
        Role role = new Role();
        role.setRoleName("Java 工程师");

        userDao.save(user);
        roleDao.save(role);
    }
}

结果:

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on
Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b
Hibernate: drop table if exists sys_role
Hibernate: drop table if exists sys_user
Hibernate: drop table if exists sys_user_role
Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id))
Hibernate: create table sys_user (user_id bigint not null auto_increment, user_age integer, user_name varchar(255), primary key (user_id))
Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_role_id, sys_user_id))
Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)
Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)
Hibernate: insert into sys_user (user_age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)

栗子2:

	// 保存一个用户,保存一个角色
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        User user = new User();
        user.setUserName("one fine");
        Role role = new Role();
        role.setRoleName("Java 工程师");

        // 配置用户到角色关系,可以对中间表中的数据进行维护
        user.getRoles().add(role);

        userDao.save(user);
        roleDao.save(role);
    }

结果:

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on
Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b
Hibernate: drop table if exists sys_role
Hibernate: drop table if exists sys_user
Hibernate: drop table if exists sys_user_role
Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id))
Hibernate: create table sys_user (user_id bigint not null auto_increment, user_age integer, user_name varchar(255), primary key (user_id))
Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_role_id, sys_user_id))
Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)
Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)
Hibernate: insert into sys_user (user_age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)

栗子3:

	@Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        User user = new User();
        user.setUserName("one fine");
        Role role = new Role();
        role.setRoleName("Java 工程师");

        // 配置角色到用户关系,可以对中间表中的数据进行维护
        role.getUsers().add(user);

        userDao.save(user);
        roleDao.save(role);
    }

效果:

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on
Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b
Hibernate: drop table if exists sys_role
Hibernate: drop table if exists sys_user
Hibernate: drop table if exists sys_user_role
Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id))
Hibernate: create table sys_user (user_id bigint not null auto_increment, user_age integer, user_name varchar(255), primary key (user_id))
Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_role_id, sys_user_id))
Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)
Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)
Hibernate: insert into sys_user (user_age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_role_id, sys_user_id) values (?, ?)

栗子4:

	// 保存一个用户,保存一个角色
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        User user = new User();
        user.setUserName("one fine");
        Role role = new Role();
        role.setRoleName("Java 工程师");

        // 配置用户到角色关系,可以对中间表中的数据进行维护	1:1
        user.getRoles().add(role);

        // 配置角色到用户关系,可以对中间表中的数据进行维护	1:1
        role.getUsers().add(user);

        userDao.save(user);
        roleDao.save(role);
    }

执行抛出异常,主键冲突:

在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,被选择的放弃,这里是角色被用户选择,所以角色放弃维护权,配置如下:

//    // 配置角色到用户的多对多关系
//    @ManyToMany(targetEntity = User.class)  // 声明多对多的关系
//    @JoinTable(name = "sys_user_role", // 配置中间表名称
//            joinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}, // 配置当前对象在中间表中的外键,name随便,referencedColumnName参照当前对象主键
//            inverseJoinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}  // 配置对方对象在中间表的外键,name随便,referencedColumnName参照当前对象主键
//    )
    // 放弃主键维护权,解决保存中主键冲突的问题
    @ManyToMany(mappedBy = "roles")
    private Set users = new HashSet<>();

重新执行,效果:

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on
Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b
Hibernate: drop table if exists sys_role
Hibernate: drop table if exists sys_user
Hibernate: drop table if exists sys_user_role
Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id))
Hibernate: create table sys_user (user_id bigint not null auto_increment, user_age integer, user_name varchar(255), primary key (user_id))
Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_user_id, sys_role_id))
Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)
Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)
Hibernate: insert into sys_user (user_age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
栗子5,级联添加:

srcmainjava oponefinedomainUser.java中:

	@ManyToMany(targetEntity = Role.class, cascade = CascadeType.ALL)
    @JoinTable(name = "sys_user_role", // 配置中间表名称
            joinColumns = {@JoinColumn(name = "sys_user_id", referencedColumnName = "user_id")}, // 配置当前对象在中间表中的外键,name随便,referencedColumnName参照当前对象主键
            inverseJoinColumns = {@JoinColumn(name = "sys_role_id", referencedColumnName = "role_id")}  // 配置对方对象在中间表的外键,name随便,referencedColumnName参照当前对象主键
    )
    private Set roles = new HashSet<>();

src estjava oponefine estMany2ManyTest.java中:

	// 测试级联添加:保存一个用户的同时保存用户的关联角色
    @Test
    @Transactional
    @Rollback(false)
    public void testCasCadeAdd() {
        User user = new User();
        user.setUserName("one fine");
        Role role = new Role();
        role.setRoleName("Java 工程师");

        // 配置用户到角色关系,可以对中间表中的数据进行维护
        user.getRoles().add(role);

        // 配置角色到用户关系,可以对中间表中的数据进行维护
        role.getUsers().add(user);

        userDao.save(user);
    }

效果:

Hibernate: alter table sys_user_role drop foreign key FK1ef5794xnbirtsnudta6p32on
Hibernate: alter table sys_user_role drop foreign key FKsbjvgfdwwy5rfbiag1bwh9x2b
Hibernate: drop table if exists sys_role
Hibernate: drop table if exists sys_user
Hibernate: drop table if exists sys_user_role
Hibernate: create table sys_role (role_id bigint not null auto_increment, role_name varchar(255), primary key (role_id))
Hibernate: create table sys_user (user_id bigint not null auto_increment, user_age integer, user_name varchar(255), primary key (user_id))
Hibernate: create table sys_user_role (sys_user_id bigint not null, sys_role_id bigint not null, primary key (sys_user_id, sys_role_id))
Hibernate: alter table sys_user_role add constraint FK1ef5794xnbirtsnudta6p32on foreign key (sys_role_id) references sys_role (role_id)
Hibernate: alter table sys_user_role add constraint FKsbjvgfdwwy5rfbiag1bwh9x2b foreign key (sys_user_id) references sys_user (user_id)
Hibernate: insert into sys_user (user_age, user_name) values (?, ?)
Hibernate: insert into sys_role (role_name) values (?)
Hibernate: insert into sys_user_role (sys_user_id, sys_role_id) values (?, ?)
4.5.2 删除

srcmain esourcespplicationContext.xml中:


	
	    
	        update
	    
	

级联删除:

	// 测试级联删除:删除id为1的客户(上个栗子保存的数据),同时删除他的关联对象
	/**
	 * 删除操作
	 * 	在多对多的删除时,双向级联删除根本不能配置
	 * 禁用
	 *	如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
	 */
    @Test
    @Transactional
    @Rollback(false)
    public void testCasCadeRemove() {
        // 1. 查询客户1
        User user = userDao.findOne(1L);
        // 2. 删除客户1
        userDao.delete(user);
    }

效果:

Hibernate: select user0_.user_id as user_id1_1_0_, user0_.user_age as user_age2_1_0_, user0_.user_name as user_nam3_1_0_ from sys_user user0_ where user0_.user_id=?
Hibernate: select roles0_.sys_user_id as sys_user1_2_0_, roles0_.sys_role_id as sys_role2_2_0_, role1_.role_id as role_id1_0_1_, role1_.role_name as role_nam2_0_1_ from sys_user_role roles0_ inner join sys_role role1_ on roles0_.sys_role_id=role1_.role_id where roles0_.sys_user_id=?
Hibernate: delete from sys_user_role where sys_user_id=?
Hibernate: delete from sys_role where role_id=?
Hibernate: delete from sys_user where user_id=?

五、Spring Data JPA中的多表查询

5.1 对象导航查询

对象导航查询即通过一个对象,查询此对象关联的所有对象。

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

查询一个客户,获取该客户下的所有联系人
 

	@Autowired
	private CustomerDao customerDao;
	
	@Test
	//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
	@Transactional 
	public void testFind() {
		Customer customer = customerDao.findOne(5l);
		Set linkMans = customer.getLinkMans();//对象导航查询
		for(LinkMan linkMan : linkMans) {
  			System.out.println(linkMan);
		}
	}

查询一个联系人,获取该联系人的所有客户

	@Autowired
	private LinkManDao linkManDao;
	
	@Test
	public void testFind() {
		LinkMan linkMan = linkManDao.findOne(4l);
		Customer customer = linkMan.getCustomer(); //对象导航查询
		System.out.println(customer);
	}

对象导航查询的问题分析

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

	/**
	 * 在客户对象的@OneToMany注解中添加fetch属性
	 * 		FetchType.EAGER	:立即加载
	 * 		FetchType.LAZY	:延迟加载
	 */
	@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
	private Set linkMans = new HashSet<>(0);

问题2:我们查询联系人时,要不要把客户查询出来?

分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。

解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来

配置方式

	/**
	 * 在联系人对象的@ManyToOne注解中添加fetch属性
	 * 		FetchType.EAGER	:立即加载
	 * 		FetchType.LAZY	:延迟加载
	 */
	@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
	@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
	private Customer customer;
栗子,使用第三章(一对多)的配置

srcmainjava oponefinedomainCustomer.java:

package top.onefine.domain;

import lombok.*;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * 1. 实体类和表的映射关系
 *      -@Eitity
 *      -@Table
 * 2. 类中属性和数据库表中字段的映射关系
 *      -@Id 主键
 *      -@GeneratedValue 主键生成策略
 *      -@Column
 */
@Entity
@Table(name = "cst_customer")
@Getter
@Setter
@NoArgsConstructor
public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "cust_id")
    private Long custId;
    @Column(name = "cust_address")
    private String custAddress;
    @Column(name = "cust_industry")
    private String custIndustry;
    @Column(name = "cust_level")
    private String custLevel;
    @Column(name = "cust_name")
    private String custName;
    @Column(name = "cust_phone")
    private String custPhone;
    @Column(name = "cust_source")
    private String custSource;

    // 配置客户和联系人之间的关系(一对多关系)
    /*
        使用注解的形式配置多表关系:
            1. 声明关系
                - @OneToMany:配置一对多关系
                    targetEntity:对方对象的字节码对象
            2. 配置外键(或中间表)
                - @JoinColumn:配置外键
                    name:从表 外键字段名称
                    referencedColumnName:参照的 主表 的主键字段名称

        注:在客户实体类上(一的一方)添加了外键的配置,所以对于客户而言,也具备了维护外键的作用

     */
//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    /*
        放弃外键维护权
            mappedBy: 对方配置关系的属性名称
                表示参照对方的属性来做

            cascade:配置级联操作,可以配置到设置多表的映射关系的注解上
            * 		CascadeType.MERGE	级联更新
            * 		CascadeType.PERSIST	级联保存:
            * 		CascadeType.REFRESH 级联刷新:
            * 		CascadeType.REMOVE	级联删除:
            * 		CascadeType.ALL		包含以上所有,推荐配置


            fetch:配置关联对象的加载方式
                FetchType.EAGER 立即加载 -- 不推荐
                FetchType.LAZY  延迟加载 -- 默认
     */
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL/*, fetch = FetchType.EAGER*/)
    private Set linkMans = new HashSet<>();

    // 注意不含集合
    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custAddress='" + custAddress + ''' +
                ", custIndustry='" + custIndustry + ''' +
                ", custLevel='" + custLevel + ''' +
                ", custName='" + custName + ''' +
                ", custPhone='" + custPhone + ''' +
                ", custSource='" + custSource + ''' +
                '}';
    }
}

srcmainjava oponefinedomainLinkMan.java:

package top.onefine.domain;

import lombok.*;

import javax.persistence.*;

@Entity
@Table(name = "cst_linkman")
//@Data
@Getter
@Setter
//@ToString
@NoArgsConstructor
public class LinkMan {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "lkm_id")
    private Long lkmId;  // 联系人编号
    @Column(name = "lkm_name")
    private String lkmName;  // 联系人姓名
    @Column(name = "lkm_gender")
    private String lkmGender;  // 联系人性别  // 对应数据库表中字段char(1)
    @Column(name = "lkm_phone")
    private String lkmPhone;  // 联系人办公电话
    @Column(name = "lkm_mobile")
    private String lkmMobile;  // 联系人手机
    @Column(name = "lkm_email")
    private String lkmEmail;  // 联系人邮箱
    @Column(name = "lkm_position")
    private String lkmPosition;  // 联系人职位
    @Column(name = "lkm_memo")
    private String lkmMemo;  // 联系人备注

    // 配置联系人到客户的多对一关系
    /*
        使用注解的形式配置多对一关系
            1. 配置表关系
                - @ManyToOne:配置多对一关系
                    targetEntity:对方对象的字节码对象
            2. 配置外键(或中间表)

        注:配置外键的过程,配置到了多的一方,就会在多的一方维护外键

        fetch:配置关联对象的加载方式
                FetchType.EAGER 立即加载 -- 默认
                FetchType.LAZY  延迟加载
     */
    @ManyToOne(targetEntity = Customer.class/*, fetch = FetchType.LAZY*/)
    @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    private Customer customer;

    // 注意不含customer
    @Override
    public String toString() {
        return "LinkMan{" +
                "lkmId=" + lkmId +
                ", lkmName='" + lkmName + ''' +
                ", lkmGender='" + lkmGender + ''' +
                ", lkmPhone='" + lkmPhone + ''' +
                ", lkmMobile='" + lkmMobile + ''' +
                ", lkmEmail='" + lkmEmail + ''' +
                ", lkmPosition='" + lkmPosition + ''' +
                ", lkmMemo='" + lkmMemo + ''' +
                '}';
    }
}

src estjava oponefinedaoObjectQueryTest.java:

package top.onefine.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import top.onefine.domain.Customer;
import top.onefine.domain.LinkMan;

import java.util.Set;


@SuppressWarnings("All")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ObjectQueryTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    @Test
    @Transactional  // 解决:could not initialize proxy - no Session 问题
    public void testQuery1() {
        // 1. 查询客户
        Customer user = customerDao.getOne(3L);  // 延迟加载
        // 2. 对象导航查询,查询此客户下的所有联系人
        Set linkMans = user.getLinkMans();
        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    }

    /**
     * 一的一方
     * 注意:对象导航查询 默认使用的是延迟加载 的形式查询的
     *      调用getLinkMans方法并不会立即发送查询,而是在使用关联对象的时候才会查询,所以是延迟加载!
     *
     * 若需要将延迟加载改为立即加载(不推荐使用),需要修改配置 Customer中设置
     *      fetch,需要配置到多表映射关系的注解上
     */
    @Test
    @Transactional  // 解决:could not initialize proxy - no Session 问题
    public void testQuery2() {
        // 1. 查询客户
        Customer user = customerDao.findOne(3L);  // 立即加载
        // 2. 对象导航查询,查询此客户下的所有联系人
        Set linkMans = user.getLinkMans();
//        System.out.println(linkMans.size());
        System.out.println(linkMans);
    }

    // 从联系人对象导航查询所属客户
    /** 多的一方
     * 注意:对象导航查询 默认使用的是立即加载 的形式查询的
     *      调用getCustomer方法会立即发送查询
     *
     * 若需要将延迟加载改为立即加载(不推荐使用),需要修改配置 LinkMan中设置
     *      fetch,需要配置到多表映射关系的注解上
     */
    @Test
    @Transactional  // 解决:could not initialize proxy - no Session 问题
    public void testQuery3() {
        // 1. 查询联系人
        LinkMan linkMan = linkManDao.findOne(4L);
        assert linkMan != null;
        // 2. 对象导航查询,查询此联系人对应的客户
        Customer customer = linkMan.getCustomer();

        System.out.println(customer);
    }
}

其他不变,参照第三章:
srcmainjava oponefinedaoCustomerDao.java
srcmainjava oponefinedaoLinkManDao.java
srcmain esourcespplicationContext.xml
pom.xml

总结:

对象导航查询:查询一个对象的同时,通过此对象查询他的关联对象

  • 从一方查询多方;默认:使用延迟加载,务必

  • 从多方查询一方;默认:使用立即加载

5.2 使用Specification查询
	/**
	 * Specification的多表查询
	 */
	@Test
	public void testFind() {
		Specification spec = new Specification() {
			public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
				//Join代表链接查询,通过root对象获取
				//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
				//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
				Join join = root.join("customer",JoinType.INNER);
				return cb.like(join.get("custName").as(String.class),"传智播客1");
			}
		};
		List list = linkManDao.findAll(spec);
		for (LinkMan linkMan : list) {
			System.out.println(linkMan);
		}
	}

本文地址:https://www.vps345.com/1524.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 服务器 linux 运维 游戏 云计算 javascript 前端 chrome edge 进程 操作系统 进程控制 Ubuntu 科技 ai java 语言模型 人工智能 个人开发 自然语言处理 python MCP 阿里云 网络 网络安全 网络协议 llama 算法 opencv 神经网络 ssh ubuntu deepseek Ollama 模型联网 API CherryStudio 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 vue.js audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 rust http 开发语言 fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse harmonyos 华为 typescript 计算机网络 数据库 centos oracle 关系型 安全 分布式 Flask FastAPI Waitress Gunicorn uWSGI Uvicorn RTSP xop RTP RTSPServer 推流 视频 运维开发 云原生 物联网 iot ESXi github go 代理模式 Windsurf ollama 大模型 mac tcp/ip php android 面试 性能优化 jdk intellij-idea 架构 Dell R750XS 华为云 笔记 嵌入式硬件 单片机 c++ 温湿度数据上传到服务器 Arduino HTTP udp unity 学习 uni-app 深度学习 YOLO 目标检测 计算机视觉 efficientVIT YOLOv8替换主干网络 TOLOv8 macos 编辑器 鸿蒙 鸿蒙系统 前端框架 spring boot websocket 计算机外设 电脑 软件需求 pycharm ide pytorch WSL2 nginx dubbo 开源 GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 ssl ESP32 golang 后端 HCIE 数通 filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 HarmonyOS Next adb node.js json html5 firefox 命名管道 客户端与服务端通信 vim vue3 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 kubernetes 容器 学习方法 经验分享 程序人生 C 环境变量 进程地址空间 asm numpy c# 创意 社区 flask AI编程 AIGC docker DeepSeek-R1 API接口 cpu 内存 实时 使用 flutter Hyper-V WinRM TrustedHosts 机器学习 向日葵 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 camera Arduino 电子信息 统信UOS 麒麟 bonding 链路聚合 cuda cudnn anaconda debian PVE 腾讯云大模型知识引擎 Deepseek AI AI大模型 程序员 Agent Reactor 设计模式 C++ 智能路由器 dell服务器 conda .net rabbitmq 直播推流 Linux 进程信号 firewalld windows yum 远程工作 服务器配置 生物信息学 mysql 中间件 web安全 可信计算技术 安全架构 网络攻击模型 自动化 n8n 工作流 workflow Qwen2.5-coder 离线部署 threejs 3D fpga开发 tomcat react.js 前端面试题 持续部署 chatgpt devops springboot Dify kylin express UOS 统信操作系统 jenkins maven svn oceanbase rc.local 开机自启 systemd redis mybatis YOLOv12 pip llama3 Chatglm 开源大模型 微服务 springcloud transformer ping++ 深度优先 图论 并集查找 换根法 树上倍增 ddos iBMC UltraISO llm c语言 qt stm32项目 stm32 kafka zotero WebDAV 同步失败 agi ffmpeg 音视频 嵌入式 linux驱动开发 arm开发 mcu 信息与通信 jupyter 部署 ocr webrtc asp.net大文件上传 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 .net mvc断点续传 mongodb 银河麒麟服务器操作系统 系统激活 k8s sql KingBase 博客 Docker Hub docker pull 镜像源 daemon.json 微信小程序 AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 小程序 zabbix 负载均衡 QQ 机器人 bot Docker 智能手机 NAS Termux Samba 弹性计算 云服务器 裸金属服务器 弹性裸金属服务器 虚拟化 postman mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 vue LDAP 并查集 leetcode ollama下载加速 kvm 跨域 微信 vscode IIS服务器 IIS性能 日志监控 腾讯云 VMware安装Ubuntu Ubuntu安装k8s intellij idea micropython esp32 mqtt rpc gitee 实时音视频 django sqlite YOLOv8 NPU Atlas800 A300I pro 服务器管理 宝塔面板 配置教程 服务器安装 网站管理 MQTT mosquitto 消息队列 r语言 数据挖掘 数据可视化 数据分析 .netcore word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 1024程序员节 git ansible 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 spring cloud hibernate 课程设计 kamailio sip VoIP 蓝耘科技 元生代平台工作流 ComfyUI 工业4.0 客户端 java-ee RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 具身智能 安全威胁分析 sqlserver 微信分享 Image wxopensdk 交换机 telnet 远程登录 rust腐蚀 漏洞 安装教程 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 豆瓣 追剧助手 迅雷 nas 缓存 unity3d 华为od OD机试真题 华为OD机试真题 服务器能耗统计 gitlab Ark-TS语言 okhttp CORS dns apache 孤岛惊魂4 spring 低代码 aws googlecloud 恒源云 vSphere vCenter 软件定义数据中心 sddc 致远OA OA服务器 服务器磁盘扩容 jmeter 软件测试 arm opcua opcda KEPServer安装 大模型微调 系统架构 数据结构 多线程服务器 Linux网络编程 hadoop 监控 自动化运维 gateway Clion Nova ResharperC++引擎 Centos7 远程开发 VMware安装mocOS VMware macOS系统安装 外网访问 内网穿透 端口映射 pillow https AISphereButler live555 rtsp rtp outlook 大数据 政务 分布式系统 监控运维 Prometheus Grafana visualstudio 大数据平台 WSL win11 无法解析服务器的名称或地址 Trae IDE AI 原生集成开发环境 Trae AI 产品经理 microsoft 框架搭建 gpu算力 web3.py unix arkUI 金融 爬虫 网络用户购物行为分析可视化平台 大数据毕业设计 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 Kali Linux 黑客 渗透测试 信息收集 测试工具 Python 网络编程 聊天服务器 套接字 TCP Socket eureka DeepSeek string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap 代码调试 ipdb adobe tcpdump NPS 雨云服务器 雨云 springsecurity6 oauth2 授权服务器 token sas pygame 小游戏 五子棋 源码剖析 rtsp实现步骤 流媒体开发 LLM nvidia list 远程控制 远程看看 远程协助 软件工程 多进程 rsyslog 僵尸进程 串口服务器 ecmascript KVM C语言 ipython 硬件工程 wireshark 本地部署 api DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 Cookie centos-root /dev/mapper yum clean all df -h / du -sh 京东云 bootstrap html asi_bench nextjs react reactjs matplotlib ue4 着色器 ue5 虚幻 safari Mac 系统 飞牛NAS 飞牛OS MacBook Pro thingsboard postgresql prometheus Ubuntu Server Ubuntu 22.04.5 实时互动 企业微信 Linux24.04 deepin 相差8小时 UTC 时间 Java netty elasticsearch ip命令 新增网卡 新增IP 启动网卡 IIS .net core Hosting Bundle .NET Framework vs2022 匿名管道 XFS xfs文件系统损坏 I_O error es jvm CLion 远程连接 frp 集成学习 集成测试 GPU 状态管理的 UDP 服务器 Arduino RTOS openEuler 报错 其他 驱动开发 gitea DevEco Studio 媒体 微信公众平台 curl wget 大模型应用 NFS deepseek r1 file server http server web server 计算机 设置代理 实用教程 iftop 网络流量监控 ruoyi make命令 makefile文件 JAVA Invalid Host allowedHosts rdp 实验 鲲鹏 昇腾 npu linux 命令 sed 命令 jar gradle 王者荣耀 Wi-Fi web DNS C# MQTTS 双向认证 emqx 自动化任务管理 英语 SysBench 基准测试 xcode ecm bpm 安卓 virtualenv dify ragflow 云电竞 云电脑 todesk 职场和发展 yum源切换 更换国内yum源 ui 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 音乐服务器 Navidrome 音流 线程 EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 游戏服务器 Minecraft 文件系统 路径解析 Erlang OTP gen_server 热代码交换 事务语义 MNN Qwen bash mamba Vmamba freebsd 系统安全 远程桌面 gaussdb playbook vr excel 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 MacOS录屏软件 Dell HPE 联想 浪潮 iDRAC R720xd 硬件架构 kind AI写作 AI作画 next.js 部署next.js 聊天室 RAGFLOW RAG 检索增强生成 文档解析 大模型垂直应用 yolov8 命令 指令 mq rocketmq 压力测试 测试用例 功能测试 shell 磁盘监控 muduo 个人博客 X11 Xming 医疗APP开发 app开发 高级IO epoll 大语言模型 矩阵 服务器繁忙 bcompare Beyond Compare 剧本 mariadb Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 计算虚拟化 弹性裸金属 网络工程师 华为认证 游戏程序 监控k8s 监控kubernetes 无人机 minio Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 EMUI 回退 降级 升级 vscode 1.86 网站搭建 serv00 jetty undertow Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 grafana langchain IPMI WebUI DeepSeek V3 redhat ios kali 共享文件夹 虚拟机 硬件 设备 PCI-Express pyautogui ip 银河麒麟 kylin v10 麒麟 v10 qemu libvirt SSH 远程服务 p2p CrewAI log4j 单元测试 selenium 游戏机 hugo Netty 即时通信 NIO SWAT 配置文件 服务管理 网络共享 灵办AI wsl 银河麒麟桌面操作系统 Kylin OS 国产化 DeepSeek行业应用 Heroku 网站部署 迁移指南 半虚拟化 硬件虚拟化 Hypervisor 思科模拟器 思科 Cisco nuxt3 gpt 图像处理 图形化界面 边缘计算 tensorflow openssl 密码学 trae 模拟退火算法 SSH 服务 SSH Server OpenSSH Server GCC crosstool-ng 田俊楠 Cline 自动化编程 算力 wsl2 npm k8s集群资源管理 云原生开发 ros2 moveit 机器人运动 多层架构 解耦 Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 数据库系统 c dity make 分析解读 Java Applet URL操作 服务器建立 Socket编程 网络文件读取 显示过滤器 安装 ICMP Wireshark安装 大模型入门 大模型教程 remote-ssh 直流充电桩 充电桩 protobuf 序列化和反序列化 W5500 OLED u8g2 TCP服务器 chfs ubuntu 16.04 webstorm 微信开放平台 微信公众号配置 同步 备份 建站 强制清理 强制删除 mac废纸篓 rime 网络穿透 火绒安全 Nuxt.js Xterminal ci/cd bug minicom 串口调试工具 MacMini 迷你主机 mini Apple CPU 主板 电源 网卡 宠物 毕业设计 免费学习 宠物领养 宠物平台 小艺 Pura X 飞书 uniapp css xml 开发环境 服务器数据恢复 数据恢复 存储数据恢复 北亚数据恢复 oracle数据恢复 监控k8s集群 集群内prometheus 能力提升 面试宝典 技术 IT信息化 oneapi open webui docker命令大全 5G 3GPP 卫星通信 cmos echarts matlab 传统数据库升级 银行 LLMs 移动云 MS Materials android studio 业界资讯 VR手套 数据手套 动捕手套 动捕数据手套 code-server 数据集 pgpool XCC Lenovo 输入法 av1 电视盒子 机顶盒ROM 魔百盒刷机 自定义客户端 SAS embedding 3d 数学建模 网络结构图 nfs 信息可视化 网页设计 SSL 域名 skynet 虚拟局域网 gcc selete 回显服务器 UDP的API使用 keepalived sonoma 自动更新 大模型面经 大模型学习 远程 执行 sshpass 操作 LORA NLP 显卡驱动 xshell termius iterm2 系统开发 binder 车载系统 framework 源码环境 国产操作系统 ukui 麒麟kylinos openeuler 统信 虚拟机安装 gpt-3 文心一言 云桌面 微软 AD域控 证书服务器 大大通 第三代半导体 碳化硅 RustDesk自建服务器 rustdesk服务器 docker rustdesk 黑客技术 URL ArcTS 登录 ArcUI GridItem ftp rustdesk VPS pyqt Linux PID EasyConnect k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm chrome devtools chromedriver LInux docker run 数据卷挂载 交互模式 Cursor big data opensearch helm ruby 服务器主板 AI芯片 ssrf 失效的访问控制 MI300x WebRTC openwrt ux 多线程 vscode1.86 1.86版本 ssh远程连接 open Euler dde RTMP 应用层 docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos IPMITOOL BMC 硬件管理 springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 USB网络共享 xrdp Playwright 自动化测试 SRS 流媒体 直播 TrinityCore 魔兽世界 P2P HDLC SSL证书 技能大赛 linux上传下载 elk 交互 iis 图形渲染 VSCode 云服务 odoo 服务器动作 Server action 黑苹果 视觉检测 linux环境变量 FTP 服务器 崖山数据库 YashanDB sdkman VMware创建虚拟机 vmware 卡死 繁忙 解决办法 替代网站 汇总推荐 AI推理 蓝桥杯 RAID RAID技术 磁盘 存储 Ubuntu 24.04.1 轻量级服务器 MQTT协议 消息服务器 代码 dash 正则表达式 dba tidb GLIBC uv pdf 群晖 文件分享 eNSP 网络规划 VLAN 企业网络 alias unalias 别名 毕设 wps raid5数据恢复 磁盘阵列数据恢复 搜索引擎 银河麒麟操作系统 远程过程调用 Windows环境 服务器部署ai模型 sqlite3 Anolis nginx安装 环境安装 linux插件下载 AI代码编辑器 mysql离线安装 ubuntu22.04 mysql8.0 linux安装配置 源码 三级等保 服务器审计日志备份 Docker Compose docker compose docker-compose 混合开发 JDK 无桌面 命令行 risc-v 嵌入式实习 Redis Desktop TRAE 软考 etcd 数据安全 RBAC hive Hive环境搭建 hive3环境 Hive远程模式 流式接口 Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 联想开天P90Z装win10 nac 802.1 portal Kylin-Server DBeaver 数据仓库 kerberos 多个客户端访问 IO多路复用 TCP相关API 链表 基础入门 编程 开机自启动 rag ragflow 源码启动 网工 visual studio code 压测 ECS Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer 宕机切换 服务器宕机 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? 目标跟踪 OpenVINO 推理应用 Google pay Apple pay flash-attention idm windows日志 ceph DOIT 四博智联 数据库架构 数据管理 数据治理 数据编织 数据虚拟化 性能测试 OpenManus 环境迁移 深度求索 私域 知识库 python3.11 cnn 邮件APP 免费软件 lio-sam SLAM glibc Node-Red 编程工具 流编程 midjourney 软件构建 ubuntu24.04.1 游戏引擎 NLP模型 sentinel 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 自学笔记 小米 澎湃OS Android prompt jina x64 SIGSEGV SSE xmm0 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 HiCar CarLife+ CarPlay QT RK3588 信创 信创终端 中科方德 c/c++ 串口 读写锁 用户缓冲区 AI Agent 字节智能运维 模拟实现 支付 微信支付 开放平台 wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 bat 端口 查看 ss FunASR ASR fd 文件描述符 佛山戴尔服务器维修 佛山三水服务器维修 OpenSSH 我的世界 我的世界联机 数码 IPv4 子网掩码 公网IP 私有IP SSH 密钥生成 SSH 公钥 私钥 生成 我的世界服务器搭建 cocoapods 干货分享 黑客工具 密码爆破 SenseVoice iphone Spring Security 技术共享 执法记录仪 智能安全帽 smarteye easyui 线性代数 电商平台 服务器时间 C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 ISO镜像作为本地源 KylinV10 麒麟操作系统 Vmware Kali 渗透 镜像 相机 eclipse 权限 ShenTong 版本 cursor IO模型 飞牛nas fnos lua ROS 自动驾驶 trea idea pppoe radius 域名服务 DHCP 符号链接 配置 dns是什么 如何设置电脑dns dns应该如何设置 信号处理 音乐库 飞牛 Ubuntu22.04 开发人员主页 H3C AI-native Docker Desktop 免费域名 域名解析 前后端分离 DIFY AI agent iventoy VmWare OpenEuler CH340 串口驱动 CH341 uart 485 李心怡 IMX317 MIPI H265 VCU 键盘 Linux的权限 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 办公自动化 自动化生成 pdf教程 模拟器 教程 代理服务器 arcgis edge浏览器 g++ g++13 docker部署Python nlp 阻塞队列 生产者消费者模型 服务器崩坏原因 grub 版本升级 扩容 运维监控 游戏开发 嵌入式Linux IPC 显示管理器 lightdm gdm 大模型推理 apt WebVM banner 中兴光猫 换光猫 网络桥接 自己换光猫 策略模式 单例模式 流水线 脚本式流水线 推荐算法 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 DenseNet 代理 llama.cpp minecraft cd 目录切换 稳定性 看门狗 Typore HTTP 服务器控制 ESP32 DeepSeek 增强现实 沉浸式体验 应用场景 技术实现 案例分析 AR Xinference RAGFlow xss 语音识别 AutoDL 国内源 virtualbox DocFlow ubuntu24 vivado24 网络药理学 生信 gromacs 分子动力学模拟 MD 动力学模拟 换源 Debian vasp安装 查询数据库服务IP地址 SQL Server 分布式训练 qt项目 qt项目实战 qt教程 国标28181 视频监控 监控接入 语音广播 流程 SIP SDP centos 7 Open WebUI 视频编解码 Radius 交叉编译 Linux环境 UOS1070e EMQX 通信协议 VS Code 代码托管服务 WLAN WSL2 上安装 Ubuntu deep learning 银河麒麟高级服务器 外接硬盘 Kylin 根服务器 clickhouse vpn deekseek 社交电子 lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 做raid 装系统 ArkTs ArkUI laravel 小智AI服务端 xiaozhi TTS junit AD 域管理 实习 内网服务器 内网代理 内网通信 VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 需求分析 规格说明书 反向代理 信号 searxng PPI String Cytoscape CytoHubba Mac内存不够用怎么办 firewall 互信 毕昇JDK 金仓数据库 2025 征文 数据库平替用金仓 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 备选 网站 调用 示例 autodl AD域 影刀 #影刀RPA# 虚拟显示器 程序员创富 CDN 重启 排查 系统重启 日志 原因 su sudo 聚类 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 Headless Linux tcp 安防软件 端口测试 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 ros Ubuntu共享文件夹 共享目录 Linux共享文件夹 强化学习 flink figma 7z 华为机试 TrueLicense Claude Windows ai工具 armbian u-boot AnythingLLM AnythingLLM安装 GoogLeNet neo4j 数据库开发 database Jellyfin powerpoint 主从复制 人工智能生成内容 项目部署到linux服务器 项目部署过程 服务网格 istio CVE-2024-7347 基础环境 超融合 本地部署AI大模型 实战案例 h.264 序列化反序列化 cpp-httplib cfssl web3 拓扑图 openvpn server openvpn配置教程 centos安装openvpn can 线程池 LLM Web APP Streamlit 双系统 GRUB引导 Linux技巧 HarmonyOS OpenHarmony 程序 IMM 本地知识库部署 DeepSeek R1 模型 虚拟现实 ssh远程登录 sysctl.conf vm.nr_hugepages 语法 QT 5.12.12 QT开发环境 Ubuntu18.04 单一职责原则 浏览器开发 AI浏览器 ssh漏洞 ssh9.9p2 CVE-2025-23419 spark HistoryServer Spark YARN jobhistory 捆绑 链接 谷歌浏览器 youtube google gmail asp.net大文件上传下载 etl prometheus数据采集 prometheus数据模型 prometheus特点 僵尸世界大战 游戏服务器搭建 yaml Ultralytics 可视化 IM即时通讯 剪切板对通 HTML FORMAT zookeeper saltstack 软负载 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 swoole FTP服务器 v10 软件 ldap rnn regedit 开机启动 GIS 遥感 WebGIS 阿里云ECS rclone AList webdav fnOS webgl perf 架构与原理 seatunnel 考研 内网环境 产测工具框架 IMX6ULL 管理框架 tailscale derp derper 中转 Linux的基础指令 triton 模型分析 chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 less 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 大文件秒传跨域报错cors 私有化 k8s二次开发 集群管理 openstack Xen seleium 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 玩机技巧 软件分享 软件图标 IDEA composer MySql MCP server C/S vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 历史版本 下载 Logstash 日志采集 Unity Dedicated Server Host Client 无头主机 aarch64 编译安装 HPC Deepseek-R1 私有化部署 推理模型 开发 移动魔百盒 USB转串口 宝塔 常用命令 文本命令 目录命令 性能分析 DeepSeek r1 docker desktop image 网络建设与运维 css3 沙盒 智能电视 PX4 fast UDP docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 粘包问题 计算生物学 生物信息 基因组 加解密 Yakit yaklang word 流量运营 rpa iperf3 带宽测试 带外管理 top Linux top top命令详解 top命令重点 top常用参数 Linux find grep 欧标 OCPP ubuntu20.04 ros1 Noetic 20.04 apt 安装 navicat kernel AI员工 抓包工具 Attention Unity插件 CentOS perl MDK 嵌入式开发工具 论文笔记 sublime text Qwen2.5-VL vllm rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK 嵌入式系统开发 gnu zip unzip 软链接 硬链接 hexo nosql 网络爬虫 多端开发 智慧分发 应用生态 鸿蒙OS onlyoffice win服务器架设 windows server 大模型部署 磁盘清理 查看显卡进程 fuser 离线部署dify ArtTS kotlin 虚幻引擎 元服务 应用上架 问题解决 Linux权限 权限命令 特殊权限 状态模式 MAC SecureCRT Sealos 论文阅读 SVN Server tortoise svn 企业网络规划 华为eNSP xpath定位元素 HAProxy ABAP rancher 物联网开发 烟花代码 烟花 元旦 性能调优 安全代理 区块链 网络搭建 神州数码 神州数码云平台 云平台 MacOS SEO 进程优先级 调度队列 进程切换 visual studio ip协议 知识图谱 hosts ranger MySQL8.0 抗锯齿 nftables 防火墙 ubuntu 18.04 MVS 海康威视相机 wpf HarmonyOS NEXT 原生鸿蒙 dock 加速 Python基础 Python教程 Python技巧 proxy模式 wsgiref Web 服务器网关接口 java-rocketmq fstab ardunio BLE 浏览器自动化 在线office 存储维护 NetApp存储 EMC存储 环境配置 容器技术 开机黑屏 docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 react native 安装MySQL 通信工程 毕业 合成模型 扩散模型 图像生成 极限编程 多路转接 CentOS Stream vu大文件秒传跨域报错cors 鸿蒙开发 移动开发 智能音箱 智能家居 对比 工具 meld DiffMerge 健康医疗 互联网医院 项目部署 sequoiaDB nvm whistle deployment daemonset statefulset cronjob 云耀服务器 本地化部署 React Next.js 开源框架 网卡的名称修改 eth0 ens33 软件卸载 系统清理 TCP协议 milvus 钉钉 NAT转发 NAT Server 端口聚合 windows11 db System V共享内存 进程通信 harmonyOS面试题 树莓派 VNC 话题通信 服务通信 数字证书 签署证书 接口优化 WebServer 搜狗输入法 中文输入法 解决方案 服务器正确解析请求体 IO 视频平台 录像 视频转发 视频流 yolov5 vnc yum换源 conda配置 conda镜像源 热榜 MobaXterm 电视剧收视率分析与可视化平台 Web服务器 多线程下载工具 PYTHON java-rabbitmq UEFI Legacy MBR GPT U盘安装操作系统 copilot mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 风扇控制软件 MAVROS 四旋翼无人机 mcp服务器 client close js 西门子PLC 通讯 Reactor反应堆 本地环回 bind 机柜 1U 2U 搭建个人相关服务器 国产数据库 瀚高数据库 数据迁移 下载安装 macOS 达梦 DM8 powerbi