Master java skills

Hibernate Identifiers

Hibernate identifiers model the primary key in entity classes. It is mandatory for every entity to have an indetifier. We can define indentifiers in many ways. In this tutorial, we will take a deep dive into all those ways. At high level, these identifiers are : Simple Identifiers, Generated Identifiers, Custom Identifiers, Composite Identifiers, and Derived Identifiers.

Simple Identifiers

If we have a single attribute in our class which can uniquely identify our entity, we can simply annotate that attribute with @Id annotation as shown below. All Java primitive and their wrappers can be identifiers. String, Date, BigDecimal, BigIntegers can also be identifiers.

@Entity
@Table(name="Teacher11")
public class Teacher {
	
	@Id
	private long id;

Generated Identifiers

If we want our identifiers to be generated automatically, then we can use @GeneratedValue annotation on an entity class data member.

@Entity
@Table(name="Teacher11")
public class Teacher {
	
	@Id
        @GeneratedValue
	private long id;

Note : We can auto generate only either integer type (byte, short, int, long) or a UUID type identifier.

There are four kinds of key generation strategies that can be used with @GeneratedValue annotation.

  1. Auto Generation: This is the default key generation strategy. The behaviour of this strategy varies from one JPA persistence provider to another. In Hibernate, if the identifier attribute is UUID type, it uses the UUIDGenerator; otherwise, it defaults to the sequence generation strategy.

Note: UUID generation is supported from Hibernate 5 and higher versions and is 36 characters in length.

@Entity
public class Teacher {
 
    @Id
    @GeneratedValue
    private UUID id;
}

The generated value in the above example would be of the form ‘6dg8s987-9654-7h02-09kj-21xfds2ejh192’

2. Identity Generation:

For Identity generation strategy, Hibernate uses org.hibernate.id.IdentityGenerator class of Hibernate api to generate identifier. The values are generated by the identity column of the database that means they are auto-incremented.

@Entity
public class Teacher {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
}

3. Sequence Generation:

In this strategy, database Sequences are used to generate identifiers. Hibernate uses org.hibernate.id.enhanced.SequenceStyleGenerator class for this purpose.

Note: If database doesn’t support sequences, hibernate automatically switches to TABLE Key generation strategy.

We can specify the database sequence that we want to use. If we don’t, hibernate uses an implicit sequence named hibernate_sequence.

In the below example, we are using database sequence named ‘teacher_seq’.

@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "teacher_generator")
    @SequenceGenerator(name = "teacher_generator", sequenceName = "teacher_seq", allocationSize = 100)
    private long id;
}

In the below example, since we are not specifying any database sequence, implicit sequence hibernate_sequence will be used.

@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
}

4. Table Generation:

Hibernate’s org.hibernate.id.enhanced.TableGenerator class uses an underlying database table. This table can hold multiple segments of an identifier generation values. By default, Hibernate uses the hibernate_sequences table.

@Entity
public class Teacher {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "teacher_generator")
    @TableGenerator(name = "teacher_generator", table = "tchr_gntr_tbl", pkColumnName = "seq_id", 
      valueColumnName = "seq_value")
    private long id;
}

In the above example, name of the table that is used is tchr_gntr_tbl. Primary key column name of this table is seq_id, and the column that is used to generate identifier is named as ‘seq_value‘.

This strategy is not scalable and affects performance. It is rarely used due to its disadvantages.

Conclusion: All these strategies will end up generating similar values but they use different mechanism.

Custom Identifiers

We can also define our own custom generator by implementing IdentifierGenerator Interface. Let’s see one example.

public class CustomGenerator implements IdentifierGenerator, Configurable {

    private String prefix;
    private static final UNDERSCORE = "_";
  
    @Override
    public Serializable generate(SharedSessionContractImplementor session, Object entityObj) 
      throws HibernateException {

        String query = String.format("select %s from %s", 
            session.getEntityPersister(entityObj.getClass().getName(), entityObj)
              .getIdentifierPropertyName(),
            entityObj.getClass().getSimpleName());

        Stream ids = session.createQuery(query).stream();

        Long max = ids.map(obj -> obj.replace(prefix + "-", ""))
          .mapToLong(Long::parseLong)
          .max()
          .orElse(0L);

        return prefix + UNDERSCORE + (max + 1);
    }

    @Override
    public void configure(Type type, Properties properties, 
      ServiceRegistry serviceRegistry) throws MappingException {
        prefix = properties.getProperty("prefix");
    }
}

Let’s see how we can use this CustomGenerator on an entity class.

@Entity
public class Teacher {

    @Id
    @GeneratedValue(generator = "id-generator")
    @GenericGenerator(name = "id-generator", 
      parameters = @Parameter(name = "prefix", value = "tchrId"), 
      strategy = "com.sks.hb.generator.CustomGenerator")
    private String teacherId;


}

Test Class

@Test
public void testCustomIdGenerator() {
    Teacher teacher1 = new Teacher();
    session.save(teacher1);
    Teacher teacher2 = new Product();
    session.save(teacher2);

    
    assertThat(teacher1.getTeacherId()).isEqualTo("tcherId_1");
    assertThat(teacher2.getTeacherId()).isEqualTo("tcherId_2");
}

Composite Identifiers

A composite identifier is the one that represent a composite id. A composite id is a combination of more than one column in the database. Similary, a compsite identifier is represented by a primary-key class that contains one or more persistent attributes. Following are the conditions that primary key class must fulfil:

  1. It should be defined using @EmbeddedId or @IdClass annotation
  2. It should be public, serializable and have a public no-arg constructor
  3. It should implement equals() and hashCode() methods

1. Composite Identifiers using @EmbeddedId and @Embeddable: A composite id can be defined by using @EmbeddedId annotation. But before that we need to define a primary key class annotated with @Embeddable. In the below example, we are defining a primary key class called TeacherPK with is a composite key with attributes ‘teacherId’ and ‘institutionId’.

@Embeddable
public class TeacherPK implements Serializable {

    private long teacherId;
    private long institutionId;

    // constructor, getters, setters
    // equals() and hashCode() 
}

Now, we can use this primary key class as an embedded id in an entity class.

@Entity
public class Teacher {

    @EmbeddedId
    private TeacherPK teacherPK;

}
@Test
public void testSaveCompositeKey() {
    TeacherPK tchrPK = new TeacherPK();
    tchrPK.setTeacherId(1L);
    tchrPK.setInstitutionId(10L);
        
    Teacher teacher = new Teacher();
    teacher.setTeacherPK(tchrPK);
    session.save(teacher);

    assertThat(teacher.getTeacherPK().getTeacherId()).isEqualTo(1L);
    assertThat(teacher.getTeacherPK().getInstitutionId()).isEqualTo(10L);

2. Composite Identifiers using @IdClass:

This is slightly different than @EmbeddedId annotation. In this case also, we have to define primary key class which is exactly same as previous example.

public class TeacherPK implements Serializable {

    private long teacherId;
    private long institutionId;

    // constructor, getters, setters
    // equals() and hashCode() 
}

Entity class will be different. At class level, we have to use @IdClass annotation along with class name. Here we have to keep the attributes of TeacherPK class with @Id annotation.

@Entity
@IdClass(TeacherPK.class)
public class Teacher {

    @Id
    private long teacherId;
    @Id
    private long institutionId; 
}

Derived Identifiers

Using @MapsId: Derived identifiers are the ones which are derived/copied from one of entity class’s associations using @MapsId annotation. In the below example, TeacherProfile entity derives its id from Teacher entity.

@Entity
public class TeacherHistory {

    @Id
    private long teacherHistId;
    
    @OneToOne
    @MapsId
    private Teacher teacher;

}
@Entity
public class Teacher {
    @Id
    private long teacherId;
}

In the test class, let’s verify if the derieved id in TeacherHistory class is equal to teacherId in Teacher entity class.

@Test
public void testDerivedId() {        
    Teacher teacher = new Teacher();
    session.save(teacher);
       
    TeacherHistory history = new TeacherHistory();
    history.setTeacher(teacher);
    session.save(history);

    assertThat(history.getTeacherHistId()).isEqualTo(Teacher.getTeacherId());
}

Verdict

We learnt all the ways of creating identifiers in Hibernate. Practise all these examples and enjoy your favourite drink – tea, coffee, beer or whatever it maybe. Enjoy!