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

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

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

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