This library will greatly simplify the process of creating a CRUD interface to the entities of model, allowing to focus in the design itself and removing the need of a graphical user interface or making queries the database to manipulate data, in early development stages.
This library is intended to be used using Spring Boot 2.3 and JPA repositories.
- Add to POM:
<dependency>
<groupId>com.github.jobosk</groupId>
<artifactId>crudifier</artifactId>
<version>1.3</version>
</dependency>
Note: You might need to add the following repository to pick it up:
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
- Define your model entities
- Define ID resolvers extending: GenericIdResolver<YOUR_ENTITY, YOUR_ENTITYS_ID> (no extra logic required)
- Indicate ID resolver for each entity with:
@JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class
, property = "id"
, scope = YOUR_ENTITY.class
, resolver = YOUR_ENTITY_ID_RESOLVER.class
)
- Define repository interfaces extending: JpaRepository<YOUR_ENTITY, YOUR_ENTITYS_ID>
- Define service interfaces extending: ICrudService<YOUR_ENTITY, YOUR_ENTITYS_ID>
- Define service implementations implementing them, and extending: CrudService<YOUR_ENTITY, YOUR_ENTITYS_ID>
- Declare public constructor for those services:
@Autowired
public YOUR_ENTITY_SERVICE(final YOUR_ENTITY_REPOSITORY repository) {
super(repository);
}
-
Define controllers (using the URL path mapping of your choice) extending: CrudController<YOUR_ENTITY, YOUR_ENTITYS_ID> (no extra logic required)
-
Add the following bean in any configuration class (to avoid Jackson bug describen below):
@Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomSerializationFeatures() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(
SerializationFeature.HANDLE_CIRCULAR_REFERENCE_INDIVIDUALLY_FOR_COLLECTIONS
);
}
Note: If the previous feature does not compile, it means that some other dependency (probably a Spring one, since it comes by default with it) has injected 'jackson-databind' as a transitive dependency (and it does not have the fix for the bug). This can be solved by adding the following dependency before any other that might add the one without the fix:
<dependency>
<groupId>com.github.jobosk</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11-fix_2791</version>
</dependency>
This will simplify the implementation of entity controllers, exposing all the required endpoints with just a few lines of code:
@RestController
@RequestMapping("entity")
public class EntityController extends CrudController<Entity, UUID> {
@Override
@GetMapping(ID_PATH)
@ResponseBody
public Entity findOne(final @PathVariable(CrudConstant.Http.Param.ID) UUID id) {
return service.find(id);
}
@Override
@GetMapping
@ResponseBody
public Collection<Entity> findAll(final @RequestParam Map<String, String> parameters
, final HttpServletResponse response) {
return service.findAll(parameters, response);
}
@Override
@PostMapping
@ResponseBody
public Entity create(final @Valid @RequestBody Entity entity) {
return service.create(entity);
}
@PutMapping(ID_PATH)
@ResponseBody
public Entity update(final @PathVariable(CrudConstant.Http.Param.ID) UUID id
, final @RequestBody Map<String, Object> fields) {
return update(service.find(id), fields);
}
@Override
public Entity update(final Entity entity, final Map<String, Object> fields) {
return service.update(entity, fields);
}
@Override
@DeleteMapping(ID_PATH)
public boolean delete(final @PathVariable(CrudConstant.Http.Param.ID) UUID id) {
return service.delete(id);
}
}
Request:
POST /<entity_mapping>
{
"name": "new entity"
}
Response:
200:
{
"id": <UUID>
, "name": "new entity"
}
Request:
PUT /<entity_mapping>/
{
"name": "edited name of present entity"
}
Response:
200:
{
"id": <UUID>
, "name": "edited name of present entity"
}
Request:
DELETE /<entity_mapping>/
Response:
200
Request:
GET /<entity_mapping>[?<filter>][&<filter>...]
filter=
- <entity attribute>=<value>
- page=<0..N>
- size=<1..N>
- order=[-]<entity attribute>
Note Filtering entity attributes by String values aplies a 'contains' match, whilst numeric values apply an exact match. Also, filters 'page' and 'size' must be both present if one is.
Response:
200:
[
{
"id": <UUID>
, "name": "first present entity"
}
, {
"id": <UUID>
, "name": "second present entity"
}
]
Request:
GET /<entity_mapping>/
Response:
200:
{
"id": <UUID>
, "name": "present entity"
}
- Due to the way Jackson handles circular dependencies, serialization of two or more different entities poiting to the same sub-entity will result in the serialization of the sub-entity as an object in the first occurrence and as an ID the following ones.
- There is a bug in the Jackson serializer mentioned in the following issue:
FasterXML/jackson-databind#2791
The current fix for this bug relies in the presence of the following dependency (within the 'crudifier' library itself, no need to add it again, unless some other dependency includes the one without the fix as a transitive dependency):
<dependency>
<groupId>com.github.jobosk</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11-fix_2791</version>
</dependency>
To make use of the bug-fixing "functionality", a Jackson configuration bean (indicated in the 'How to' section) is required.