Vrushank
Vrushank

Reputation: 2813

Why does hibernate execute redundant update statements when no values for those tables were changed?

I have the following piece of code:

public int makePayment(User forUser, Collection<Movie> cartItems, 
     Address deliverTo) 
{
   int returnCode = 0;

   LOGGER.debug("Number of items in cart - {}", cartItems.size());

   Rental rental = new Rental();
   rental.setRentalID(RandomStringUtils.randomAlphanumeric(ID_LENGTH).toUpperCase());
   rental.setUser(forUser); // Update reference to parent
   rental.setAddress(deliverTo);

   // Each rental has its own set of rented movies
   Set<RentedMovie> rentedMovies = new HashSet<>(cartItems.size());
   for (Iterator<Movie> iterator = cartItems.iterator(); iterator.hasNext();)
   {
      Movie movie = iterator.next();
      RentedMovie rentedMovie = new RentedMovie();
      rentedMovie.setMovie(movie);
      rentedMovie.setRental(rental); // Update reference to parent
      rentedMovies.add(rentedMovie);
   }

   rental.setRentedMovies(rentedMovies);
   rentalDAO.create(rental);

   Set<Rental> userRentals = userDAO.findById(forUser.getUserID()).getRentals();
   userRentals.add(rental); // Add to existing set of rentals

   try
   {
      String addressID = addressDAO.create(deliverTo);
      LOGGER.debug("Delivery Address successfully inserted. Generated address ID - {}", addressID);

      forUser.setRentals(userRentals);
      userDAO.update(forUser); // Internally calls session.merge()
   }
   catch (Exception e) {
      returnCode = -1;
      LOGGER.error("Exception during payment - {}", e.getMessage(), e);
   }

   return returnCode;
}

Now when the above method completes, hibernate performs certain redundant update queries as described below:

Let's say I add 2 items to cart, so...

DEBUG PaymentService - Number of items in cart - 2

Next, a row is inserted in RENTALS table (expected)

insert 
into
  rentals
  (bookingDate, userID, addressID, rentalID) 
values
  (?, ?, ?, ?)

USER and ADDRESS table updated once since 1 row inserted in RENTALS (redundant)

update
  users 
set
  userName=?,
  password=?,
  firstName=?,
  lastName=?,
  email=?,
  addressID=? 
where
  userID=?

update
  addresses 
set
  line1=?,
  line2=?,
  city=?,
  state=?,
  pincode=? 
where
  addressID=?

MOVIES table updated as many times as the number of items in cart (2 in this case, again redundant)

update
  movies 
set
  movieName=?,
  releaseYear=?,
  rating=?,
  nCopies=?,
  imageUri=?,
  plotLine=? 
where
  movieID=?

/* And another query with different movieID */

2 records inserted in RENTED_MOVIE table (expected)

insert 
into
  rented_movie
  (rentalID, returnDate, movieID) 
values
  (?, ?, ?)

/* And one more with same rentalID and diff. movieID */

And last, but not the least, a select query to RENTALS table (Why??)

What is the reason for the above mentioned redundant queries?

I am using Hibernate 4.2.1 with MySQL 5.5


Link to Schema Diagram

Link to User.hbm.xml

Link to Rental.hbm.xml


UPDATE: It seems no matter what possible combinations of inverse I try, I can't seem to avoid these n+2 updates (n = number of movies in cart) :(

Could someone please let me know if this is a known behavior? If yes, what is the possible workaround?

Upvotes: 0

Views: 390

Answers (1)

Vrushank
Vrushank

Reputation: 2813

I finally managed to fix my problem. The issue was with the cascading settings. I had set it to cascasde="all" in all associations.

I made changes to Rental.hbm.xml to set specific cascade options for an association:

<!-- n:1 mapping with User -->
<many-to-one name="user"
   column="userID"
   class="User"
   not-null="true"
   fetch="join"
   cascade="none" /> <!-- No cascading required for User -->

<!-- 1:1 mapping with Address. Association is MANDATORY. -->
<many-to-one name="address"
      column="addressID"
      class="Address"
      unique="true"
      not-null="true"
      cascade="persist"/> <!-- Either create a new delivery address or use an existing user address -->

<!-- m:n mapping with Movie -->
<set name="rentedMovies" table="rented_movie" lazy="true" 
    cascade="save-update, delete-orphan"> <!-- Selective update / delete -->
   <key column="rentalID"/>
   <composite-element class="RentedMovie">
      <parent name="rental"/>
      <property name="returnDate" column="returnDate" type="date" not-null="true"/>
      <many-to-one name="movie" column="movieID" class="Movie" not-null="true" fetch="join"/>
   </composite-element>
</set>

I removed the cascade from the <many-to-one name="movie" ../> since I am updating the inventory count through an HQL by passing a list of the IDs instead of updating the property in the object itself since the latter results in N update statements.

Upvotes: 1

Related Questions