tldr; in this article I cover these basic Hibernate annotations: @Entity, @Table, @Column, @OneToOne, @OneToMany. In a future follow up post, I will explain @ManyToOne, @ManyToMany, and @SecondaryTable (and maybe a few others).

Spring is filled with annotations. It can make your life so much easier and at the same time, a living nightmare. Accidentally misplace an annotation? Spring will break and cry and show only semi-useful error messages. Use the wrong annotation altogether and you’ll have no luck deciphering those error messages. It can be tricky, but when used correctly it can save you from writing a whole lot of code and, even better, having to think out complicated object relationships and try to weave it all together.

On a recent project I noticed that out of all the annotations my team struggled with, Hibernate annotations were by far the most difficult. Trying to take a very complicated relational database and figure out how to get hibernate to just do what we wanted to do proved very difficult. (Granted some of the issues might’ve been from a very very complicated data schema and big design changes happening often, but I digress 🤐)

Since I noticed some of the documentation out there was a little hard to read, I thought I would try and make a clear and simple example on how to use these annotations.

Getting Started

You can clone a copy of the code :point_right: here

Included in the README of the repository are instructions on how to set up the database using Docker :whale:. If you haven’t set up docker before, I really recommend doing so. It makes quickly spinning up things like databases super easy. If you would rather work with a local instance, then feel free to set up mysql (or something similar) and change the application.properties file of the project as necessary.

The Schema

Get your barista hats, we’ll be setting up a Coffee Shop :coffee:! By the end of this tutorial, our database will look like this:

CoffeeShop

shop_id name owner_id
1 Gotham Coffee 65


Owner

owner_id first_name last_name
65 Bruce Wayne


CoffeeShopEmployees

shop_id employee_id
1 22
1 23


Employees

employee_id first_name last_name
22 Richard Grayson
23 Tim Drake


**I’ve purposely made the ids in this example unique so you can easily see the relationships

Creating a Coffee Shop

This app will be as simple as possible: we have one controller that will serve as our REST endpoint and when we send information about our new coffee shop to it, it will call our repository class to make changes to the database.

@RequestMapping("/")
@RestController
public class CoffeeShopController {

  @Autowired
  private CoffeeShopRepository coffeeShopRepository;

  @RequestMapping(method = RequestMethod.POST)
  public ResponseEntity<?> add(@RequestBody CoffeeShop coffeeShop) {
    CoffeeShop savedShop = coffeeShopRepository.save(coffeeShop);
    return new ResponseEntity<Object>(savedShop, HttpStatus.OK);
  }
}


The repository will be an interface that extends from the JpaRepository. In this case we’ll be utilizing the useful default methods that the jpa comes with (save, findAll, etc). The benefit of using Hibernate Annotations is that it helps keep your repository class as small and as simple as possible; hopefully eliminating any need for any mapping or custom logic.

@Repository
public interface CoffeeShopRepository extends JpaRepository<CoffeeShop, Long> {}


For our objects, let’s start off with a basic coffee shop with only a name and an id:

@Entity
@NoArgsConstructor // auto generate a constructor
@Getter // auto generate getter methods for each parameter
@Setter // auto generate setter methods for each parameter
public class CoffeeShop {

  @Id
  @GeneratedValue
  @Column(name="shop_id")
  private Long id;

  private String name;
}

:star2: Important things to note:

  • The inclusion of the @Entity annotation. Marks this object as an entity bean aka an object that will be used in database transactions.

  • The @Id marks the id parameter as the unique identifier for the object. Doesn’t have to be an id, but I find that’s the most common in relational databases.

  • The use of lombok annotations (@Getter, @Setter, @NoArgsConstructor). This is an amazing library that helps auto generate methods and constructors that I find clutter objects. All Entities require getters and setters, and why have a huge file filled with standard methods, when 1 annotation can replace all of that? 🤔 Still not convinced? That’s ok! Just write out getter and setter methods for each parameter and an empty constructor. It’ll work just the same.

:warning: If the name of the object is different from the table use the @Table(name="") annotation.

  • i.e. if we wanted the table to be called shop because we want to eventually expand to candy shops and pet shops, we can just say:

@Entity
@Table(name="shop")
@Getter
@Setter
public class CoffeeShop {...}
  • Otherwise, it will assume the object name and the table name are the same.

  • Similarly if the column in the table does not match the naming of the parameter, use the @Column annotation like we did for the id parameter:

@Column(name="shop_id")
private Long id;
  • Other than name, @Column also has some really useful modifiers. You can make the parameter unique, required, nullable, etc. Theres a lot you can do with it.


If everything is working as it should, when we run the app and send a POST to localhost:8080 with this as the body:

json body of request
  {
   "name": "gotham coffee"
  }

We should get back this:

json body of the response
  {
   "id": 1,
   "name": "gotham coffee"
  }

If we look at our table, we should then see a new entry!

terminal view of the database
mysql> select * from coffee_shop;
+---------+---------------+
| shop_id | name          |
+---------+---------------+
|       1 | gotham coffee |
+---------+---------------+
1 row in set (0.00 sec)

Adding an Owner: @OneToOne

Our Coffee Shop is now up and running! Time to spice things up and add an owner 💁

For this example we will say each coffee shop will only belong to one owner. And each owner will only have one coffee shop. So can you guess which annotation, we’ll be using? @OneToOne :open_mouth::exclamation:

The owner will basically be the same as the coffee shop, but with its own unique parameters.

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Owner {

    @Id
    @GeneratedValue
    @Column(name="owner_id")
    private Long id;

    private String firstName;

    private String lastName;
}

And now we’ll update our coffee shop so that it has the owner.

@Entity
@NoArgsConstructor
@Getter
@Setter
public class CoffeeShop {

    @Id
    @GeneratedValue
    @Column(name="shop_id")
    private Long id;

    private String name;

    @OneToOne(cascade = {CascadeType.ALL})
    private Owner owner;
}

:star2: Things to note:

  • Adding the @OneToOne annotation tells hibernate that the relationship as we stated before is one to one, meaning one coffeeshop has one owner and vise versa. There are other relationships we can express using similar annotations (OneToMany, ManyToOne, ManyToMany). We’ll get into some of them later on.

  • The addition of the CascadeType.ALL to the annotation says that the operation you are doing should cascade down (aka if we are creating a coffeeshop with an owner that doesn’t exist, cascade and create that owner too; if you are deleting the coffeeshop, delete the owner too, etc). There are a couple of options for when cascading should take place. It’s optional so if you do not want it to happen, don’t include it.

:warning: In this case the relationship between owner and coffeeshop is :arrow_right: uni-directional (aka coffeeshop has a reference to its owner, but the owner doesn’t have a reference to its coffeeshop). This is just a design decision, if you need the relationship to be :left_right_arrow: bi-directional (they both have a reference to each other) then you can use the parameter mappedBy="". This will look the same in the database, really depends on the business logic and what data you want each object to have. So if in your app you want the owner be able to get its coffee shop just add the following along with the changes we made to CoffeeShop:

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Owner {

    @Id
    @GeneratedValue
    @Column(name="owner_id")
    private Long id;

    private String firstName;

    private String lastName;

    @OneToOne(mappedBy = "owner")
    private CoffeeShop coffeeShop;
}


Ok let’s try this all out (I just went with the uni-directional route). Send the updated json to localhost:8080.

json body of the request
{
  "name": "gotham coffee",
  "owner": {
    "firstName": "Bruce",
    "lastName": "Wayne"
  }
}
json body of the response
{
  "id": 1,
  "name": "gotham coffee",
  "owner": {
    "id": 1,
    "firstName": "Bruce",
    "lastName": "Wayne"
  }
}

And if we check out the database:

mysql> select * from coffee_shop;
+---------+---------------+----------+
| shop_id | name          | owner_id |
+---------+---------------+----------+
|       1 | gotham coffee |        1 |
+---------+---------------+----------+
1 row in set (0.00 sec)

mysql> select * from owner;
+----------+------------+-----------+
| owner_id | first_name | last_name |
+----------+------------+-----------+
|        1 | Bruce      | Wayne     |
+----------+------------+-----------+
1 row in set (0.00 sec)

Adding Employees: @OneToMany

One Coffee Shop can have many employees. This means we’ll use the @OneToMany annotation :tada:

Similar to CoffeeShop and Owner, we’ll make an Employee :two_men_holding_hands:

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Employee {

    @Id
    @GeneratedValue
    @Column(name="employee_id")
    private Long id;

    private String firstName;

    private String lastName;
}

Nothing special here. Now we’ll add employee to CoffeeShop

@Entity
@NoArgsConstructor
@Getter
@Setter
public class CoffeeShop {

    @Id
    @GeneratedValue
    @Column(name="shop_id")
    private Long id;

    private String name;

    @OneToOne(cascade = {CascadeType.ALL})
    @JoinColumn(name="owner_id")
    private Owner owner;

    @OneToMany(cascade = {CascadeType.ALL})
    private List<Employee> employees;
}

Very similar to Owner, except this time we’re using the @OneToMany annotation. Hibernate is smart enough to see this and create that table of foreign keys I showed in the overview of how the database will end up looking. Also note how the CoffeeShop has a List of employees. This represents that Many part of the relationship.

Just send an updated POST and see:

json body of the request
{
  "name": "gotham coffee",
  "owner": {
    "firstName": "Bruce",
    "lastName": "Wayne"
  },
  "employees": [
    {
      "firstName": "Richard",
      "lastName": "Grayson"
    },
    {
      "firstName": "Tim",
      "lastName": "Drake"
    }
  ]
}
json body of the response
{
  "id": 1,
  "name": "gotham coffee",
  "owner": {
    "id": 1,
    "firstName": "Bruce",
    "lastName": "Wayne"
  },
  "employees": [
    {
      "id": 1,
      "firstName": "Richard",
      "lastName": "Grayson"
    },
    {
      "id": 2,
      "firstName": "Tim",
      "lastName": "Drake"
    }
  ]
}

And if we check out the database:

mysql> select * from coffee_shop;
+---------+---------------+----------+
| shop_id | name          | owner_id |
+---------+---------------+----------+
|       1 | gotham coffee |        1 |
+---------+---------------+----------+
1 row in set (0.00 sec)

mysql> select * from coffee_shop_employees;
+---------------------+-----------------------+
| coffee_shop_shop_id | employees_employee_id |
+---------------------+-----------------------+
|                   1 |                     1 |
|                   1 |                     2 |
+---------------------+-----------------------+
2 rows in set (0.00 sec)

mysql> select * from employee;
+-------------+------------+-----------+
| employee_id | first_name | last_name |
+-------------+------------+-----------+
|           1 | Richard    | Grayson   |
|           2 | Tim        | Drake     |
+-------------+------------+-----------+
2 rows in set (0.00 sec)

Recap & What We Didn’t Get To

In this post, we covered how to use hibernate to create your tables and specifically how to create the one-to-one/one-to-many relationship using annotations. This is just the tip of the iceberg of hibernate’s capabilities and I want to continue with this post with examples of some more complicated annotations:

  • ManyToMany
  • ManyToOne
  • SecondaryTable

and possibly more. Feel free to reach out if any of the examples aren’t clear or if there was something you wanted to see that I did not cover.

:woman: :computer: :wave: