4. Persistence Service Code

Persistence Service Interface Code

There is a persistence interface and abstract base broken into read-only and persistent operations. For standard persistence based on Spring Data JPA repositories, just extending one of the base classes will handle basic persistence.

Example 5. PersistenceFindService

The find service takes a generic response and find response. The generic response is a wrapper, with messages, for a single result. The find response is for a standard and paginated search. It also can have messages.

                    
public interface PersistenceFindService<R extends EntityResponseResult, FR extends EntityFindResponseResult> {

    /**
     * Find a record with an id.
     */
    public R findById(Integer id);

    /**
     * Find all records.
     */
    public FR find();
    
    /**
     * Find a paginated record set.
     */
    public FR find(int page, int pageSize);

}    
                    
                

Example 6. PersistenceService

Besides a generic response and find response, the PersistenceService also takes the entity bean the persistence class handles.

                    
public interface PersistenceService<V extends PkEntityBase,
                                    R extends EntityResponseResult, FR extends EntityFindResponseResult> 
        extends PersistenceFindService<R, FR> {

    /**
     * Creates a record.
     */
    public R create(V request);

    /**
     * Updates a record.
     */
    public R update(V request);

    /**
     * Deletes person.
     */
    public ResponseResult delete(Integer id);

}
                    
                

Persistence Service Abstract Code

Example 7. AbstractPersistenceFindService

The abstract persistence find service adds another generic value representing a Spring Data JPA entity. It expects a JpaRepository, Converter, and a MessageHelper for it's constructor. The MessageHelper is just a helper bean for accessing the Spring MessageSource.

The @Transactional annotation is set on the class to be read-only. Any method will automatically have a read-only transaction in any subclass unless marked otherwise. The converter is used to convert the Spring Data JPA results into ws beans, then an abstract method is used to create the response.

                    
@Transactional(readOnly=true)
public abstract class AbstractPersistenceFindService<T extends AbstractPersistable<Integer>, V extends PkEntityBase, 
                                                     R extends EntityResponseResult, FR extends EntityFindResponseResult> 
        extends AbstractService implements PersistenceFindService<R, FR> {

    protected final JpaRepository<T, Integer> repository;
    protected final ListConverter<T, V> converter;
    
    public AbstractPersistenceFindService(JpaRepository<T, Integer> repository, ListConverter<T, V> converter, 
                                          MessageHelper messageHelper) {
        super(messageHelper);
        
        this.repository = repository;
        this.converter = converter;
    }

    @Override
    public R findById(Integer id) {
        T bean = repository.findOne(id);
        V result = (bean != null ? converter.convertTo(bean) : null);
        
        return createResponse(result);
    }

    @Override
    public FR find() {
        List<V> results = converter.convertListTo(repository.findAll(createDefaultSort()));
        
        return createFindResponse(results);
    }

    @Override
    public FR find(int page, int pageSize) {
        Page<T> pageResults = repository.findAll(new PageRequest(page, pageSize, createDefaultSort()));

        List<V> results = converter.convertListTo(pageResults.getContent());
        
        return createFindResponse(results, pageResults.getTotalElements());
    }

    /**
     * Create a response.
     */
    protected abstract R createResponse(V result);

    /**
     * Create a find response with the count representing the size of the list.
     */
    protected abstract FR createFindResponse(List<V> results);

    /**
     * Create a find response with the results representing the page request 
     * and the count representing the size of the query.
     */
    protected abstract FR createFindResponse(List<V> results, long count);
    
    /**
     * Whether or not the primary key is valid (greater than zero).
     */
    protected boolean isPrimaryKeyValid(V request) {
        return DBUtil.isPrimaryKeyValid(request);
    }

    /**
     * Creates default sort.
     */
    private Sort createDefaultSort() {
        return new Sort("lastName", "firstName");
    }

}
                    
                

Example 8. AbstractPersistenceService

The AbstractPersistenceService extends AbstractPersistenceFindService and adds the methods create/update/delete. The create & update methods are separated they can have different Spring Security annotations applied, even though they both call into the same method for actually saving (doSave).

The create/update/delete methods also all are marked with @Transactional to override the default transactional configuration since they are not read-only transactions.

                    
public abstract class AbstractPersistenceService<T extends AbstractPersistable<Integer>, V extends PkEntityBase, 
                                                 R extends EntityResponseResult, FR extends EntityFindResponseResult> 
        extends AbstractPersistenceFindService<T, V, R, FR>
        implements PersistenceService<V, R, FR> {

    protected static final String DELETE_MSG = "delete.msg";
    
    public AbstractPersistenceService(JpaRepository<T, Integer> repository, ListConverter<T, V> converter, 
                                      MessageHelper messageHelper) {
        super(repository, converter, messageHelper);
    }
    
    @Override
    @Transactional
    public R create(V request) {
        Assert.isTrue(!isPrimaryKeyValid(request), "Create should not have a valid primary key.");
        
        return doSave(request);
    }

    @Override
    @Transactional
    public final R update(V request) {
        Assert.isTrue(isPrimaryKeyValid(request), "Update should have a valid primary key.");
        
        return doSave(request);
    }

    @Override
    @Transactional
    public ResponseResult delete(Integer id) {
        return doDelete(id);
    }

    /**
     * Processes save.  Can be overridden for custom save logic.
     */
    protected R doSave(V request) {
        V result = null;
        
        T convertedRequest = converter.convertFrom(request);

        // issues with lock version updating if flush isn't called
        T bean = repository.saveAndFlush(convertedRequest);
        
        result = converter.convertTo(bean);
        
        return createSaveResponse(result);
    }

    /**
     * Processes delete. Can be overridden for custom save logic.
     */
    protected ResponseResult doDelete(long id) {
        repository.delete((int) id);
        repository.flush();

        return createDeleteResponse();
    }

    /**
     * Create a save response.
     */
    protected abstract R createSaveResponse(V result);

    protected ResponseResult createDeleteResponse() {
        return new ResponseResult().withMessageList(new Message().withMessageType(MessageType.INFO)
                .withMessageKey(DELETE_MSG).withMessage(getMessage(DELETE_MSG)));
    }

}