Hibernate - 通过 Join-Table 实现一对多映射
详细介绍 Hibernate 如何使用 MySQL 和 Spring 实现多对多关联关系
引言
最近,我正在开发一个小项目,需要在两个实体之间进行多对多映射。网上有很多例子。但其中大部分都没有解释我必须解决的一些棘手问题。这促使我写下这篇教程——让读者了解我必须处理的一些问题。事实是,一旦我解决了所有问题,这是一种表示两种实体类型之间多对多关系的绝佳方式。本教程将向您展示这是多么棒,以及如何做到。
那么,什么是多对多关系?想象一下,你有一个博客,并且你想管理上传到博客的照片。你通过画廊(galleries)来给照片分类。可能有五张照片(image-1 到 image-5),以及两个画廊(gal-1,gal-2)。照片 image-1、image-3 和 image-4 在 gal-1 中;image-2、image-3、image-4 和 image-5 在 gal-2 中。正如你所见,照片 image-3 和 image-4 同时存在于两个画廊中。这些关联可以被识别为多对多。我过去曾避免如此复杂的关系,只处理直接映射两个实体的情况,使用一对一或一对多。对于这种多对多关系,可以使用三个表来实现,并使用某种类型的一对多映射(画廊与 `join` 表之间是一对多,图像与 `join` 表之间也是一对多)。但现在,我意识到这种复杂的映射可能不是一个好主意。我特别担心我必须向后端数据库发出的显式 SQL 调用数量。通过正确的多对多映射,我认为 Hibernate 可以帮助我简化我感兴趣的操作。
那么,我感兴趣的操作有哪些呢?好吧,有以下几项:
- 我希望创建画廊,上传照片,然后将照片关联到画廊。
- 我希望删除画廊或照片。执行这些操作时,我不必在删除之前显式地移除关联。
- 我希望在画廊中查找所有照片并进行分页。
- 我希望添加或删除画廊和照片之间的关联,但又不删除画廊或照片。
背景
听起来很简单。我们如何用纯 SQL 脚本来实现这些?好吧,我可以向 gallery 表插入一行,向 image 表插入另一行。最后,我为这两个实体向 `imagetogallery`(这是 `join` 表)添加一行。现在,如果我删除 gallery 或 image 行,SQL 数据库可以自动删除 `join` 表中的行。我也可以删除 `join` 表中的行,从而断开 image 和 gallery 之间的关系。如果我想查找画廊中的所有图像,只需一个包含两个内连接(inner join)的查询即可。
为了说明我的操作,下面是我将要创建的测试表(顺便说一句,我使用的是 MySQL):
DROP TABLE IF EXISTS imagetogallery;
DROP TABLE IF EXISTS gallery;
DROP TABLE IF EXISTS image;
CREATE TABLE image (
id int NOT NULL PRIMARY KEY,
filepath VARCHAR(256) NULL
);
CREATE TABLE gallery (
id int NOT NULL PRIMARY KEY,
name VARCHAR(128) NULL
);
CREATE TABLE imagetogallery (
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
imageid int NOT NULL,
galleryid int NOT NULL,
FOREIGN KEY (galleryid) REFERENCES gallery(id)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (imageid) REFERENCES image(id)
ON DELETE CASCADE
ON UPDATE CASCADE
);
前三行基本上是在已存在时删除表。gallery 表和 image 表各有两列,第一列是主键“`id`”。“`id`”的类型是整数,不能为 `NULL`。为了方便我测试,我将在我的 SQL 测试和我的 Hibernate 启用的 Java 程序中显式设置“`id`”值。最后一个表 `imagetogallery` 更加复杂。它有一个主键“`id`”,其值设置为自动递增。当开始使用 Hibernate 时,提供这个 `join` 表的新 id 值在插入行时非常重要。我将在后面解释这一点。该 `join` 表还有两个外键,一个指向 `gallery` 表,一个指向 `image` 表。这两个外键都有 `update` 和 `delete` 的级联操作。这对于运行 SQL 语句或使用 Hibernate 也很重要。同样,当我到达那里时,我会解释原因。
创建这些表后,我认为最好使用纯 SQL 语句对设置进行一些模拟。我做的第一件事是:
INSERT INTO gallery (id, name) VALUES (1, 'My Gallery');
INSERT INTO image (id, filepath) VALUES (2, 'c://images//testimg1.jpg');
INSERT INTO image (id, filepath) VALUES (3, 'c://images//testimg2.jpg');
INSERT INTO imagetogallery (imageid, galleryid) VALUES (2, 1);
INSERT INTO imagetogallery (imageid, galleryid) VALUES (3, 1);
上面的代码片段将在 gallery 表中创建一个行,在 image 表中创建两行,然后将这两行图像与 gallery 行关联起来。接下来,我想确保我可以通过查询找到属于 id 等于 1 的画廊的所有图像。这是我的查询:
SELECT image.* FROM image
INNER JOIN imagetogallery ON image.id = imagetogallery.imageid
WHERE imagetogallery.galleryid = 1
该查询应该成功并产生以下输出:
id | filepath |
2 | c://images//testimg1.jpg |
3 | c://images//testimg2.jpg |
接下来,我将尝试删除 `image` 表中的一行,例如 id 等于 `2`。这可以通过以下 SQL 语句完成:
DELETE FROM image WHERE image.id = 2;
我使用相同的查询来查找 id 等于 1 的画廊的图像。查询返回:
id | filepath |
3 | c://images//testimg2.jpg |
发生了什么?嗯,我提到过,当我讲到 CASCADE `delete` 和 `update` 时,我会解释它们的作用以及它们为什么重要。现在就是时候了。当我创建 `join` 表时,我不需要声明 CASCADE `delete` 或 `update`。然后,如果我在 image 表上执行 `delete`,我会收到一个错误,表明由于外键约束冲突,操作将失败。原因是 `join` 表中有一行引用了我正要删除的图像。为了纠正这个错误,我必须先删除 `join` 表中引用了该图像的行,然后才能删除该图像。这相当麻烦。现在有了外键的 CASCADE `delete`,我只需要在 image 或 gallery 表中删除一行,而 `join` 表中引用了 gallery 或 image 的行将自动被删除。哇!这真是革命性的!那么 CASCADE `update` 是做什么的呢?想象一下,假设我必须更新 gallery 或 image 的 `id` 值。这是主键的更新(危险!)它可能会因错误而失败,因为 `join` 表中的一行或多行可能引用了此图像。但这种情况确实会发生,而且可以做到。有了 CASCADE update,我可以更新该图像的 id(只要我选择的 id 尚未在 image 表中使用)。数据库引擎将自动更新 `join` 表中的 `imageid`。实际上,如果我想对 gallery 执行此操作,也可以在不手动更新 `join` 表的情况下进行。神奇!我喜欢它!
通过所有这些测试,我对表设计感到满意。现在我想将所有这些移到一个 Hibernate 应用程序中。这没什么特别的,只是一个普通的 Java 控制台应用程序。
Hibernate 应用程序
我从一个旧的 Spring 程序中提取了一些代码,并将其转换成了这个应用程序。该程序具有以下文件结构:
project base directory
|____DB
|____table1.sql
|____logs
|____{nothing in this folder}
|____src
|____main
|____java
|____org
|____hanbo
|____hibernate
|____experiment
|____entities
|____Gallery.java
|____Image.java
|____ImageGalleryRepository.java
|____Main.java
|____resources
|____application-context.xml
|____log4j.properties
|____pom.xml
步骤 1 -- POM 文件
我将首先展示 `pom.xml`。在此文件中,它包含了我的实验应用程序所需的所有依赖项。它看起来像这样:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.hanbo.hibernate-experiment</groupId>
<artifactId>hibernate-manytomany</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>hibernate-manytomany</name>
<url>http://maven.apache.org</url>
<properties>
<spring.version>3.2.11.RELEASE</spring.version>
<slf4j.version>1.7.5</slf4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.2.15.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<version>3.1.4.GA</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.29</version>
</dependency>
</dependencies>
<build>
<finalName>hiberfnate-manytomany</finalName>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Spring 框架中的部分用于依赖注入、与 Hibernate 的集成以及数据库事务。Apache Commons 中的部分用于日志记录、便利性和连接池。我专门使用 Log4J 进行日志记录。其他部分是 MySQL JDBC 驱动程序、Hibernate 使用的 JBoss 日志、Hibernate 库、JPA 注解和 javax 事务。
要构建此应用程序,您只需:
mvn clean install
步骤 3 -- 实体类
为了让所有场景都能与 Hibernate 一起工作,我需要定义我的实体。因为我使用 `join` 表进行多对多关联。我只需要定义两个实体。`Gallery` 的实体和 `Image` 的实体。`join` 表在两个实体中是隐式定义的。正如您所见,使用 `join` 表,我实际上不需要定义三个实体,只需要两个。然后我就可以让 Hibernate 和数据库为我处理繁重的工作。
让我向您展示 `Image` 实体的 Java 代码。代码如下:
package org.hanbo.hibernate.experiment.entities;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "image")
public class Image
{
@Id
@Column(name = "id", nullable = false)
private int id;
@Column(name = "filepath", nullable = true, length = 256)
private String filePath;
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "associatedImages")
private Set<Gallery> associatedGalleries;
public Image()
{
associatedGalleries = new HashSet<Gallery>();
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getFilePath()
{
return filePath;
}
public void setFilePath(String filePath)
{
this.filePath = filePath;
}
public Set<Gallery> getAssociatedGalleries()
{
return associatedGalleries;
}
public void setAssociatedGalleries(Set<Gallery> associatedGalleries)
{
this.associatedGalleries = associatedGalleries;
}
}
这个类中最重要的一部分是多对多注解:
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "associatedImages")
private Set<Gallery> associatedGalleries;
您可能会问,“associatedImages”在哪里?它位于 `Gallery` 实体类中。`mappedBy` 的思想是让 `Image` 实体查找 `Gallery` 实体定义并找到图像集合。`Gallery` 实体中的这个集合属性称为“`associatedImages`”。现在,让我分享 `Gallery` 实体定义的源代码:
package org.hanbo.hibernate.experiment.entities;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name = "gallery")
public class Gallery
{
@Id
@Column(name = "id", nullable = false)
private int id;
@Column(name = "name", nullable = true, length = 128)
private String name;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "imagetogallery", joinColumns = {
@JoinColumn(name = "galleryid",
nullable = false, updatable = false)
}, inverseJoinColumns = {
@JoinColumn(name = "imageid",
nullable = false, updatable = false)
}
)
private Set<Image> associatedImages;
public Gallery()
{
setAssociatedImages(new HashSet<Image>());
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Set<Image> getAssociatedImages()
{
return associatedImages;
}
public void setAssociatedImages(Set<Image> associatedImages)
{
this.associatedImages = associatedImages;
}
}
正如您所见,类中最复杂的部分是以下内容,它实际上使用我创建的 `join` 表定义了多对多关联:
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "imagetogallery", joinColumns = {
@JoinColumn(name = "galleryid",
nullable = false, updatable = false)
}, inverseJoinColumns = {
@JoinColumn(name = "imageid",
nullable = false, updatable = false)
}
)
private Set<Image> associatedImages;
有几件事您需要了解:
- `CascadeType.All` 是我使用的级联类型,这意味着当我更改 `Image` 实体或 `Gallery` 实体时,所有更改都必须传播(级联效应)到 `join` 表。
- `JoinTable` 注解定义了我创建的 `join` 表。
- `JoinTable` 注解还定义了 `join` 列,一个用于 `galleryid`,一个用于 `imageid`。这些是 `join` 表中的表列。
- 第一个 `join` 列是 `join` 表中的 `galleryid`。第二个是反向 `join` 列 `imageid`。这两者告诉 `Gallery` 实体如何在 `join` 表中找到自身以及与画廊关联的图像。
- `join` 列有两个属性。一个是 `nullable`,我将其设置为 `false`,表示该列的值不能为 `null`。另一个是 `updatable`,我将其设置为 `false`。这些列中的值是其他表的 `primary key`,允许它们更新是不明智的。
步骤 4 -- Repository 类
我还创建了一个 repository 类,以便我可以测试场景。`repository` 类与实体位于同一包中。该类看起来像这样:
package org.hanbo.hibernate.experiment.entities;
import java.util.List;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@SuppressWarnings("unchecked")
public class ImageGalleryRepository
{
private static Logger _logger = LogManager.getLogger(ImageGalleryRepository.class);
@Autowired
private SessionFactory _sessionFactory;
@Transactional
public void deleteAll()
{
Session session = _sessionFactory.getCurrentSession();
Query q = session.createQuery("delete from Gallery");
q.executeUpdate();
q = session.createQuery("delete from Image");
q.executeUpdate();
}
@Transactional
public int testPersistence()
{
Session session = _sessionFactory.getCurrentSession();
Gallery gallery = new Gallery();
gallery.setId(1);
gallery.setName("My Test Gallery");
Image img1 = new Image();
img1.setId(2);
img1.setFilePath("C:\\testimages\\img1.jpg");
gallery.getAssociatedImages().add(img1);
Image img2 = new Image();
img2.setId(3);
img2.setFilePath("C:\\testimages\\img2.jpg");
gallery.getAssociatedImages().add(img2);
session.save(gallery);
return gallery.getId();
}
@Transactional
public void testPersistence2()
{
Session session = _sessionFactory.getCurrentSession();
Query query = session.createQuery(
"select image from Gallery gallery join gallery.associatedImages image"
+ " where gallery.id = :galId order by image.id desc"
).setParameter("galId", 1).
setMaxResults(2);
List<Image> imgs = query.list();
for(Image image : imgs)
{
_logger.info(String.format("Image Id: %s", image.getId()));
_logger.info(String.format("Image File Path: %s", image.getFilePath()));
}
}
@Transactional
public void testPersistence3()
{
Session session = _sessionFactory.getCurrentSession();
Gallery gal = (Gallery)session.get(Gallery.class, 1);
Image img = (Image)session.get(Image.class, 3);
gal.getAssociatedImages().remove(img);
session.update(gal);
}
}
这个类相当复杂。
首先,该类被注解为 repository。内部有一个名为 `_sessionFactory` 的 autowired 属性。然后有四个方法,每个方法都有一个目的:
- `deleteAll`:可用于清理两个表。这反过来也会删除 `join` 表中的行。
- `testPersistence`:用于创建一个画廊和两个图像。然后将这两个图像关联到画廊。
- `testPersistence2`:用于查找属于特定画廊的所有图像。这是通过 Hibernate 查询语言完成的。
- `testPersistence3`:用于从画廊持有的图像集合中移除 id 为 3 的图像。然后 session 更新画廊。这应该从 `join` 表中删除一行。
`deleteAll` 的源代码:
@Transactional
public void deleteAll()
{
Session session = _sessionFactory.getCurrentSession();
Query q = session.createQuery("delete from Gallery");
q.executeUpdate();
q = session.createQuery("delete from Image");
q.executeUpdate();
}
此方法提供了一种删除所有三个表中的所有行的方法。我执行此操作的方式是假设我已正确设置所有内容,特别是代码和数据库表设置中的 CASCADE 设置。当我删除一个表中的所有行时,`join` 表中的行将消失。我可以安全地删除另一个表中的所有行,而不会有问题。
`testPersistence` 的源代码:
@Transactional
public int testPersistence()
{
Session session = _sessionFactory.getCurrentSession();
Gallery gallery = new Gallery();
gallery.setId(1);
gallery.setName("My Test Gallery");
Image img1 = new Image();
img1.setId(2);
img1.setFilePath("C:\\testimages\\img1.jpg");
gallery.getAssociatedImages().add(img1);
Image img2 = new Image();
img2.setId(3);
img2.setFilePath("C:\\testimages\\img2.jpg");
gallery.getAssociatedImages().add(img2);
session.save(gallery);
return gallery.getId();
}
上面的代码应该非常自明。我像普通对象一样创建 `Image` 和 `Gallery` 实体,调用构造函数和 setter。读者应该注意的一点是,只有一个实体保存操作——在将两个 `Image` 实体添加到 `Gallery` 的 `Image` 集合后,保存 `Gallery` 实体。由于 Hibernate 为我们处理了繁重的工作,因此可以做到这一点。它实际上为我们插入了 `Image` 实体。我相信如果您将 log4j 的级别设置为 DEBUG,您就可以看到它。
这是我们能对这两种实体做的唯一方法吗?当然不是,您可以创建两个 `Image` 实体并显式保存它们,然后创建 `Gallery` 实体,将这两个 `Image` 实体添加到其集合中,然后保存 `Gallery`。我已经测试过了。它有效。我相信如果您愿意,可以创建 `Gallery` 实体,然后保存它。然后创建两个 `Image` 实体,也保存它们。最后,在 CRUD 操作的某个环节,您可以将图像添加到 `Gallery` 实体的集合中,然后保存 `Gallery` 实体。
我曾向您承诺,将解释为什么我需要在 `join` 表中使用 `AUTO_INCREMENT`。上面的代码就是原因。正如您所见,没有代码插入行到 `join` 表。这是由 Hibernate 完成的。它所做的只是插入带有值的行(`galleryid = 1`,`imageid = 2` 或 `3`)。如果您不使用 `join` 表中的 `AUTO_INCREMENT` 作为主键,那么通过 session 的 `save()` 会因异常而失败。这是我希望有人能告诉我的事情。我花了一段时间才弄清楚。现在您知道了,使用隐式的 `join` 表 `insert`,您**必须**提供一种自动为 `join` 表行创建唯一主键的方法。
`testPersistence2` 的代码:
@Transactional
public void testPersistence2()
{
Session session = _sessionFactory.getCurrentSession();
Query query = session.createQuery(
"select image from Gallery gallery join gallery.associatedImages image"
+ " where gallery.id = :galId order by image.id desc"
).setParameter("galId", 1).
setMaxResults(2);
List<Image> imgs = query.list();
for(Image image : imgs)
{
_logger.info(String.format("Image Id: %s", image.getId()));
_logger.info(String.format("Image File Path: %s", image.getFilePath()));
}
}
对于上面的方法,我试图进行实验,看看是否可以只获取画廊的所有图像,如果我只有画廊 ID。我认为我可以用 HQL 轻松做到这一点,也可以通过从 `Gallery` 实体遍历到其 `Image` 集合来做到。但如果我想按日期降序对 `Image` 集合进行排序,并限制 `Image` 实体的数量到一个特定的数字或设置一个特定的起始索引,那就没那么容易了。专家可能会纠正我,但我更喜欢 HQL。再次,我告诉您上面的代码是有效的。正如您所见,我执行了 `Gallery` 表和 `Image` 表的 `join`,而我甚至没有提到 `join` 表。这不是很棒吗?
我们最后一个方法 `testPersistence3` 的代码:
@Transactional
public void testPersistence3()
{
Session session = _sessionFactory.getCurrentSession();
Gallery gal = (Gallery)session.get(Gallery.class, 1);
Image img = (Image)session.get(Image.class, 3);
gal.getAssociatedImages().remove(img);
session.update(gal);
}
这个方法的作用是,假设我有了画廊 ID 和图像 ID,我想移除这个图像与这个画廊的关联。再次,您可以看到这个方法多么酷,我查找画廊和图像,然后我调用那个画廊的 `getAssociatedImages()`,然后从集合中移除那个图像。最后,我只需保存 `gallery` 实体。我可以向您保证这很有效。而且它非常棒。这一切都归功于我们将 `CASCADE` 设置正确地添加到了表和实体类中。
接下来,我想在 `Main` 类中运行所有四个场景,该类包含 `static main` 方法。这就是我们接下来要看到的。
步骤 5 -- Main 类
main 类只是我演示 repository 类中测试方法的一种方式。它相当简单。代码如下:
package org.hanbo.hibernate.experiment;
import org.hanbo.hibernate.experiment.entities.ImageGalleryRepository;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main
{
public static void main(String[] argv)
{
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext("application-context.xml");
ImageGalleryRepository repo
= context.getBean(ImageGalleryRepository.class);
repo.deleteAll();
repo.testPersistence();
repo.testPersistence2();
repo.testPersistence3();
context.close();
}
}
上面的 Java 类执行以下操作:
- 首先,加载应用程序上下文。应用程序上下文包含所有 bean 定义。它必须是第一个加载的东西。
- 一旦应用程序拥有应用程序上下文对象,我应该就能获得我想要使用的 bean。在这种情况下,我想获取 repository 对象,以便我可以运行我的场景。
- 在我的应用程序中,我运行我的场景。
- 关闭应用程序上下文,以便应用程序可以退出。
步骤 6 -- Application Context XML 文件
由于这是一个 Spring 应用程序,我需要定义 bean、Hibernate、MySQL 和事务管理器的配置等。这是我的应用程序上下文 XML 文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<context:component-scan base-package="org.hanbo.hibernate.experiment.entities" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/hanbotest?autoReconnect=true"/>
<property name="username" value="hbuser1"/>
<property name="password" value="123test321"/>
<property name="maxActive" value="8"/>
<property name="maxIdle" value="4"/>
<property name="maxWait" value="900000"/>
<property name="validationQuery" value="SELECT 1" />
<property name="testOnBorrow" value="true" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan">
<array>
<value>org.hanbo.hibernate.experiment.entities</value>
</array>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.cache.provider_class">
org.hibernate.cache.NoCacheProvider</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
如果您对 Spring 有一些经验,阅读此文件并不困难。基本上,我将注解和应用程序上下文 XML 文件结合使用。这是我 XML 文件中的几个部分:
- component scan 部分告诉 Spring 框架某些包包含可以找到用于依赖注入的类。
- dataSource 部分创建了针对 MySQL 数据库的 JDBC 驱动程序的配置。
- sessionFactory 部分为 Hibernate 创建了 session factory bean。
- 最后两个部分是事务管理器。您读者可能在 repository 的方法上看到了 `@Transactional` 注解。为了使用这个注解,我必须定义这两个部分。
步骤 6 -- Log4J 配置
我还必须配置 `log4j` 日志记录。这相当容易,我从 `log4j` 官方网站获取了一个示例日志属性文件,并为其修改了我的项目。内容如下:
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
# Pattern to output the caller's file name and line number.
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=C:/temp/logs/hibernate-manytomany.log
log4j.appender.R.DatePatttern=yyyy-MM-dd
log4j.appender.R.MaxFileSize=10MB
log4j.appender.R.MaxBackupIndex=10
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
该文件使用每日滚动日志文件。文件最大大小为 10MB,最多备份 10 个副本。第一行允许您控制日志级别。我将其设置为 `DEBUG`。如果您想降低级别,可以将 `DEBUG` 更改为 `INFO`。其他行并不重要。**请确保您已创建日志目录 -- C:\temp\logs\**
执行结果
`deleteAll()` 方法将删除所有三个表中的任何行。
`testPersistence()` 方法将在 `Gallery` 表中创建一个行,`id` 设置为 `1`。在 `Image` 表中创建两行,`id` 分别是 `2` 和 `3`。由于这在 `deleteAll()` 之后运行,并且如果成功,则证明 `deleteAll()` 工作正常。如果 `deleteAll()` 未能正常工作,我将对所有三个行使用相同的 `id`,`testPersistence()` 将失败。
`testPersistence2()` 方法将输出两行,一行 `id` 为 `2`,一行 `id` 为 `3`。这假设 `tesPersistence()` 已正确工作。输出应该是两个 `Image` 行的详细信息。
最后的 `testPersistence3()` 将移除 id 为 3 的图像与 id 为 1 的画廊之间的关联。我没有提供任何输出测试方法来测试这一点。所以您只需在数据库中查询 `image` 表和 `imagetogallery` 表。`image` 表中的两行都应该存在。`imagetogallery` 表中应该只有一行。
关注点
使用 `join` 表在两个实体之间映射多对多关系可能是您需要做的最复杂的实体配置。在我达到这一点之后,我对使用 Hibernate 配合关系数据库非常有信心。本教程足够清晰。有很多信息。我也确信本教程胜过了所有其他类似的教程。写这个教程很有趣。希望您也喜欢!
历史
- 2015年12月31日:初稿