Spring Data MongoDB

This post is all about the setup of spring data mongodb for the Mos Erp application.

I wanted to create a backend application with a clear REST interface. Spring provides a very sophisticated framework stack to implement it (and I recently listened to a speak from Juergen Heller :-). Anyway this setup should give me lot of functionality with little code.

Maven Dependencies

All my poms derive from the spring-boot-parent. This takes care off most of my dependency versions:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.0.RELEASE</version>
</parent>

Each backend module providing a rest interface has additionally the following dependencies:

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Complete examples:

Mongo DB Configuration

@Configuration
@EnableMongoAuditing
@EnableMongoRepositories
public class MongoDbConfiguration {

    @Bean
    public AuditorAware<String> myAuditorProvider() {
        return new SpringSecurityAuditorAware();
    }

    @Bean
    @Primary
    public CustomConversions customConversions() {
        List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
        converterList.add(new BigDecimalWrapperToStringConverter());
        converterList.add(new StringToQuantityConverter());
        converterList.add(new StringToPriceConverter());
        converterList.add(new RestUriToStringConverter());
        converterList.add(new StringToRestUriConverter());
        return new CustomConversions(converterList);
    }

}

The EnableMongoAuditing annotation and myAuditorProvider method are used to auto fill the following fields into my entities:

    @CreatedBy
    @Order(Ordered.LOWEST_PRECEDENCE-3)
    private String createdBy;

    @CreatedDate
    @Order(Ordered.LOWEST_PRECEDENCE-2)
    private Instant createdDate;

    @LastModifiedBy
    @Order(Ordered.LOWEST_PRECEDENCE-1)
    private String lastModifiedBy;

    @LastModifiedDate
    @Order(Ordered.LOWEST_PRECEDENCE)
    private Instant lastModifiedDate;

Full example

Each module has one additional property, the mongo url:

spring:
  data:
    mongodb:
      uri: mongodb://${MONGO_URL:localhost:27017}/environment

Full example

Additional Converters

As you could see in the configuration class, there are some additional converters registered. Mainly because we use special wrapper classes for the most important numbers (Price and Quantity). The converters used by spring mongodb are standard spring converters that convert to or from String.

Example:

public class StringToPriceConverter implements Converter<String, Price> {

    @Override
    public Price convert(String source) {
        return StringUtils.hasText(source) ? new Price(source) : null;
    }

}

Basic Repository Interface

All repositories in the application derive from a single basic repository interface. Spring has quite a few to use and I want to be able to switch to another basic interface when needed.

@PreAuthorize("hasRole('user')")
@NoRepositoryBean 
public interface EntityRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
}

The PreAuthorize annotation is for the rest interface.

Auto Creation of numeric Ids

Normally spring mongodb will create a so called ObjectId as id field, which is an unreadable hex string. Of course it has some advantages (concurrency!), but I need a human readable id for most of my entities to show to the users.

It all starts with listening to some mongo specific events:

@Component
public class CreateIdListener implements ApplicationListener<MongoMappingEvent<?>> {

    @Autowired
    private SequenceService sequenceService;

    @Override
    public void onApplicationEvent(MongoMappingEvent<?> event) {
        if (!(event instanceof BeforeSaveEvent)) {
            return;
        }
        if (!IdentifiableEntity.class.isAssignableFrom(event.getSource().getClass())) {
            return;
        }
        IdentifiableEntity source = (IdentifiableEntity) event.getSource();
        if (source.getId() != null) {
            return;
        }
        DBObject dbObject = event.getDBObject();
        String id = sequenceService.getNextIt(event.getSource().getClass());
        source.setId(id);
        dbObject.put("_id", id);
    }

}

Whenever a new entity (IdentifiableEntity is one the base classes for all entities) without an id is saved into the DB, it gets a new generated id assigned.

Internally the SequenceService uses an internal table to create id increments. This class is thread safe, but not safe for multiple jvm instances. Future versions will have to improve that (by looking e.g. at the Hibernate Table generator).