Helps binding IDs of objects/entities to their type and thus prevents using IDs for the wrong object/entity - and implicitly documents the entity-type associated to the ID.
Have you ever mixed up your variables containing UUID/String/Long/... IDs in JPA/Hibernate/JAX-RS or other frameworks?
Like maybe so?
import java.util.UUID;
class SomeEntity {
@Id
public UUID id = UUID.randomUUID();
}
class OtherEntity {
@Id
public UUID id;
}
class Foo {
void doStuff(OtherEntity otherEntity) {
// id gets "transformed" to id of wrong entity:
UUID id = otherEntity.getId();
// and now you e.g. try to load SomeEntity
// with the id of OtherEntity but did not find anything:
SomeEntity someEntity = entityManager.find(SomeEntity.class, id);
}
}
id-class to the rescue: now you can have typed IDs bound to the entity-type:
import com.github.honoluluhenk.idclass.ID;
public class SomeEntity {
@Id
public ID<SomeEntity> id = ID.randomUUID(SomeEntity.class); // directly support ID generation
}
class OtherEntity {
@Id
public UUID id;
}
class Foo {
void doStuff(OtherEntity otherEntity) {
// compiler error: ID<OtherEntity> is not compatible with ID<SomeEntity>
ID<SomeEntity> id = otherEntity.getId();
// direct integration into hibernate!
SomeEntity someEntity = entityManager.find(SomeEntity.class, id);
}
}
(this example is using UUIDs, but there are more options and it's customizable)
You will also find modules to integrate this nicely into
Install using maven:
<dependencies>
<dependency>
<groupId>com.github.honoluluhenk.id-class</groupId>
<artifactId>id-class</artifactId>
<version>${id-class-parent.version}<!-- see github releases --></version>
</dependency>
</dependencies>
Basic usage:
The ID class mainly consists of various factory methods:
class Foo {
private ID<Foo> id = ID.randomUUID();
private ID<Foo> id = ID.of(UUID.fromString("af897f93-9067-48a3-96aa-974511a5c3c4"));
private ID<Foo> id = ID.of("cb310f0f-596e-48f5-b6b6-25301d589fe5");
//... and more
}
ID is immutable, comes with hashCode()/equals(), a nice toString() and implements Serializable.
See javadoc comments or ID.java source.
Most of its value actually comes from using it in one of the integrations, see below.
Install using maven (also pulls in the basic ID class):
<dependencies>
<dependency>
<groupId>com.github.honoluluhenk.id-class</groupId>
<artifactId>id-class-jackson-integration</artifactId>
<version>${id-class-parent.version}</version>
</dependency>
<!-- optional, depending on your requirements: -->
</dependencies>
You need to declare dependencies to jackson yourself (so you don't end up in dependency hell)!
Jackson integration contains two parts:
- a ParamConverter
/ ParamConverterProvider to make
REST @PathParams work (automatically configured by
the
@Provider
annotation) - a Jackson module containing Serializer/Deserializer to convert IDs in object bodys.
You need to manually customize the Jackson
Databind ObjectMapper
.
Fortunately, There's a module for that:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.honoluluhenk.idclass.integration.jackson.IDTypeModule;
class MyObjectMapperCustomizer {
void customize(ObjectMapper om) {
om.registerModule(new IDTypeModule());
}
}
Now you can use it in all possible places in JAX-RS ressources/DTOs:
class JaxHello {
private ID<SomeEntity> entityID;
private String name;
private Map<ID<SomeEntity>, SomeEntity> entityMap;
// getters/setters omitted for brevity
}
@Path("hello")
public class HelloResource {
@POST
@Path("{id}")
public JaxHello sayHello(@PathParam("id") ID<SomeEntity> entityID) {
return someService.findEntity(entityID);
}
}
Install using maven (also pulls in the basic ID class):
<dependencies>
<dependency>
<groupId>com.github.honoluluhenk.id-class</groupId>
<artifactId>id-class-jpa-hibernate-integration</artifactId>
<version>${id-class-parent.version}</version>
</dependency>
</dependencies>
You need to declare dependencies to hibernate yourself (so you don't end up in dependency hell)!
No you can start using the converter by attaching the
Hibernate @Type
or @TypeDef
annotation
to the field in your entity:
import com.github.honoluluhenk.idclass.ID;
import org.hibernate.annotations.Type;
@Entity
public class SomeEntity {
@Id
@Type(type = "com.github.honoluluhenk.idclass.integration.jpahibernate.IDType")
private ID<SomeEntity> id;
// ...
}
Install using maven (also pulls in id-class-jpa-hibernate-integration and the basic id-class).
<dependencies>
<dependency>
<groupId>com.github.honoluluhenk.id-class</groupId>
<artifactId>id-class-jpa22-smartentitymanager</artifactId>
<!-- JPA 2.1: (please open an issue if you need another JPA 2.x version)
<artifactId>id-class-jpa21-smartentitymanager</artifactId>
-->
<version>${id-class-parent.version}</version>
</dependency>
</dependencies>
This package enhances the standard JPA EntityManager so you can use the ID classes directly.
This also works with Spring-Data!
import com.github.honoluluhenk.idclass.ID;
import com.github.honoluluhenk.idclass.integration.jpahibernate.SmartEntityManager;
@RequestScoped
@Transactional
class MyDAO {
@PersistenceContext
private EntityManager em;
private SmartEntityManager db() {
return new SmartEntityManager(em);
}
// easier in the long run: implement your own @Producer and directly inject here:
// @Inject
// private SmartEntityManager sem;
public MyEntity findById(ID<MyEntity> entityID) {
return db()
.find(entityID);
}
// also implemented: Java8 Optionals:
public Optional<MyEntity> findOneByID(ID<MyEntity> entityID) {
return db()
.findOne(entityID);
// same for Query/TypedQuery/StoreProcedureQuery: query.getSoleResult()
}
}
SmartEntityManager might also be injected via e.g. your own @Produce method (CDI) or a Spring FactoryBean.
Example for a CDI producer:
import javax.enterprise.inject.Produces;
import com.github.honoluluhenk.idclass.integration.jpahibernate.SmartEntityManager;
@ApplicationScoped
public class SmartEntityManagerProducer {
@PersistenceContext
EntityManager defaultEntitymanager;
@Produces
@RequestScoped
public SmartEntityManager produceSmartEntityManager() {
return new SmartEntityManager(defaultEntitymanager);
}
}
Thanks to Fabio for this great idea a long time ago!