Composite Keys in JPA

Making friends with enemies

Gratus Devanesan
Code Smells
3 min readDec 21, 2017

--

The general consensus seem to be that composite keys and Hibernate are not bedfellows. And if you are designing a system and are creating a table you should just add an auto incrementing integer index. Your table is not going to be much bigger and your code will be more manageable.

But if you are sitting on legacy tables and you want to build a new, or improved, application without tearing everything down and bringing stuff to a standstill, composite keys maybe part of your life.

But, Composite Keys are easy and can be fun…sort of.

What are Composite Keys?

Just in case you have been sitting in meetings and some senior developer or architect was throwing this term around like everyone should know what it means, and you were just sitting there nodding…

A composite key is a single index across many columns.

Let’s look at an example:
(I’m using Postgres friendly sql)

CREATE TABLE cars (
"make" VARCHAR(16),
"model" VARCHAR(16),
"trim" VARCHAR(16),
"year" INT
)

We can see that no single column would be unique. A sample of entries would look like these:

INSERT INTO cars (make, model, trim, year) 
VALUES ('Audi','A8','',2018);
INSERT INTO cars (make, model, trim, year)
VALUES ('Audi','A8','L',2018);
INSERT INTO cars (make, model, trim, year)
VALUES ('Audi','A8','S',2018);

Audi makes more than one type of car each year, in fact even more than one type of A8. The only unique value in the above three is the “trim” which in the above three instances are single characters — which even without further examples are clearly not suitable for unique indexes.

The smallest unique index we can create is by combining model, trim, and year. We can be fairly certain there will only be one A8 of type S in 2018. But we cannot be 100% certain. We can be 100% certain that Audi will only have one A8 of type S in 2018, and that Audi’s trademark lawyers will prevent any other manufacturer from calling its cars Audi. So in this imagined example our unique index would have to contain all 4 columns.

CREATE UNIQUE INDEX unique_cars_index 
ON cars (make,model,trim,year);

As you can see our composite key is simply an index spanning multiple columns.

Composite Keys in JPA with Spring and Lombok

We are using Spring, because who is still writing Java web applications without Spring these days? And Lombok makes our code look just that much nicer.

package com.example.car;


import lombok.Data;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import javax.persistence.Entity;



@Table(name = "cars")
@Entity
@Data
@IdClass(UniqueCar.class)
public class Car {


@Id
@Column(name = "Make")
//we have to have at least on property called id.
private String id;

@Id
@Column(name = "Model")
private String model;

@Id
@Column(name = "Trim")
private String trim;

@Id
@Column(name = "Year")
private int year;

}

In the code above we are defining a model called “Car” (the table is called cars but lets not argue about that). In it we are annotating all 4 properties with “@Id”. We are also annotating the class itself with “@IdClass” and telling it what its Id class is. We now need to define our Id class.

package com.example.car;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Getter;

import java.io.Serializable;


@EqualsAndHashCode
@AllArgsConstructor
@NoArgsConstructor
public class UniqueCar implements Serializable {

@Getter @Setter
private String id;

@Getter @Setter
private String model;

@Getter @Setter
private String trim;

@Getter @Setter
private int year;

}

As you can see the Id class is simply just a class that has all the index columns as properties — which in our case are all the columns. In addition it has an equals and hashcode method, which we let lombock create for us.The equals method allows Java to check for equality — this is important as your index can span across data types. The hashcode provides a simpler and faster check for equality, especially if we are serializing.

Example Code to go with this article can be found at:
https://github.com/gratuz/code-smells

That’s it. Questions? Suggestions? Improvements? Let me know below.

--

--