Project Archive

Adding Spring Data to Hibernate

In the last set of lecture notes I introduced Hibernate and we saw how to construct a set of Entity classes to model objects stored in the auction database. I also showed example code that demonstrated how to use an EntityManager to move objects into and out of the database.

In this lecture we are going to use the Spring Data framework to simplify access to the database. We will still be working with Entity classes, but we will no longer be working directly with EntityManager objects.

Working with Spring Data Repositories

Spring Data is a framework designed to make it easier to work with a variety of different types of data stores. In this example I am going to be using Spring Data to interact with a relational database. In a later lecture we will see examples of how we can use the same Spring Data mechanisms to interact with a NoSQL database.

Data access in Spring Data is managed through the use of Spring Data repository objects. In an project where we are working with a relational database Spring Data will expect us to create a repository for every table in our database.

Here is a very simple example. One of the tables in our auction database is the bids table, where we will be storing Bid objects. Here is the code for the Spring Data repository we will be using to access this table:

public interface BidRepository extends JpaRepository<Bid,Integer> {
}

Here are some things to note about this code:

The textbook discussed the JpaRepository interface in chapter 7. You can also find documentation on this interface online.

The JpaRepository interface contains many useful methods, but in some cases we may need to supplement that interface with a few custom methods. Here is a simple example: this is the repository we will use to interact with the users table:

public interface UserRepository extends JpaRepository<User,UUID>{
  List<User> findByName(String name);
}

This example shows an example of a custom find method. When Spring Data generates a class to go with this interface it will construct the code for this method automatically based on just the name we provided. This example will search the table of users to find all of the users with a particular name.

Spring Data can generate a wide range of custom methods for you automatically based solely on the name of the method. You can read more about Spring Data's automatic code generation in chapter seven.

Finally, even though the built-in methods in JpaRepository and the automatic method generation technique will allow us to handle almost everything we need to do, there will be a few unusual cases where we will need to construct custom code. To make this possible Spring Data also allows us to construct custom queries.

Here is an example. During the purchasing process we will need to run a number of custom queries to locate Purchase records that are at a particular step in the purchase process. Here is the repository that deals with Purchase objects:

public interface PurchaseRepository extends JpaRepository<Purchase,UUID>{
  @Query("select p from Purchase p where p.status='Won_bid' and p.bid.bidder.userid=:user")
  List<Purchase> findOffers(UUID user);
  
  @Query("select p from Purchase p where p.status='Confirmed' and p.bid.auction.seller.userid=:user")
  List<Purchase> findSales(UUID user);
  
  @Query("select p from Purchase p where p.status='Charged' and p.bid.bidder.userid=:user")
  List<Purchase> findBills(UUID user);

  @Query("select p from Purchase p where p.status='Paid' and p.bid.auction.seller.userid=:user")
  List<Purchase> findSoldBills(UUID user);

  @Query("select p from Purchase p where p.status='Charged' and p.bid.bidder.userid=:user")
  List<Purchase> findShippedBills(UUID user);
}

In each of these cases I have annotated a custom find method with a @Query annotation that contains some JPQL code. When Spring Boot generates the class to go with this interface it will automatically generate code for each of these methods, and that code will execute the JPQL query we have provided.

You can also see in this example that each of the queries features placeholders. To ensure that those placeholders get filled in correctly when the query runs, we set up method parameters that the method will use to supply values for the placeholders. Most importantly, the parameter name matches the name of the JPQL placeholder we want to match it with.

From DAO classes to Service classes

These repositories will now provide us will all of the access to the database we will need. This also means that we will no longer be constructing DAO classes to manage database interactions.

One thing that our former DAO classes did for us that the new repository classes can not do was to manage the application logic for our application. For example, when a user wants to post a new User object to the system we had to check to make sure that there was not already a User in the system with the same user name.

To give this application logic a home, we are going to be supplementing the repository classes with service classes. The service classes will talk to the repositories, and the controllers in turn will only communicate with the service classes. The new version of the project has UserService, AuctionService, and PurchaseService classes. The controller classes have all been rewritten to work with the new service classes.

Here is a sample of code from one of the new service classes. Here is the method to insert a new User into the system.

@Service
public class UserService {
  @Autowired 
    PasswordService passwordService;
  
  @Autowired
  UserRepository userRepository;
  
  public String save(UserDTO user) {
    List<User> existing = userRepository.findByName(user.getName());
    if(existing.size() > 0)
      return "Duplicate";
    
    User newUser = new User();
    newUser.setName(user.getName());
    String hash = passwordService.hashPassword(user.getPassword());
      newUser.setPassword(hash);
    userRepository.save(newUser);
    return newUser.getUserid().toString();
  }
}