使用 Apache OpenJPA 开发 EJB 3.0 应用,第 4 部分: 实体关联

2007 年 7 月 17 日

JPA 是 EJB 3.0 新引入的数据持久化编程模型。JPA 充分利用了注释(Annotation)和对象/关系映射,为数据持久化提供了更简单、易用的编程方式。OpenJPA 是 Apache 组织提供的 JPA 标准实现。本文是 使用 Apache OpenJPA 开发 EJB 3.0 应用系列 的第四部分,介绍 OpenJPA 中对实体关联关系的支持,包括一对一、多对多、多对一和多对多关系,并且通过简单的例子描述了 OpenJPA 容器实现一对一实体关联关系时的实现过程。

对象和对象之间除了继承关系之外,还存在着关联关系:包括一对一、一对多、多对一和多对多关系,在 OpenJPA 框架下,开发者只需要使用 javax.persistence.OneToOne 这样的注释,并提供相应的配置内容,就可以轻松的实现实体之间的关联关系,并且能够实现实体的级联创建、更新和删除。

本 文中我们将以实体之间的一对一关联关系为例,深入地讲述如何使用 OpenJPA 框架提供的注释,实现企业应用中实体之间的关联关系。文中将提供一个简单的例子,详细的说明如何定义类和类之间的一对一关联关系的步骤,同时会重点讲述这 些注释所支持的属性。一对多、多对一和多对多这三种关联关系在 OpenJPA 中的实现过程和一对一关联关系的实现过程是一致的,只是需要选择使用不同的注释,在本文的最后,会对实现这三种关联关系进行简单说明,读者可以参考一对一 关系的实现过程来实现一对多、多对一和多对多的关联关系。

一对一关系

在面向对象的世界里,类 A 和类 B 之间形成一对一关系必须满足如下条件:

  1. 对象 A1 引用了对象 B1;
  2. 类 A 的其它对象 An 不能引用同样的对象 B1。

在关系数据库中,我们通常使用唯一外键的方式来实现一对一关系,下面这个图说明了这种的情况。

图 1. 关系数据库中的一对一关系

下面开始介绍 OpenJPA 中实现实体之间一对一关联关系的相关知识,为了说明的需要,我们首先定义一个简单的应用场景。

模拟场景

假定开发者要完成一个图书馆管理系统,我们需要记录书的基本信息如编号、书名、出版日期等基本信息,还需要记录书的前言,序等信息。

为 了说明实体之间的一对一关系,我们将书设计成一个类(Book),包括书的编号和名称两个属性,同时将书的前言设计成另外一个类 (BookExtend),它包括书的编号和前言两个属性。由于一本书有前言而且也不可能有其它书的前言部分会和它一样,所以类 Book 和 BookExtend 之间很自然的形成了一对一的关系。这两个类的属性以及类之间的关系如下图所示。

图 2. 类之间的一对一关系

[注]:为了说明的简单,本例子设计时每个对象只选择了必要的属性。

描述一对一关系

在 OpenJPA 中,开发者用来描述实体之间一对一关系时可选择的注释包括 javax.persistence.OneToOne 和 javax.persistence.JoinColumn。其中 javax.persistence.OneToOne 注释是必须使用的,它被用来声明类和类之间存在着一对一关系,javax.persistence.JoinColumn 注释是可选的,开发者使用 JoinColumn 注释来声明两个类在数据库中对应的表之间关联时的细节,包括主表中关联字段的名称、从表中使用什么字段来进行关联等。

javax.persistence.OneToOne

javax.persistence.OneToOne 注释支持如下 5 个属性,它们可以被开发者用来定义实体和实体之间一对一关联关系的细节内容。

target Entity

targetEntity 属性是 Class 类型的属性。定义实体一对一关系中处于从属地位的实体类的类型。如果没有为该属性设置值,OpenJPA 容器默认 targetEntity 属性的值是该成员属性对应的类类型,所以实体关系定义时通常不需要为 targetEntity 属性设置值。

mappedBy

mappedBy 属性是 String 类型的属性。mappedBy 属性的值是当前实体在关联实体中的属性名称,使用 mappedBy 可以定义实体类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要使用这个属性进行定义,否则可能引起数据一致性的问题。

以 演示场景中 Book 和 BookExtend 实体为例,假设我们只定义 Book 类有 BookExtend 类型的属性,而 BookExtend 并没有 Book 类型的属性,那么说明 Book 和 BookExtend 实体之间是单向关系;如果 BookExtend 中也定义了 Book 属性,那么 Book 和 BookExtend 实体之间就构成了双向关系。

cascade

cascade 属性的类型是 CascadeType[] 类型。cascade 属性定义实体和实体之间的级联关系。使用 cascade 属性定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。

以 演示场景中 Book 和 BookExtend 实体为例:如果设置 Book 和 BookExtend 存在级联关系,那么删除 Book 时将同时删除它所对应的 BookExtend 对象。而如果 BookExtend 还和其它的对象之间有级联关系,那么这样的操作会一直递归执行下去。

cascade 的值只能从 CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个更方便的选择是使用 CascadeType.ALL,表示选择上面全部四项。

fetch

fetch 属性是 FetchType 类型的属性。可选择项包括:FetchType.EAGER 和 FetchType.LAZY。前者表示关联关系的从类在主类加载的时候同时加载,后者表示关联关系的从类在自己被访问时才加载。默认值是 FetchType.EAGER。

optional

optional 属性是 boolean 类型的属性。optional 属性用于定义关联关系的从类对象是否必须存在。如果设置为 false,那么该属性就不能设置为 null。默认值是 true。

javax.persistence.OneToOne 用法举例

public class Book {    // 其它实体映射内容…  /*    * 使用 OneToOne 注释表示该属性和 Book 类形成一对一关系,OneToOne  * 注释的 option 属性设为 True 表示该对象可以不存在,cascade 属性   * 设置为 CascadeType.ALL,表示 Book 和 BookExtend 对象级联新建、 更新、删除、刷新    */    @OneToOne(optional=true,cascade=CascadeType.ALL)   public BookExtend bookExtend;}

javax.persistence.JoinColumn

javax.persistence.JoinColumn 注释可以和 javax.persistence.OneToOne 注释一起使用,用于定义关联关系中的主类在数据库中对应的表通过什么字段和关联关系中的从类的主键进行关联,这个注释是可选的,如果不提供该注释,OpenJPA 会默认使用”对象名_ID”和关联表的主键字段进行关联。

以演示场景中 Book 和 BookExtend 实体为例:如果 Book 的 bookExtend 属性没有使用 javax.persistence.JoinColumn 注释进行声明,我们使用 OpenJPA 提供的 Mapping Tool 工具生成表格的时候,Book 类对应的表 Book 中将自动加入列 bookExtend_ID,它的类型将和 BookExtend 对应表的主键字段id类型保持一致。

JoinColumn 注释支持两个重要属性:name 和 referencedColumnName 属性。

name

name 属性的类型是 String 类型。name 属性用于指定关联关系中的主类对应的表中和关联关系中的从类的主键进行关联的字段的名称。以演示场景中 Book 和 BookExtend 实体的关系为例:如果 Book 实体对应的表使用“beID”字段和 BookExtend 实体对应表的主键进行对应,我们可以在 Book 类中为 bookExtend 属性提供 javax.persistence.JoinColumn 注释,设置它的 name 属性为“beID”。

referencedColumnName

referencedColumnName 属性的类型是 String 类型。referencedColumnName 属性指定关联关系中的从类与关联关系中的主类对应的表之间形成关联关系的字段名称,通常用于关联关系中的从类的关联字段不是自己的主键的情况。以演示场景 中 Book 和 BookExtend 实体的关系为例:BookExtend 表中默认使用 Id 字段和 Book 类的某个字段进行关联,但如果实际情况下 BookExtends 表需要使用“myID“字段和 Book 表进行关联,我们就可以设置 javax.persistence.JoinColumn 注释的属性值为“myID”。

javax.persistence.JoinColumn 用法举例

public class Book { // 其它内容…

    /*    * 使用 OneToOne 注释表示该属性和 Book 类形成一对一关系,OneToOne  * 注释的 option 属性设为 True 表示该对象可以不存在,cascade 属性   * 设置为 CascadeType.ALL,表示 Book 和 BookExtend 对象级联新建、 更新、删除、刷新    */    @OneToOne(optional = true, cascade = CascadeType.ALL)  /*    * 使用 JoinColumn 注释设置两个对象对应数据库表之间的关联字段 *  name 属性指定关联关系中主类对应表中参与关联关系的字段名称,    *  referencedColumnNam 属性指定关联关系中从类对应表中参与关 *  联关系的字段名称,   */    @JoinColumn(name = "beID", referencedColumnName = "myID")  public BookExtend bookExtend;}

编写符合要求的持久化类

根据模拟场景的需求,结合我们前面学习到的描述实体之间一对一关联关系的知识,我们可以采用如下设计:

  1. Book 类和 BookExtend 之间存在一对一关联关系;
  2. Book、BookExtend 对应的表的主键字段由 MySQL 自动生成;
  3. Book 表中参与关联关系的字段名为“beID“;
  4. BookExtend 表中参与关联关系的字段使用默认字段“ID”;
  5. Book 类和 BookExtend 类之间存在全部级联关系;
  6. 不是每一个 Book 对象都需要有对应的 BookExtend 对象。

根据这样的设计,我们可以开始编写实体 Book 和 BookExtend 对应的持久化类代码,下面是作者编写的两个实体类的全部代码,大家可以参考代码中加入的大量注释学习如何使用注释来描述实体和实体之间的一对一关联关系。

Book 类

1. package org.vivianj.openjpa.beans;2.  3.    import javax.persistence.Basic;4. import javax.persistence.CascadeType;5.   import javax.persistence.Column;6.    import javax.persistence.Entity;7.    import javax.persistence.GeneratedValue;8.    import javax.persistence.GenerationType;9.    import javax.persistence.Id;10.   import javax.persistence.Inheritance;11.  import javax.persistence.InheritanceType;12.  import javax.persistence.JoinColumn;13.   import javax.persistence.OneToOne;14. 15.   /**16.     * Book 用于表征系统中的书籍对象,它有三个属性 id - 书籍编号,       * 书籍编号将由 MySQL 数据库自动生成 name - 书名 bookExtend –17.  * 书的扩展信息,和 BookExtend 是一对一(OneToOne)关系18.  */19.    20.   @Entity(name = "Book")21. public class Book {22.        /* Id 注释表示该字段是标识字段 */23.      @Id24.       /*25.      * GeneratedValue 注释定义了该标识字段的产生方式,我们的演示系统中26.       * id 由 MySQL 数据库字段自动生成,因此选择 GenerationType.IDENTITY27.         */28.        @GeneratedValue(strategy = GenerationType.IDENTITY)29.      /*30.      * Column 注释的 name 属性定义了该类属性对应的数据字段的名称,             * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符31.         */32.        @Column(name = "ID")33.       public int id;34. 35.       /* Basic 注释表示该属性是基本属性 */36.       @Basic37.        /*38.      * Column 注释的 name 属性定义了该类属性对应的数据字段的名称,             * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符39.         */40.        @Column(name = "NAME")41.     public String name = null;42.    43.       /*44.      * 使用 OneToOne 注释表示该属性和 Book 类形成一对一关系,OneToOne45.       * 注释的 option 属性设为 True 表示该对象可以不存在,cascade 属性46.        * 设置为 CascadeType.ALL,表示 Book 和 BookExtend 对象级联新建、 更新、删除、刷新47.         */48.        @OneToOne(optional = true, cascade = CascadeType.ALL)49.       /* 使用 JoinColumn 注释设置两个对象对应数据库表之间的关联字段 */50.      @JoinColumn(name = "extendID")51.     public BookExtend bookExtend;52.  }

BookExtend 类

1.   package org.vivianj.openjpa.beans;2.  3.    import javax.persistence.Basic;4. import javax.persistence.Column;5.    import javax.persistence.Entity;6.    import javax.persistence.GeneratedValue;7.    import javax.persistence.GenerationType;8.    import javax.persistence.Id;9.    import javax.persistence.Inheritance;10.  import javax.persistence.InheritanceType;11.  12.    /**13.    * BookExtend 用于表征系统中书的扩展信息,它有两个属性:         * id - 扩展信息编号,扩展信息编号将由 MySQL 数据库自动生成 14.   * name -书的前言信息15.     */

16.  @Entity17.   public class BookExtend {18.      /* Id 注释表示该字段是标识字段 */19.      @Id20.       /*21.      * GeneratedValue 注释定义了该标识字段的产生方式,我们的演示系统中22.       * id 由 MySQL 数据库字段自动生成,因此选择 GenerationType.IDENTITY23.         */24.        @GeneratedValue(strategy = GenerationType.IDENTITY)25.      /*26.      * Column 注释的 name 属性定义了该类属性对应的数据字段的名称,             * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符27.         */28.        @Column(name = "ID")29.       public int id;30. 31.       /* Basic 注释表示该属性是基本属性 */32.       @Basic33.        /*34.      * Column 注释的 name 属性定义了该类属性对应的数据字段的名称,             * 为了最大限度保持系统和数据库之前的独立性,建议使用大写字符35.         */36.        @Column(name = "NAME")37.     public String name = null;38.    }

调用代码

上面的代码中,我们已经准备好了符合要求的持久化类,下面我们看看 OpenJPA 中如何调用这两个类完成 Book 类和 BookExtend 类的创建、修改、删除工作。

由于篇幅的关系,这些没有讲述如何编译、加强这些类并且准备相应的配置文件来完成整个项目开发环境的建立,这部分的内容请参考另外一篇文章《OpenJPA:符合 EJB3 规范的持久层框架》。

级联新建对象

下面的这段代码演示了只需要调用 Book 类的 persist 方法就同时持久化 Book 类对象和 BookExtend 类对象的情况。请注意其中用粗体标识出的部分。

/* 获得 EJB 的实体管理器 */EntityManagerFactory emf = Persistence.createEntityManagerFactory(null);EntityManager em = emf.createEntityManager(PersistenceContextType.EXTENDED);/* 开始事务 */em.getTransaction().begin();

/* 创建新的 BookExtend 对象 */BookExtend bookExtend = new BookExtend();/* 设置对象属性 */bookExtend.name = "前言 本书重点说明了...";/* 创建新的 Book 对象 */Book book = new Book();/* 设置 Book 对象的 name 属性 */book.name = "<>";/* 建立对象之间的关系 */book.bookExtend = bookExtend;

/* 持久化对象,只需要持久化 Book 对象,不需要单独持久化 bookExtend 对象 */em.persist(book);

/* 结束事务 */em.getTransaction().commit();em.close();emf.close();

产生的 SQL 语句

下面的这段 SQL 语句是运行上面的代码时 OpenJPA 自动生成的,我们可以从中看到 OpenJPA 级联新建对象时的处理过程:

-- 创建 Book 实体对应的数据库表 --CREATE TABLE Book (ID INTEGER NOT NULL AUTO_INCREMENT, NME VARCHAR(255),    extendID INTEGER, PRIMARY KEY (ID));-- 创建 BookExtend 实体对应数据库表 --CREATE TABLE BookExtend (ID INTEGER NOT NULL AUTO_INCREMET,   NAME VARCHAR(255), PRIMARY KEY (ID)) ;-- 将 Book 实体对象插入数据库中 --INSERT INTO Book (NAME) VALUES (‘<>’)-- 获取 Book 实体对象的编号 --SELECT LAST_INSERT_ID();-- 将 BookExtend 实体对象插入数据库中 --INSERT INTO BookExtend (NAME) VALUES (‘前言 本书重点说明了...’) ;--获取 BookExtend 实体对象的编号  --SELECT LAST_INSERT_ID();-- 将 BookExtend 实体对象的编号更新到Book表中形成关联关系 --UPDATE Book SET extendID = 1 WHERE ID = 1;

级联更新对象状态

下面的这段代码演示了只需要调用 Book 类的 merge 方法就同时更新 Book 类对象和 BookExtend 类对象状态的情况。请注意其中用粗体标识出的部分。

/* 获得 EJB 的实体管理器 */EntityManagerFactory emf = Persistence.createEntityManagerFactory(null);EntityManager em = emf.createEntityManager(PersistenceContextType.EXTENDED);/* 开始事务 */em.getTransaction().begin();

/* 创建新的 Book 对象 */Book book = new Book();/* 设置 Book 对象的 id 属性 */book.id= 1;book.name = “OpenJPA入门”;/* 创建新的 BookExtend 对象 */BookExtend bookExtend = new BookExtend();/* 设置对象属性 */bookExtend.id=1;bookExtend.name = "OpenJPA开发EJB3.0应用 ...";/* 建立对象之间的关系 */book.bookExtend = bookExtend;

/* 持久化对象,只需要调用 Book 对象的 merge 方法,不需要单独处理 bookExtend 对象 */em.merge(book);

/* 结束事务 */em.getTransaction().commit();em.close();emf.close();

级联删除对象

下面的这段代码演示了只需要通过 Query 对象,就可以在删除 Book 类的同时,删除它对应的 BookExtend 实体对象的情况。请注意其中用粗体标识出的部分。

/* 获得 EJB 的实体管理器 */EntityManagerFactory emf = Persistence.createEntityManagerFactory(null);EntityManager em = emf.createEntityManager(PersistenceContextType.EXTENDED);/* 开始事务 */em.getTransaction().begin();/* 使用查询删除对象,可以不必将对象加入到内存中,提高效率 */Query q = entityManager.createQuery("delete from Book c WHERE c.id=:id");int id = book.id;/* 设置被删除 Book 对象的主键值 */q.setParameter("id", id);/* 当方法被调用时,Book 对象对应的 BookExtend 对象会同时被删除 */q.executeUpdate();

/* 结束事务 */em.getTransaction().commit();em.close();emf.close();

其它几种关联关系

在 上面的文章中我们学习了如何在 OpenJPA 中通过 javax.persistence.OneToOne 注释和 javax.persistence.JoinColumn 注释实现实体之间的一对一关联关系。在企业应用中,除了一对一关联关系,实体之间还可能存在一对多、多对一、多对多等关联关系,不过在 OpenJPA 容器中,这些实体之间关联关系的实现都大同小异,只是需要开发者选择使用不同的注释。

开发者用来描述实体之间 一对多关联关系的注释是 javax.persistence.OneToMany 注释,用来描述实体之间多对一关联关系的注释是 javax.persistence.OneToMany 注释,用来描述实体之间多对多关联关系的注释是 javax.persistence.ManyToMany 注释。这三个注释都支持 targetEntity、mappedBy、cascade 和 fetch 这四个属性,这些属性的具体含义和 OneToOne 注释注释的同名属性一一对应,请大家参考前面章节中的内容。

javax.persistence.OneToMany、 javax.persistence.OneToMany、javax.persistence.ManyToMany 这三个注释都可以和 javax.persistence.JoinColumns 注释一起使用,javax.persistence.JoinColumns 注释的作用是为一对多、多对一、多对多关联关系在数据库中的体现提供更多细节描述。javax.persistence.JoinColumns 注释中可以包含多个 javax.persistence.JoinColumn 注释的内容,javax.persistence.JoinColumn 注释的属性请参考本文前面部分的描述。

总结

对 象和对象之间除了继承关系之外,还存在着关联关系,包括一对一、一对多、多对一和多对多的关系,本文中,作者以实体之间的一对一关联关系为例,结合企业应 用中的实际例子,详细地描述了如何在 OpenJPA 框架下通过注释简单的描述实体和实体之间的关联关系,并且实现实体的级联操作。文章的最后简单的介绍了 OpenJPA 中实现实体之间一对多、多对一和多对多关联关系时需要用到的注释,这些注释的用法和描述一对一关联关系时大体一致,如何通过描述实现实体之间一对多、多对 一和多对多关联关系请大家参考本文中的内容自行完成。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130041/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/374079/viewspace-130041/

[转载]使用 Apache OpenJPA 开发 EJB 3.0 应用,第 4 部分: 实体关联相关推荐

  1. 使用 Apache OpenJPA 开发 EJB 3.0 应用,第 6 部分: 处理实体生命周期事件的回调

    企业应用开发过程中,经常会存在这样的需求:当企业应用中的某些数据被增加.删除.修改时,引发一些特定的动作,完成企业应用中的一些特别的要求,比如企业应用中要完成数据操作日志.处理数据之间的某种关系.或者 ...

  2. linux ejb远程调用,[转载]在容器外使用EJB 3.0 Persistence

    在容器外使用EJB 3.0 Persistence Lucas Jellema 最近写了篇文章,名字叫:Getting Started with EJB 3.0 Persistence out-of- ...

  3. Apache OpenJPA 2.1.0 发布

    OpenJPA 是 Apache 组织提供的开源项目,它实现了 EJB 3.0 中的 JPA 标准,为开发者提供功能强大.使用简单的持久化数据管理框架.OpenJPA 封装了和关系型数据库交互的操作, ...

  4. Apache PDFbox开发指南之PDF文档读取

    转载请注明来源:http://blog.csdn.net/loongshawn/article/details/51542309 相关文章: <Apache PDFbox开发指南之PDF文本内容 ...

  5. visual studio 2017 中默认无法开发 Android 8.0 及以上系统的解决方案

    一般默认比较旧有两个原因,系统版本过旧,Visual Studio 版本过旧. 第一步,将windows 更新到最新版,必须是windows 10 并且更新到最新. 第二步,将visual studi ...

  6. 服务器架设笔记——Apache模块开发基础知识

    通过上节的例子,我们发现Apache插件开发的一个门槛便是学习它自成体系的一套API.虽然Apache的官网上有对这些API的详细介绍,但是空拿着一些零散的说明书,是很难快速建立起一套可以运行的系统. ...

  7. docker mysql开发环境_Docker 构建PHP+Apache+MySQL 开发环境

    文章目錄 通过Docker 构建PHP+Apache+MySQL 开发环境,所有的服务(Apache,MySQL)和语言环境(PHP)都将在容器中运行,代码编写在宿主机内编写,并在宿主机的浏览器进行查 ...

  8. APACHE 2.2.8+TOMCAT6.0.14配置负载均衡

    作者: jiake0504  链接:http://jiake0504.javaeye.com/blog/265000  发表时间: 2008年11月10日 声明:本文系JavaEye网站发布的原创博客 ...

  9. Apache模块开发

    一.简介 Apache HTTP服务器是一个模块化的软件,使管理者可以选择核心中包含的模块以裁剪功能.可以在编译时选择被静态包含进httpd二进制映象的模块,也可以编译成独立于主httpd二进制映象的 ...

最新文章

  1. 《预训练周刊》第16期:中科院推出全球首个图文音三模态预训练模型、顶会论文探索100层序列推荐模型的加速训练...
  2. Python中format_map与format字符串格式化的区别
  3. Linux C编程--进程间通信(IPC)4--管道详解
  4. 在openshift上自定义node.js的版本
  5. Material Design
  6. 网贷,高利贷,套路贷为什么必须铲除?
  7. SetInterval(循环计时器)
  8. raiden_graph
  9. mysql数据库自动添加编号_mysql数据库自动添加编号
  10. 转载+收藏 数理化地生常用软件
  11. 我国中药产业的国际竞争力研究
  12. pr剪辑打开多个项目_Pr入门之十一:基本图形面板
  13. 打造敏捷的自组织团队
  14. CSS属性之线性渐变实现透明度渐变和重复渐变效果
  15. FPGA综合系统设计(四):串口控制的DDS信号发生器
  16. 从 160 万到 1.5 亿美元 ,开源软件迎来融资热潮
  17. 三星android手机工程模式设置中文,三星工程模式怎么进入?三星手机进入工程模式方法...
  18. 常见前端面试题及答案-转载
  19. 鸿蒙系统源代码解析,鸿蒙内核源码分析(系统调用篇) | 图解系统调用全貌
  20. 手机拍照技巧(一:校园拍摄)

热门文章

  1. Windows-空硬盘安装系统
  2. 详解注意力机制和Transformer
  3. python作业02
  4. 【echarts地图制作】下钻到乡镇/街道级别的
  5. C语言中的fgets函数
  6. Python+OpenCV图像处理与识别 Step by Step
  7. 简单理解什么是虚拟存储器
  8. 《策略驱动型数据中心——ACI技术详解》一1.3 数据中心设计
  9. vscode切换中英文
  10. 西门子S7系列PLC安全防护研究