JPA 实体和数据库关系
美好的一天,同事们!什么是实体?
实体是现实生活中的对象(例如汽车),具有属性(门、车轮、发动机)。DB实体:在这种情况下,我们的实体存储在数据库中,一切都很简单。我们为什么以及如何将汽车放入数据库 - 我们稍后会看到。什么是数据库关系?
很久以前,在遥远的王国,创建了一个关系型数据库。在该数据库中,数据以表格的形式呈现。但即使是史莱克的驴子也明白,有必要创建一种机制来互连这些桌子。结果出现了4个DB关系: 如果您是第一次看到这一切,我再次警告您 - 情况会变得更糟:考虑出去散步。我们将通过一个例子来分析所有这些关系,并理解它们之间的区别。恐怖的例子
我们将有一个项目,该项目将有 5 个分支:master,其中将有项目的描述,以及每个数据库关系的 1 个分支。每个分支将包含用于创建数据库并用测试数据填充它的 SQL 脚本,以及带有注释映射的实体类。每个分支还将有一个 Hibernate 配置文件。我将在项目中使用H2嵌入式数据库,以免被云数据库或外部数据库的各个方面分散注意力。通过点击链接,在吸尘器上安装 H2 DB。我将在1个分支中描述每个步骤,其余的只是要点。最后我们来总结一下。去。 这是我项目的主分支的链接。一对一的关系
链接到此处的分支。-
我们需要将 H2 DB 连接到我们的项目。这里我们需要强调的是,我们需要Ultimate IDEA才能轻松地与DB和其他东西一起工作。如果已经有了,那么直接进入数据库连接。转到数据库选项卡并按照屏幕截图中的操作进行操作:
接下来我们继续进行数据库设置。您可以输入您的数据,甚至您的 DBMS;我重复一遍,为了简单起见,我使用 H2 DB。
接下来,让我们设置电路。此步骤是可选的,但如果数据库中有多个架构,则建议执行此步骤。
应用设置,最后我们应该得到这样的结果:
-
我们创建了数据库并配置了从 IDEA 对其的访问。现在您需要在其中创建表并填充一些数据。例如,我将采用两个实体:作者和书籍。一本书可能有一个作者,可能有多个作者,也可能没有一个作者。在此示例中,我们将创建所有类型的连接。但此时——一对一的关系。让我们创建相应的脚本来创建数据库表:
DROP TABLE IF EXISTS PUBLIC.BOOK; CREATE TABLE PUBLIC.BOOK ( ID INTEGER NOT NULL AUTO_INCREMENT, NAME VARCHAR(255) NOT NULL, PRINT_YEAR INTEGER(4) NOT NULL, CONSTRAINT BOOK_PRIMARY_KEY PRIMARY KEY (ID) ); DROP TABLE IF EXISTS PUBLIC.AUTHOR; CREATE TABLE PUBLIC.AUTHOR ( ID INTEGER NOT NULL AUTO_INCREMENT, FIRST_NAME VARCHAR(255) NOT NULL, SECOND_NAME VARCHAR(255) NOT NULL, BOOK_ID INTEGER NOT NULL UNIQUE, CONSTRAINT AUTHOR_PRIMARY_KEY PRIMARY KEY (ID), CONSTRAINT BOOK_FOREIGN_KEY FOREIGN KEY (BOOK_ID) REFERENCES BOOK (ID) );
让我们执行它:
控制台执行结果:
数据库中的结果:
-
让我们看一下表格的图表。为此,我们的数据库上的人民币:
结果:
在UML图上我们可以看到所有的主键和外键,我们还可以看到表之间的连接。
-
让我们编写一个脚本,用测试数据填充我们的数据库:
INSERT INTO PUBLIC.BOOK (NAME, PRINT_YEAR) VALUES ('First book', 2010), ('Second book', 2011), ('Third book', 2012); INSERT INTO PUBLIC.AUTHOR (FIRST_NAME, SECOND_NAME, BOOK_ID) VALUES ('Pablo', 'Lambado', 1), ('Pazo', 'Zopa', 2), ('Lika', 'Vika', 3);
我的意思是,会发生什么?当一个表的实体与另一个表的一个实体相关(或者如果从 BOOK_ID 中删除 NOT NULL 则根本不相关)时,需要一对一关系。在我们的示例中,一本书必须有一位作者。别无退路。
-
现在最有趣的是如何将Java类与DB实体连接起来?很简单。让我们创建两个类 Book 和 Author。我将通过一个例子来分析第一类和关键的通信领域。我们以Author类为例:
@Data @Entity @DynamicInsert @DynamicUpdate @Table(name = "AUTHOR") public class Author { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID", nullable = false) private Long id; @Column(name = "FIRST_NAME", nullable = false) private String firstName; @Column(name = "SECOND_NAME", nullable = false) private String secondName; @OneToOne @JoinColumn(name = "BOOK_ID", unique = true, nullable = false) private Book book; }
- 类中的所有字段都重复数据库实体的属性。
- @Data(来自Lombok)表示,对于每个字段,将创建一个 getter 和 setter,equals、hashcode 将被覆盖,并生成一个 toString 方法。
- @Entity表示给定的类是一个实体并与数据库实体关联。
- @DynamicInsert和@DynamicUpdate表示动态插入和更新将在数据库中执行。这些是更深层次的 Hibernate 设置,对您有用,以便您获得正确的批处理。
- @Table (name = "AUTHOR") 将 Book 类绑定到 DB AUTHOR 表。
- @Id表示该字段是主键。
- @GenerateValue (strategy = GenerationType.IDENTITY) – 主键生成策略。
- @Column (name = "ID", nullable = false) 将一个字段与一个 DB 属性关联起来,同时也表示给定的 DB 字段不能为 null。从实体生成表时这也很有用。与我们现在创建项目的方式相反的过程,这在单元测试的测试数据库中是必需的。
- @OneToOne表示给定字段是一对一关系字段。
- @JoinColumn (name = "BOOK_ID", unique = true, nullable = false) - 将创建一个 BOOK_ID 列,该列是唯一的且不为空。
-
现在让我们配置 Hibernate。为此,请创建一个hibernate.cfg.xml文件:
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> <property name="hibernate.connection.driver_class">org.h2.Driver</property> <property name="hibernate.connection.url">jdbc:h2:~/db/onetoone</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password"/> <property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="hibernate.use_sql_comments">true</property> <property name="hibernate.generate_statistics">true</property> <property name="hibernate.jdbc.batch_size">50</property> <property name="hibernate.jdbc.fetch_size">50</property> <property name="hibernate.order_inserts">true</property> <property name="hibernate.order_updates">true</property> <property name="hibernate.jdbc.batch_versioned_data">true</property> <mapping class="com.qthegamep.forjavarushpublication2.entity.Book"/> <mapping class="com.qthegamep.forjavarushpublication2.entity.Author"/> </session-factory> </hibernate-configuration>
- hibernate.dialect 是我们选择的 DBMS 的方言。
- hibernate.connection.driver_class - 我们的数据库的驱动程序类。
- hibernate.connection.url - 我们数据库的 utl。你可以从我们配置数据库的第一点开始。
- hibernate.connection.username - 数据库用户名。
- hibernate.connection.password — 数据库用户密码。
- hibernate.hbm2ddl.auto - 设置表生成。如果是update,那么如果已经创建了就不会生成,而只是更新它。
- hibernate.show_sql - 是否显示数据库查询。
- hibernate.format_sql - 是否格式化数据库查询。如果没有,那么他们都会在一条线上。我建议打开它。
- hibernate.use_sql_comments - 注释数据库查询。如果这是一个Insert,则在请求上方写一条注释,表明该请求是Insert类型的。
- hibernate.generate_statistics - 生成日志。我推荐并建议将日志记录设置为最大。阅读日志将增加您正确使用 ORM 的机会。
- hibernate.jdbc.batch_size — 最大批量大小。
- hibernate.jdbc.fetch_size — 最大获取大小。
- hibernate.order_inserts - 允许动态插入。
- hibernate.order_updates - 允许动态更新。
- hibernate.jdbc.batch_versioned_data - 允许批处理。看看你的 DBMS:不是每个人都支持这一点。
- 映射类 - 作为我们实体的类。所有的事情都需要列出来。
-
现在我们必须确定我们的本质。我们可以在持久性选项卡中检查这一点:
结果:
-
我们还需要配置分配数据:
结果:我们完成了一对一映射。该材料仅供参考,详细信息请参见参考资料。
一对多关系
链接到此处的分支。文章中的代码我就不再贴出来了,因为代码已经太长了。我们查看 GitHub 上的所有代码。-
执行初始化脚本的结果是:
你感觉和上一张表有什么不同吗?
-
图表:
一对多关系 - 一位作者可以拥有多本书。左侧实体对应于一个或多个右侧实体。
-
映射的区别在于注释和字段:
Author类中出现一个字段:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author") private Set<Book> books;
这已经是一套了,因为我们可以有几本书。@OneToMany正在谈论态度类型。FetchType.Lazy 表示如果请求中未指定,我们不需要加载整个图书列表。还应该说的是,这个字段不能添加到 toString 中,否则我们将开始产生 StackOverflowError。我心爱的 Lombok 负责处理这个问题:
@ToString(exclude = "books")
在Book类中,我们进行多对一反馈:
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "AUTHOR_ID", nullable = false) private Author author;
在这里我们得出结论,一对多是多对一的镜像,反之亦然。应该强调的是,Hibernate 对双向通信一无所知。对他来说,这是两种不同的联系:一种是同向的,另一种是相反的。
-
hibernate.cfg.xml中没有发生太大变化。
-
持久性:
多对一关系
由于多对一是一对多的镜像,因此几乎没有差异。链接到此处的分支。多对多关系
让我们继续讨论最有趣的关系。根据所有正派和不正派规则,这种关系是通过一个附加表创建的。但这个表不是一个实体。有趣,对吧?我们来看看这个狗屎。链接到此处的分支。-
查看初始化脚本,这里出现了一个额外的HAS表。我们得到类似作者有书的东西。
执行脚本的结果是,我们将得到下表:
-
图表:
在我们的例子中,事实证明,一本书可以有很多作者,而一个作者可以有很多书。它们可能会重叠。
-
映射类将在类内有集合。但正如我所说,HAS 表不是一个实体。
作者类别:
@ManyToMany @JoinTable(name = "HAS", joinColumns = @JoinColumn(name = "AUTHOR_ID", referencedColumnName = "ID"), inverseJoinColumns = @JoinColumn(name = "BOOK_ID", referencedColumnName = "ID") ) private Set<Book> books;
@ManyToMany是一种关系类型。
@JoinTable - 这正是将属性与附加 HAS 表连接起来的方法。在其中我们指定两个属性,它们将指向两个实体的主键。
图书类别:
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "books") private Set<Author> authors;
在这里,我们指示 FetchType 和我们将用于映射的字段。
-
我们的hibernate.cfg.xml再次保持不变(我没有考虑我们为每个分支创建一个新数据库的事实)。
-
持久性:
GO TO FULL VERSION