快速,持续,稳定,傻瓜式
支持Mysql,Sqlserver数据同步

休眠-通过联接表进行多对多映射

在线QQ客服:1922638

专业的SQL Server、MySQL数据库同步软件

介绍

最近,我在一个小型项目中工作,需要在两个实体之间使用多对多映射。在线上有很多示例。他们中的大多数人并没有解释我必须解决的一些陷阱。这迫使我编写本教程-让读者知道我必须处理的一些问题。事实是,一旦我解决了所有问题,这就是一种以多对多关系表示两种实体类型的好方法。本教程将向您展示为什么以及如何如此出色

那么,多对多关系是什么?想象一下,您有一个博客,并且想要管理上传到博客的照片。您通过画廊对照片进行分类。可以有五个图像(图像1至图像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都在两个画廊中。这些关联可以被识别为多对多。我过去避免了这种复杂的关系,只处理两个具有直接映射关系的实体,使用一对一或一对多。对于这种多对多关系,它可以与三个表一起使用,并使用某种类型的一对多映射(图库和联接表之间为一对多,图像和图形之间为一对多。联接表)。但是现在我已经意识到,如此复杂的映射可能不是一个好主意。我特别担心必须对后端数据库进行的显式SQL调用的数量。通过适当的多对多映射,我认为Hibernate可以帮助我简化我感兴趣的操作。

那么,我对哪种类型的手术感兴趣?好吧,这里有几个:

  • 我喜欢创建画廊,上传图像,然后将图像与画廊关联。
  • 我喜欢删除画廊或图像。执行这些操作,在删除之前,我不必显式删除关联。
  • 我喜欢在画廊中查找所有图像并进行分页。
  • 我喜欢添加或删除图库和图像之间的关联,但不删除图库或图像

背景

听起来很简单。我们如何用普通的SQL脚本来做到这些?好吧,我可以在图库表中插入一行,并在图像表中插入另一行。最后,我将这两行添加到imagetogallery(这是联接表)中。现在,如果我删除图库或图像行,则SQL DB有一种方法可以自动删除联接表中的行。我还可以删除联接表中的行,并严格处理图像和图库之间的关系。如果要查找图库中的所有图像,请使用两个内部联接的一个查询来完成。

为了说明我的操作,这是我将要创建的测试表(顺便说一下,我使用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
);

如果表已经存在,则前三行基本上会删除它们。表库和图像分别具有两列,第一列是主键“ id”。“ id”具有整数类型,不能为NULL。为了方便测试,我将在SQL测试和启用Hibernate的Java程序中显式设置“ id”值。最后一张表的图像画廊更加复杂。它具有主键“ id”。并且其值设置为自动递增。当我开始使用Hibernate时,每当插入一行时自动提供此连接表的新id值非常重要。我将在解释时对此进行解释。联接表也有两个外键,一个是图库表,另一个是图像表。这两个外键在更新和删除时具有级联。这又很重要,用于运行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);

上面的代码片段将在图库表中创建一行,在图像表中创建两行,然后将这两个图像行与图库行关联。接下来,我想确保可以执行查询以查找属于id等于1的画廊的所有图像。这是我的查询:

SELECT image.* FROM image
INNER JOIN imagetogallery ON image.id = imagetogallery.imageid
WHERE imagetogallery.galleryid = 1

该查询应成功并产生以下输出:

ID 文件路径
2 c://images//testimg1.jpg
3 c://images//testimg2.jpg

接下来,我将尝试删除图像表中的一行,即id等于2。这是通过以下SQL语句完成的:

DELETE FROM image WHERE image.id = 2;

我使用与查找图库ID等于1的图像相同的查询。该查询返回:

ID 文件路径
3 c://images//testimg2.jpg

发生了什么?好吧,我确实提到过,当我进入CASCADE删除和更新时,我将解释它们的用途以及它们为何重要。这里是。创建联接表时,不必声明CASCADE删除或更新。然后,如果我在图像表上执行删除操作,则会收到一条错误消息,指示由于违反外键约束,操作将失败。原因是联接表的行引用了我要删除的图像。为了纠正此错误,我必须首先删除联接表中具有该图像引用的行,然后才能删除该图像。这很尴尬。现在,通过CASCADE删除外键,我可以只删除图像或图库表中的一行,而引用图库或图像的联接表中的行将被自动删除。哇!那是革命性的!那么CASCADE更新有什么作用?想象一下,假设我必须更新图库或图像的id值。这是主键的更新(危险!),并且可能会因错误而失败,因为联接表中的一个或多个行可能已引用了该映像。但是cit可能会发生并且可以做到。声明了CASCADE更新后,我可以更新该图像的ID(只要在图像表中尚未使用我选择的ID)。数据库引擎将自动更新联接表中的imageid。实际上,如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!那是革命性的!那么CASCADE更新有什么作用?想象一下,假设我必须更新图库或图像的id值。这是主键的更新(危险!),并且可能会因错误而失败,因为联接表中的一个或多个行可能已引用了该映像。但是cit可能会发生并且可以做到。声明了CASCADE更新后,我可以更新该图像的ID(只要在图像表中尚未使用我选择的ID)。数据库引擎将自动更新联接表中的imageid。实际上,如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!那是革命性的!那么CASCADE更新有什么作用?想象一下,假设我必须更新图库或图像的id值。这是主键的更新(危险!),并且可能会因错误而失败,因为联接表中的一个或多个行可能已引用了该映像。但是cit可能会发生并且可以做到。声明了CASCADE更新后,我可以更新该图像的ID(只要在图像表中尚未使用我选择的ID)。数据库引擎将自动更新联接表中的imageid。实际上,如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!),并且可能会因错误而失败,因为联接表中的一行或多行可能已引用了该映像。但是cit可能会发生并且可以做到。声明了CASCADE更新后,我可以更新该图像的ID(只要在图像表中尚未使用我选择的ID)。数据库引擎将自动更新联接表中的imageid。实际上,如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!),并且可能会因错误而失败,因为联接表中的一行或多行可能已引用了该映像。但是cit可能会发生并且可以做到。声明了CASCADE更新后,我可以更新该图像的ID(只要在图像表中尚未使用我选择的ID)。数据库引擎将自动更新联接表中的imageid。实际上,如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!如果我想对图库执行此操作,则无需手动更新联接表也可以执行此操作。神奇地!我喜欢它!

通过所有这些测试,我对表格设计感到满意。现在,我想将所有这些都移到Hibernate应用程序中。没什么特别的,只是一个普通的旧Java控制台应用程序。

休眠应用

我回收了一个旧的基于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集成以及DB事务。来自Apache Commons的用于日志记录,便利和连接池。我仅将Log4J用于日志记录。其他的是MySQL JDBC驱动程序,Hibernate使用的JBoss日志记录,Hibernate库,JP​​A批注和javax事务。

生成此应用程序。您需要做的只是:

mvn clean install

第3步-实体类

为了使我所有的场景都可以与Hibernate一起使用,我需要定义我的实体。因为我将联接表用于多对多关联。我只需要定义两个实体。图库的实体和图像的实体。连接表是在两个实体中隐式定义的。如您所见,使用联接表,我实际上不需要定义三个实体,而只需定义两个。然后,我可以让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;
   }
}

如您所见,该类中最复杂的部分如下,它们实际上使用我创建的联接表定义了多对多关联:

@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实体进行更改时,所有更改都必须传播(级联效果)到联接表。
  • JoinTable批注定义了我创建的联接表。
  • JoinTable批注还定义了连接列,一列用于galleryid,一列用于imageid。这些是联接表中的表列。
  • 第一个连接列是连接表中的Galleryid。第二个是反向联接列imageid。这两个告诉画廊实体如何在联接表中找到自己以及与画廊相关的图像。
  • 连接列具有两个属性。一个称为可空值,对于该列,我将其设置为false,值不能为null。另一个称为可更新,我将其设置为false。这些列中的值是其他表的主键,允许它们可更新是不明智的。

第4步-存储库类

我还创建了一个存储库类,以便可以使用场景。存储库类与实体位于同一包中。该类如下所示:

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);
   }
}

这个课很复杂。

首先,该类被注释为存储库。内部有一个名为的自动连线属性_sessionFactory。然后有四种方法,每种都有一个目的:

  • deleteAll:可用于清理两个表。这也将删除联接表中的行。
  • testPersistence:用于创建一个图库和两个图像。然后将两个图像关联到图库。
  • testPersistence2:用于查找属于特定图库的所有图像。这是通过Hibernate查询语言完成的。
  • testPersistence3:用于从图库保存的图像集中删除ID为3的图像。然后会话更新图库。这应该从联接表中删除一行。

的源代码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();
}

这种方法给我一种删除所有3个表中所有行的方法。我这样做的方法是假设我正确设置了所有设置,尤其是在代码和数据库表设置中,尤其是使用CASCADE设置时。当我删除一个表中的所有行时,联接表中的行将消失。我可以安全地删除另一个表中的所有行,没有问题。

的源代码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实体。

我向读者保证,我将解释为什么在联接表中需要AUTO_INCREMENT。上面的代码是原因。如您所见,没有代码将行插入到联接表中。它是由Hibernate完成的。它只是在插入带有值的行(galleryid = 1,imageid = 2或3)。如果您没有在联接表中使用该AUTO_INCREMENT作为主键,则通过session的save()将失败,并发生异常。我希望有人能告诉我这一点。我花了一段时间才弄清楚。现在您知道了,通过隐式联接表插入,您必须提供一种自动为联接表行创建唯一主键的方法。

的代码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表的联接,甚至根本没有提到联接表。这不是很棒吗?

我们最后一个方法的代码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()图库的图像,然后从集合中删除该图像。最后,我只保存了画廊实体。我可以向您保证这是可行的。这真是太棒了。都是因为我们已将CASCADE设置正确添加到表和实体类中。

接下来,我要在Main类中运行所有4个场景,其中包含静态main方法。这是我们接下来要看到的。

第5步-主班

主类只是我演示存储库类中的测试方法的一种方式。这很简单。这里是:

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。在这种情况下,我想要获取存储库对象,以便可以运行我的方案。
  • 在我的应用程序中,我运行我的方案。
  • 关闭应用程序上下文,以便应用程序可以退出。

第6步-应用程序上下文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文件的各个部分:

  • 组件扫描片段告诉Spring框架,某些软件包包含可用于依赖项注入的类。
  • 日期源块针对MySQL DB创建JDBC驱动程序的配置。
  • 会话工厂块为Hibernate创建了会话工厂bean。
  • 最后两部分用于事务管理器。您可能已经在存储库中的方法上看到读者注解@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()方法将删除所有3个表中的任何行。

testPersistence()方法将在Gallery表中创建1行,将ID设置为1。在Image表中创建2行,其ID为2和3。由于此操作在之后执行deleteAll(),如果成功,则证明是deleteAll()可行的。如果deleteAll()无法正常工作,我对所有3行都使用相同的ID,testPersistence()将会失败。

testPersistence2()方法将输出2行,其中一行ID为2,另一行ID为3。这假定tesPersistence()工作正常。输出应该是两个Image行的详细信息。

最后一个testPersistence3()将删除ID为3的图像与ID为1的图库之间的关联。我没有提供任何输出测试方法=来对此进行测试。因此,您只需要对数据库中的图像表和imagetogallery表运行查询。图像表中的两行均应存在。imagetogallery表中应该只有一行。

兴趣点

使用联接表映射两个实体之间的多对多关系可能是您要做的最复杂的实体配置。到此为止,我对将Hibernate与关系数据库一起使用非常有信心。本教程很清楚。有很多信息。我也相信本教程将击败所有其他类似的教程。编写这篇文章非常有趣。希望你喜欢它!

历史

相关推荐

咨询软件
 
QQ在线咨询
售前咨询热线
QQ1922638