利用Hibernate开发一个一对一的映射应用程序
本教程适用于那些对使用hibernate的工作知之甚少或一无所知、需要减少某个项目中编写的SQL查询的Java开发人员。对于那些想了解对象关系映射是如何实现的Hibernate爱好者来说,它也很有用。
简介
再加上它提供的所有不同的功能,以缓解开发过程。Hibernate是由Gavin King和Cirrus Technologies的同事于2001年开始的,作为使用EJB2风格的实体豆的替代方案。最初的目标是提供比EJB2更好的持久化能力。
据报道,有173家公司在其技术栈中使用Hibernate,包括Platform、Trendyol Group和WealthSiple。
在本教程中,读者将学习如何创建一个一对一的关系应用程序,其中客户只能有一个订阅。
目录
- 设置开发环境。
- 创建数据库并配置hibernate配置文件。
- 创建实体。
- 创建并执行主应用程序。
先决条件
使用面向对象编程的基本Java知识,如何实现实体之间的组合,SQL命令,MySQLshell/bash和Intellij,但可以自由使用任何首选环境。
第1步:设置开发环境
成功运行应用程序需要Java开发工具包,在您的计算机上安装Java 11。
安装MySQL服务器。
成功安装MySQL并设置数据库凭证后,使用以下命令登录MySQL。
mysql -u username -p
提示将询问您的密码,输入后,访问权将被授予,显示以下信息。

Maven是一种用于管理Java项目的构建工具。Maven基于项目对象模型(POM),这是一个包含配置细节和项目信息的XML文件。
依赖关系可以从maven中央仓库获得,并添加到POM文件中,maven将负责源代码的生成、代码编译和编译后的JAR文件打包。
这将发生在本地资源库中,在创建新的maven项目时可以访问这些资源。所需的依赖项将由maven自动下载,并在POM文件中加入后添加到项目的classpath中。
Eclipse通过原型提供了一种更简单的方法来创建maven项目。原型用于创建新的maven项目,其中包含特定maven项目的模板文件。你可以把它看成是Java或Web项目的启动文件的集合。
常见的原型有。
- Maven-archetype-quickstart - 这是一个原型,用于生成适合独立项目的maven项目样本。
- maven-archetype-webapp - 这是一种原型,用于生成Java网络应用程序的maven示例项目。
在Eclipse中,点击文件、新建、其他、maven,然后点击maven项目。按下一步,选择maven-archetype-quickstart,在打开的窗口中填写组ID和工件ID,分别代表包和项目名称。
点击 "完成",生成一个新的maven项目,其中包含独立应用中使用的所有文件。
在IntelliJ中点击文件、新建、项目,在打开的窗口中选择maven,然后点击下一步,给项目取名。这将创建一个新的maven项目,其结构如下。

在名为pom.xml的文件中,在属性结束标签后添加以下依赖项。
<dependencies>
<!-- Hibernate's core ORM functionality -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.3.6.Final</version>
</dependency>
<!-- JDBC driver for MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<!-- Support for Java 9/10/11 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
</dependencies>
第2步:创建数据库和配置hibernate配置文件
使用以下命令为该应用程序创建一个新的数据库。
create database database_name;
在IntelliJ中,在资源下创建一个新文件,命名为hibernate.cfg.xml或任何喜欢的名称,并添加以下属性。
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- JDBC Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/database_name?useSSL=false&serverTimezone=UTC</property>
<property name="connection.username">username</property>
<property name="connection.password">password</property>
<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">10</property>
<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<!-- Echo the SQL to stdout -->
<property name="show_sql">true</property>
<!-- Set the current session context -->
<property name="current_session_context_class">thread</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
- 第一部分包含数据库的连接细节,包括数据库URL、用户名和密码。
- 连接池可以最大限度地减少应用程序和数据库之间打开的连接数。
- Dialect指定了hibernate中使用的数据库类型,这样hibernate就会生成适当类型的SQL语句。
- show_sql将把所有SQL语句写到控制台。
- current_session_context_class 设置实现当前会话上下文的类。
- 更新类型的hibernate.hbm2dl.auto将自动在数据库中创建表,如果表在第一次执行时不存在,那么它将在实体被创建后更新记录,维护数据。
第3步:创建实体
创建一个名为customer的类,其细节如下。
- id
- 名
- 姓氏
- 电子邮件
- 构造函数,除了id之外,所有的字段都将被生成。
- toString方法来查看对象实例的数据。
import javax.persistence.*;
@Entity
@Table(name = "customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@Column(name = "email")
private String email;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "sub_id")
private Subscription subscription;
public Customer(String firstName, String lastName, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Customer() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Subscription getSubscription() {
return subscription;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
'}';
}
}
- @Entity - 将该类标记为Hibernate要创建的数据库表。
- @Table(name="customer") - 告诉hibernate表的名称将被称为customer。
- @Id - 当前实体的主键。
- @GeneratedValue - 主键的自动递增策略。
- @Column - 表示将由Hibernate在数据库中创建的字段的名称。
- @OneToOne(cascade = CascadeType.ALL) - 这表明一个客户有一个订阅,级联类型为all,告诉hibernate对客户进行的CRUD操作也应该级联到订阅。例如,如果一个客户被删除,他们的订阅也应该被删除。
- @JoinColumn(name = "sub_id") - 告诉hibernate在客户表中寻找列sub_id,并使用该信息为客户找到合适的订阅。
在创建完客户类后,创建一个订阅类,其细节如下。
- id
- 名称
- 价格
- 描述
- 构造函数中没有id
- toString方法
import javax.persistence.*;
@Entity
@Table(name = "subscription")
public class Subscription {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@Column(name = "name")
private String name;
@Column(name = "price")
private Double price;
@Column(name = "description")
private String descrition;
@OneToOne(cascade = {
CascadeType.MERGE,
CascadeType.PERSIST,
CascadeType.REFRESH,
CascadeType.DETACH
},mappedBy = "subscription")
private Customer customer;
public Subscription() {
}
public Subscription(String name, Double price, String descrition) {
this.name = name;
this.price = price;
this.descrition = descrition;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public String getDescrition() {
return descrition;
}
public void setDescrition(String descrition) {
this.descrition = descrition;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "Package{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
", descrition='" + descrition + '\'' +
'}';
}
}
CascadeType.REMOVE被省略了,以避免一旦订阅被删除就删除客户,以展示级联的作用。
mappedBy = "subscription" - 添加的目的是将关系标记为双向的,这意味着当一个订阅被加载时,客户可以被识别,反之亦然。
第4步:创建和执行主应用程序
在这个阶段,将涵盖以下内容。
- 创建一个新的客户。
- 通过ID检索客户,并使用setter方法为客户分配一个新的订阅。
- 通过id检索客户并删除,以看到应用于订阅的级联删除。
- 使用setter方法检索和更新一个客户。
创建一个新的客户
import com.javadev.hibernate.demo.entity.Customer;
import com.javadev.hibernate.demo.entity.Subscription;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class CreateCustomer {
public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Customer.class)
.addAnnotatedClass(Subscription.class)
.buildSessionFactory();
//create session
Session session = factory.getCurrentSession();
try {
//create the object
Customer tempCustomer = new Customer("john","doe","john@javadev.com");
//start a transaction
session.beginTransaction();
//save the object
System.out.println("Saving the customer");
session.persist(tempCustomer);
//commit the transaction
session.getTransaction().commit();
System.out.println("Done!!");
}finally {
//clean up code
session.close();
factory.close();
}
}
}
当主程序被执行时,将创建一个表customer和subscription,并在customer表中插入一条新行,使用下面的命令可以看到创建的表和插入的行。
use database_name;
show tables;
select * from customer;
新的客户条目将由查询返回,注意sub_id为空。

通过id检索客户,并使用setter方法为客户分配一个新的订阅。
import com.javadev.hibernate.demo.entity.Customer;
import com.javadev.hibernate.demo.entity.Subscription;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class CreateCustomerSubscription {
public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Customer.class)
.addAnnotatedClass(Subscription.class)
.buildSessionFactory();
//create session
Session session = factory.getCurrentSession();
try {
int theId =1;
Subscription subscription = new Subscription("Beginner",1400.00,"This subscription is for beginners");
//start a transaction
session.beginTransaction();
//read a customer
Customer customer = session.get(Customer.class,theId);
customer.setSubscription(subscription);
//save the object
System.out.println("Saving the customer");
session.persist(customer);
//commit the transaction
session.getTransaction().commit();
System.out.println("Done!!");
}finally {
//clean up code
session.close();
factory.close();
}
}
}
一条新的记录将被插入到订阅表中,客户sub_id列将被更新为外键id,指的是客户被设置到的订阅。这是因为CascadeType.ALL被设置在客户类中。

通过ID检索客户并删除,以看到应用于订阅的级联删除。
import com.javadev.hibernate.demo.entity.Customer;
import com.javadev.hibernate.demo.entity.Subscription;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class DeleteSubscription {
public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Customer.class)
.addAnnotatedClass(Subscription.class)
.buildSessionFactory();
//create session
Session session = factory.getCurrentSession();
try {
int theId =1;
session.beginTransaction();
//read a subscription
Subscription subscription = session.get(Subscription.class,theId);
subscription.getCustomer().setSubscription(null); //brakes the bidirectional link of customer and subscription
session.delete(subscription);
//commit the transaction
session.getTransaction().commit();
System.out.println("Done!!");
}finally {
//clean up code
session.close();
factory.close();
}
}
}
请注意,因为在customer和subscription之间有一个双向的链接,所以要删除一个subscription,链接必须被破坏。
由于CascadeType.REMOVE在订阅类中被遗漏,删除一个订阅将保持客户。

订阅表将返回一个空集,而客户表的列sub_id将被更新为null。
使用setter方法检索和更新一个客户
import com.javadev.hibernate.demo.entity.Customer;
import com.javadev.hibernate.demo.entity.Subscription;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class UpdateCustomer {
public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Customer.class)
.addAnnotatedClass(Subscription.class)
.buildSessionFactory();
//create session
Session session = factory.getCurrentSession();
try {
//start a transaction
session.beginTransaction();
int customerId =1;
//read a customer
Customer tempCustomer = session.get(Customer.class,customerId);
//update customer last name
tempCustomer.setLastName("bilal");
//save the object
System.out.println("Saving the customer");
session.persist(tempCustomer);
//commit the transaction
session.getTransaction().commit();
System.out.println("Done!!");
}finally {
//clean up code
session.close();
factory.close();
}
}
}

总结
通过覆盖和实现这些操作,读者应该更有信心接近一对一的Hibernate任务并按要求交付。
至此,读者已经学会了如何通过应用一对一的映射策略,使用hibernate创建、读取、更新和删除数据。