Project Archive

From JDBC to Hibernate

In the first version of the Auction application I used JDBC to communicate with the database. We used the JdbcTemplate class provided by Spring Boot to do all of our database interactions, and we also wrote SQL for every single database interaction.

In the next version of the Auction application we are going to be using a combination of Spring Data JPA and Hibernate to handle all of our database interactions. Hibernate is an example of an object-relational mapping system, or ORM. Hibernate automates the process of converting data in database tables into Java objects. The Jakarta Persistence Architecture, or JPA, is a set of software standards for working with ORM systems like Hibernate. Spring Data is a Spring project that aims to make working with JPA and ORM systems easier and more straightforward.

In the next version of the Auction application I am going to be using Hibernate, but I won't yet be taking advantage of the full power offered by Spring Data. I will be demonstrating how to make more complete use of Spring Data in the next version after this one.

The material in these notes is based on the material in chapter six of our textbook. To get a more complete picture of what Hibernate can do you should read that chapter after reviewing these notes.

Getting started

The first step in using Hibernate is to set up a new project. In the initial project configuration we are going to be making one important change. In place of the JDBC API project dependency we are going to substitute the Spring Data JPA dependency.

In the initial project configuration we are still going to be working with the same MySQL database. The entries in our application.properties file that set up the database connection will stay the same as in our first project:

spring.datasource.url=jdbc:mysql://localhost:3306/auction
spring.datasource.username=student
spring.datasource.password=Cmsc250!
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

Hibernate operates in two modes. We can use Hibernate to generate a set of database tables for us automatically from a set of Java classes we define, or we can instead set up a set of Java classes to model tables that already exist in a database. We will be taking the latter approach in the example below.

Entity classes

The first step in using Hibernate is to set up an Entity class for every table in our database. When Hibernate pulls data from one of our database tables it will convert that data into a Java Entity class that corresponds to the table.

Here is a typical example. One of the simpler tables in our database is the shipping table, which has this structure:

Here is the corresponding Entity class for this table:

@Entity
public class Shipping {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Integer shippingid;
  @ManyToOne
  @JoinColumn(name="user")
  private User user;
  private String displayname;
  private String addressone;
  private String addresstwo;
  private String city;
  private String state;
  private String zip;
  
  public Shipping() {}

  // Getters and setters not shown
}

Here are some important things to note about this class:

Here now is a second, slightly more complex example. Here is the Hibernate Entity class to match the users table in our database.

@Entity
@Table(name="users")
public class User {

  @Id
  @GeneratedValue(strategy = GenerationType.UUID)
  @Column(columnDefinition = "VARCHAR(45)")
  @JdbcTypeCode(SqlTypes.VARCHAR)
  private UUID userid;
  private String name;
  private String password;
  @OneToOne(mappedBy="user")
  private Profile profile;
  @OneToMany(mappedBy="user")
  List<Shipping> shipping;
  @OneToMany(mappedBy="seller")
  List<Auction> auctions;
  
  public User() {}

  // Getter and setter methods left out
}

Here are some additional things to note about this example:

Working with the EntityManager class

To get our Entity objects into and out of the database we will be making use of an EntityManager object. This object will appear as a member variable in a @Repository class.

Here is a simple example to show how this is all set up. Here is a portion of the AuctionDAO class:

@Repository
public class AuctionDAO {
  @Autowired
  EntityManager em;
  
  @Transactional
  public String save(AuctionDTO auction) {
    User user = em.find(User.class,UUID.fromString(auction.getSeller()));
    if(user == null) 
      return "Bad Id";
    
    Auction newAuction = new Auction(auction);
    newAuction.setSeller(user);
    em.persist(newAuction);
    
    for(String t : auction.getTags()) {
      Tag newTag = new Tag();
      newTag.setAuction(newAuction);
      newTag.setTag(t);
      em.persist(newTag);
    }
    return newAuction.getAuctionid().toString();
  }
}

Here are some things to note about this example:

Using custom JPQL queries

Hibernate automates a great many database operations for us. For example, if we know the UUID for a user and want to get a list of all of the Auction objects published by that user we simply do

User user = em.find(User.class,userid);
List<Auction> auctions = user.getAuctions();

There are still a few relatively rare cases in which we are going to have to give Hibernate some additional help in finding things. A typical example is finding a list of all currently "live" auctions. Here is the method from the AuctionDAO that does this:

@Transactional
public List<Auction> findActive() {
  return em.createQuery("select a from Auction a where opens<=:now and closes>=:now",Auction.class)
      .setParameter("now", LocalDate.now()).getResultList();
}

This code runs a custom query using the JPQL query language. JPQL is essentially an object-oriented variant of SQL. Just like a prepared statement in SQL, a JPQL query can have placeholders that get replaced by subsequent calls to setParameter(). Chapter six in the textbook provides a lot more information about how JPQL works and gives lots of examples.

DTO classes

One unfortunate problem with Entity classes is that they are only suitable to get data to and from the database. Our application is also going to feature REST controllers, and those controllers will be passing objects to and from clients. There is a temptation to set up controller methods to simply return Entity classes: for example, one of our REST methods is GET /auctions, which is supposed to return a list of "live" auctions. We could write that method this way:

@GetMapping
public List<Auction> getActiveAuctions() {
  return dao.findActive();
}

This will almost work: Spring Boot can convert the list of Auction objects to JSON for you and send that to the client. Unfortunately, the JSON that the client will be getting will have a very big and obvious problem. To see what this problem is, here is how the Auction Entity class is defined:

@Entity
@Table(name="auctions")
public class Auction {

  @Id
  @GeneratedValue(strategy = GenerationType.UUID)
  @Column(columnDefinition = "VARCHAR(45)")
  @JdbcTypeCode(SqlTypes.VARCHAR)
  private UUID auctionid;
  @ManyToOne
  @JoinColumn(name="seller")
  private User seller;
  private String item;
  private String description;
  private String imageurl;
  private int reserve;
  private LocalDate opens;
  private LocalDate closes;
  private boolean completed;
  @OneToMany(mappedBy="auction")
  List<Tag> tags;
  @OneToMany(mappedBy="auction")
  List<Bid> bids;
  
  public Auction() {}

  // Getters and setters left off
}

The problem here is that the Auction Entity contains references to a bunch of other classes. When the Auction object gets converted to JSON the embedded objects and lists will get embedded in the JSON code for the Auction. This in turn leads to big trouble: for example, the embedded User object here also contains a list of all of the Auctions owned by that User. Each of those Auctions in turn contains a reference to a User, which contains more Auctions, and so on.

The solution to this problem is to never send Entity classes to clients as responses to REST requests. Instead, we will couple each Entity class with a companion class called a data transfer object, or DTO. The DTO classes are designed to convert to and from JSON in a simple way. For example, here is the code for the AuctionDTO class:

public class AuctionDTO {
  private String auctionid;
  private String seller;
  private String item;
  private String description;
  private String imageurl;
  private int reserve;
  private String opens;
  private String closes;
  private boolean completed;
  private List<String> tags;
  
  public AuctionDTO() {
    this.tags = new ArrayList<String>();
  }

  public AuctionDTO(Auction core) {
    auctionid = core.getAuctionid().toString();
    seller = core.getSeller().getUserid().toString();
    item = core.getItem();
    description = core.getDescription();
    imageurl = core.getImageurl();
    reserve = core.getReserve();
    opens = core.getOpens().toString();
    closes = core.getCloses().toString();
    completed = core.getCompleted();
    tags = new ArrayList<String>();
    List<Tag> tagList = core.getTags();
    for(Tag t : tagList) {
      tags.add(t.getTag());
    }
  }

  // Getters and setters left out
}

One of the key features of the DTO class is a custom constructor that can initialize the DTO from the corresponding Entity class.

Here now is the actual code for the REST controller method that returns the list of active auctions:

@GetMapping
public ResponseEntity<List<AuctionDTO>> findActiveAuctions() {
  List<Auction> auctions = dao.findActive();
  List<AuctionDTO> results = new ArrayList<AuctionDTO>();
  for(Auction a : auctions) {
    results.add(new AuctionDTO(a));
  }
  return ResponseEntity.ok().body(results);
}

The method returns a list of AuctionDTO objects instead of the more problematic Auction objects.