Click here to Skip to main content
15,881,898 members
Articles / Web Development / Spring

Hibernate - Many to Many Mapping via Join-Table

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
4 Jan 2016Ms-PL15 min read 37.5K   146   3  
Detailed description of how many-to-many association works with Hibernate, using MySQL and Spring
In this post, I will list out some issues that I had to deal with while working on a small project where I needed to use many to many mapping between two entities. Once I hashed out all the issues, there is a great way to represent two entity types in many-to-many relationship. This tutorial will show you why and how this is great.

Introduction

Recently, I was working on a small project where I needed to use many to many mapping between two entities. There are many examples online. Most of them did not explain some of the gotchas that I have to work out. This compels me to write this tutorial -- letting readers know some of the issues that I have to deal with. The truth is that once I hashed out all the issues, this is a great way to represent two entity types in many-to-many relationship. This tutorial will show you why and how this is great.

So what is a many to many relationship? Imagine this, you have a blog, and you want to manage your photos uploaded to your blog. You categorize the photos via galleries. There can be five images (image-1 to image-5). and there can two galleries (gal-1, gal-2). The images image-1, image-3, and image-4 are in gal-1; image-2, image-3, image-4, and image-5 are in gal-2. As you can see, the images image-3 and image-4 are in both galleries. These associations can be identified as many-to-many. I used to avoid such a complex relationship, and only deal with two entities that have direct mapping between them, use one-to-one or one-to-many. For this many-to-many relationship, it is do-able with three tables and use some type of one-to-many mapping (one-to-many between gallery and the join table, and one-to-many between image and join table). But now, I have realized that such complex mapping might not be a good idea. I am particularly concerned about the number of explicit SQL calls I had to make to the back end DB. With the proper many-to-many mapping, I think Hibernate can help me simplify the operations I am interested in.

So what type of operations am I interested in? Well, here are a few ones:

  • I like to create galleries, and upload images, then associate images with a gallery.
  • I like to delete galleries or images. To do these, I don't have to explicitly remove the association before deleting.
  • I like to find all the images in a gallery and do pagination.
  • I like to add or remove the association between gallery and images, yet not delete the gallery or the images.

Background

Sounds simple. How do we do these with plain SQL script? Well, I can insert a row in gallery table, and insert another row in image table. Finally, I add a row into imagetogallery (this is the join-table) for these two. Now if I delete either the gallery or the image row, there is a way for SQL DB to automatically delete the row in the join-table. I can also delete the row in the join table and severe the relationship between image and gallery. If I want to find all images in a gallery, I do it with one query of two inner joins.

To illustrate my operations, here is the test table I will be creating (I use MySQL, by the way):

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

The first three lines basically remove the tables if they've already existed. The table gallery and image each have two columns, the first is the primary key "id". "id" has type integer and cannot be NULL. For me to test easily, I will explicitly set the "id" values in my SQL test and my Hibernate enabled Java program. The last table, imagetogallery, is more complicated. It has a primary key "id". And its value is set to auto increment. Providing a new id value of this join table automatically whenever a row is inserted is very important when I start using Hibernate. I will explain this when I get to it. The join table also have two foreign keys, one to the gallery table and one to the image table. Those two foreign keys have the cascade on update and delete. This again is important, for running SQL statement or using Hibernate. Again, when I get there, I will explain why.

Once I created these tables, I thought it would be a good idea to run some simulations against the setup using plain SQL statements. First thing I do is:

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

The above code snippet will create a row in gallery table, two rows in the image table, then associate the two image rows with the gallery row. Next, I want to make sure I can do a query to find all the images belong to the gallery with id equals 1. Here is my query:

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

The query should succeed and produce the following output:

id filepath
2 c://images//testimg1.jpg
3 c://images//testimg2.jpg

Next, I would experiment with deleting a row in the image table, say the id equals 2. This is done by the following SQL statement:

SQL
DELETE FROM image WHERE image.id = 2;

I use the same query that finds the images with gallery id equals 1. The query returns:

id filepath
3 c://images//testimg2.jpg

What happened? Well, I did mention that when I get to the CASCADE delete and update, I will explain what these are for and why they are important. Here it is. When I create the join table, I don't have to declare the CASCADE delete or update. Then if I do the delete on the image table, I would get an error back indicating the operation will fail because of foreign key constraint violation. The reason is that the join table has a row that references the image I was about to delete. In order to correct this error, I have to first delete the row in the join table that has the reference to the image, then I can delete the image. This is rather awkward. Now with the CASCADE delete on the foreign keys, I can just delete a row in image or in gallery tables and the rows in the join table that references either the gallery or the image will automatically be deleted. Wow! That is revolutionary! So what does CASCADE update do? Imagine this, assuming I have to update the id value for a gallery or a image. That is an update of the primary key (dangerous!) and it could fail with error because one or more rows in the join table might have reference to this image. But it can happen and can be done. With the CASCADE update declared, I can update the id of that image (as long as the id I chose is not used already in the image table). And DB engine would automatically update the imageid in the join table. In fact, if I want to do this to the gallery, I can do it without the manual update to the join table as well. Magically! I love it!

With all these tests, I am satisfied with the table design. Now I want to move all these to a Hibernate application. It is nothing fancy, just a plain old Java console application.

The Hibernate Application

I salvaged an old Spring based program, and converted into this application. The program has the following file structure:

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

Step 1 -- The POM File

I will first show the pom.xml. In this file, it contains all the dependencies needed for my experimental application. It looks like this:

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>

The ones from Spring framework are used for dependency injections, integration with Hibernate, and DB transactions. The ones from Apache Commons are used for logging, convenience, and connection pools. I use Log4J exclusively for logging. The other ones, are MySQL JDBC driver, JBoss logging used by Hibernate, the Hibernate library, JPA annotation, and javax transactions.

To build this application. All you need to do is:

mvn clean install

Step 3 -- The Entities Classes

To get all my scenarios to work with Hibernate, I need to define my entities. Because I am using join table for the many-to-many association. I only need to define two entities. The entity for the Gallery and entity for Image. The join table is implicitly defined in the two entities. As you can see, using the join table, I actually don't need to define three entities, but only two. Then I can let Hibernate and database do the heavy lifting for me.

Let me show you the Java code for the Image entity. Here it is:

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

The most important part of this class is the many-to-many annotation:

Java
@ManyToMany(fetch = FetchType.LAZY, mappedBy = "associatedImages")
private Set<Gallery> associatedGalleries;

You might ask, where do I find this "associatedImages"? It is located in the Gallery entity class. The idea of mappedBy is to have the Image entity looks up the Gallery entity definition and find the collection of images. This collection property in Gallery entity is called "associatedImages". Now let me share the source code to the Gallery entity definition:

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

As you can see, the most complex part of the class is the following, which actually defines the many-to-many association using the join table I have created:

Java
@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;

There are a couple things you need to know:

  • The cascade type I use is CascadeType.All, meaning that when I do changes to Image entity or Gallery entity, all the changes must be propagated (cascade effect) to the join table.
  • The JoinTable annotation defines the join table I have created.
  • The JoinTable annotation also defines the join columns, one for galleryid, and one for the imageid. These are the table columns in the join table.
  • The first join column is the galleryid in the join table. The second one is the inverse join column imageid. These two tells Gallery entity how to find itself in the join table, and the images associated with the gallery.
  • The join columns has two properties. One is called nullable, I set it to false means for that column, the value cannot be null. The other is called updatable, I set it to false. The values in these columns are primary keys to other tables, allowing them to be updatable is just not wise.

Step 4 -- The Repository Class

I also created a repository class so that I can play with the scenarios. The repository class is in the same package as the entities. The class looks like this:

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

This class is quite complicated.

First, the class is annotated as repository. Inside, there is an autowired property called _sessionFactory. Then there are four methods, each has a purpose:

  • deleteAll: can be used to clean up the two tables. Which in term deletes the rows in the join table as well.
  • testPersistence: used to create one gallery, and two images. Then associate the two images to the gallery.
  • testPersistence2: used to find all the images that belong to a specific gallery. This is done by Hibernate Query Language.
  • testPersistence3: used to remove the image with id 3 from the collection of images held by the gallery. Then session updates the gallery. This should remove a row from the join table.

The source code for deleteAll:

Java
@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();
}

This method gives me a way to delete all the rows from all three tables. The way I do this is to assume that I have everything setup correctly, especially with the CASCADE setups, both in code and in DB table setup. And when I delete all rows in one table, the rows in join table will be gone. I can safely delete all rows in the other table, with no problem.

The source code for testPersistence:

Java
@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();
}

The code above should be very self-explanatory. I create the Image and Gallery entities like regular objects, invoking the constructor and setters. One thing the reader should notice is that there is only one save of entity -- saving the Gallery entity after the two Image entities are added into Gallery's collection of Image. This can be done because Hibernate does the heavy lifting for us. It actually does the insertion of the Image entities for us. I am sure if you turn log4j level to DEBUG, you can see it.

Is that the only way we can do to these two types entities? Of course not, you can create the two Image entities and explicitly save them, then create the Gallery entity, add the two Image entities to its collection, then save the Gallery. I've tested it. It works. I am sure if you want, you can create Gallery entity, then save it. Then create the two Image entities, save them as well. Finally, somewhere down the line of CRUD operations, you can add the images to the collection for the Gallery entity, then save the Gallery entity.

I promised you the reader that I will explain why I needed AUTO_INCREMENT in the join table. The above code is the reason. As you can see, there is no code that inserts a row into the join table. It is done by Hibernate. What it does is just insert rows with the values (galleryid = 1, imageid = 2 or 3). If you don't use that AUTO_INCREMENT in the join table for the primary key, the save() via session will fail with an exception. This is something I wish some one could have told me. It took me a while to figure out. Now you know, with an implicit join table insert, you must provide a way to create unique primary key to the join table rows automatically.

The code for testPersistence2:

Java
@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()));
   }
}

For the above method, I was trying to do is experimenting and see if I can get just the images, all the images, of a gallery if I only have the gallery Id. I think I can do this easily with HQL, I can also do it by traversing from Gallery entity down to its Image collections. But it is not as easy if I want to sort the Image collection by date in descending order, and limit the number of Image entities to a certain number or set the start index to a certain number. Experts might prove me wrong, but I prefer HQL. Again, let me tell you that the above code works. As you can see, I was doing a join of Gallery table and Image table and I didn't even mention the join table at all. Isn't this awesome?

The code for our last method testPersistence3:

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

What this method does is that, assuming that I have the gallery id and I have an image id, I want to remove the association of this image from this gallery. Again, you can see how cool this method is, I look up the gallery, and the image, then I involve the getAssociatedImages() of that gallery, then I do a remove of that image from the collection. Finally, I just save the gallery entity. I can assure you this works. And it is just plain awesome. All because we have the CASCADE settings properly added to the tables and to the entity classes.

Next, I want to run all the four scenarios in the Main class, which contains the static main method. It is what we are going to see next.

Step 5 -- The Main Class

The main class is just a way for me to demo the test methods in my repository class. It is quite simple. Here it is:

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

What the above Java class does is the following:

  • First, load the application context. Application context contains all the bean definition. It has to be the first thing to be loaded.
  • Once the app has the application context object, I should just be able to get the bean I wanted to use. In this case, I want to get the repository object so that I can run my scenarios.
  • In my application, I run my scenarios.
  • Close the application context so that application can exit.

Step 6 -- The Application Context XML File

Since this is a spring application, I need to define the beans, the configuration of Hibernate, MySQL, and transaction manager, etc. Here is my application context XML file:

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>

If you have a little experience with Spring, this file will not be difficult to read. Essentially, I am combining the use of annotations and application context XML file. Here are the sections of my XML file:

  • The component scan piece tells Spring framework that certain packages contains classes that can be found for dependency injection.
  • Date source piece creates the configuration of JDBC driver against MySQL DB.
  • The session factory piece creates the session factory bean for Hibernate.
  • The last two pieces are for transaction manager. You the reader might have seen the annotation @Transactional on the methods in the repository. In order to use this annotation, I have to define these two pieces.

Step 6 -- Log4J Configuration

I also need to configure log4j logging. It is fairly easy to do, I get a sample logging properties file from log4j official site, and modified for my project. The content looks like this:

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

The file uses a daily rolling log file. The file has max size of 10MB, and max back up copy of 10. The very first line allows you to control the logging level. I set it to DEBUG. If you want to tune down the level, you can change DEBUG to INFO. The other lines, they are unimportant. Please make sure you have the logging directory created -- C:\temp\logs\

The Execution Result

The deleteAll() method will remove any rows in all three tables.

The testPersistence() method will create one row in Gallery table, the id is set to 1. And two rows in Image table, the ids are 2 and 3. Since this runs after deleteAll(), and if successful, then it proves deleteAll() worked. If deleteAll() failed to work, I use the same ids for all three rows, testPersistence() will fail.

The testPersistence2() method will output two rows, one with id of 2 and one with id 3. This assumes tesPersistence() worked correctly. And the output should be the details of the two Image rows.

The last testPersistence3() will remove the association between image with id 3 and gallery with id 1. I did not provide any output test methods= to test this. So you just have to run query against image table and imagetogallery table in DB. Both rows in image table should be there. There should be just one row in imagetogallery table.

Points of Interest

Mapping many-to-many relationship between two entities using a join table is probably the most complicated entities configuration you have to do. After I get to this point, I am very confident about using Hibernate with relational database. This tutorial is clear enough. There is a lot of information. I am also confident that this tutorial beats all other similar ones. It has been great fun writing this. Hope you enjoy it!

History

  • 31st December, 2015: Initial draft

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Team Leader The Judge Group
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --