From 4e6002ba3bf880fafb0da6cc3c7c45bb8030d4ab Mon Sep 17 00:00:00 2001 From: Gustavo Martin Morcuende Date: Sun, 3 Jul 2016 14:37:54 +0200 Subject: [PATCH] JPA improvements --- .../de/spring/persistence/example/domain/Ad.java | 40 +++--- .../repository/AdDescriptionRepository.java | 3 +- .../example/repository/AdRepository.java | 4 +- .../org/resthub/common/service/CrudService.java | 94 +++++++++++++ .../web/controller/ServiceBasedRestController.java | 146 +++++++++++++++++++++ 5 files changed, 264 insertions(+), 23 deletions(-) create mode 100644 SpringJava/JPA/src/main/java/org/resthub/common/service/CrudService.java create mode 100644 SpringJava/JPA/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java index 6784538..be28d06 100644 --- a/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java @@ -11,10 +11,10 @@ import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.NamedNativeQueries; -import javax.persistence.NamedNativeQuery; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; +//import javax.persistence.NamedNativeQueries; +//import javax.persistence.NamedNativeQuery; +//import javax.persistence.NamedQueries; +//import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.Table; import javax.validation.constraints.Max; @@ -31,14 +31,14 @@ import de.spring.persistence.converters.LocalDateTimeAttributeConverter; // So you'd better use @Query. //http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query //See: de.spring.persistence.example.repository.AdRepository -@NamedQueries( - { - @NamedQuery( - name="Ad.findByIdQuery", - query="select a FROM AD a WHERE a.id = :id") - } - -) +//@NamedQueries( +// { +// @NamedQuery( +// name="Ad.findByIdQuery", +// query="select a from Ad a where a.id = :id) +// } +// +//) // 1. Native query IS NOT JPL. It is not portable and it is written directly in the native language // of the store. We can use special features at the cost of portability. // 2. Instead of annotating the domain class we should be using @Query annotation at the query method @@ -46,14 +46,14 @@ import de.spring.persistence.converters.LocalDateTimeAttributeConverter; // So you'd better use @Query. // http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query // See: de.spring.persistence.example.repository.AdRepository -@NamedNativeQueries( - { - @NamedNativeQuery( - name="Ad.findByIdNativeQuery", - query="SELECT * FROM AD WHERE AD.ID = :id", - resultClass=Ad.class) - } -) +//@NamedNativeQueries( +// { +// @NamedNativeQuery( +// name="Ad.findByIdNativeQuery", +// query="SELECT * FROM ad WHERE ad.id = :id", +// resultClass=Ad.class) +// } +//) public class Ad implements Serializable { @Id diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java index 120b2c8..d59ade2 100644 --- a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java @@ -1,6 +1,7 @@ package de.spring.persistence.example.repository; import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.repository.PagingAndSortingRepository; import de.spring.persistence.example.domain.Ad; @@ -9,5 +10,5 @@ import de.spring.persistence.example.domain.AdDescription; public interface AdDescriptionRepository extends PagingAndSortingRepository { // Custom Query method - Page findByAd(Ad ad); + Page findByAd(Ad ad, Pageable pageable); } diff --git a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java index b625fed..24b9c47 100644 --- a/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java +++ b/SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java @@ -10,11 +10,11 @@ public interface AdRepository extends PagingAndSortingRepository { // Named Native Query (using the native language of the store) It is not portable. // See de.spring.persistence.example.domain.Ad - @Query(value="SELECT * FROM AD WHERE AD.ID = :id", nativeQuery=true) + @Query(value="SELECT * FROM ad WHERE ad.id = :id", nativeQuery=true) Ad findByIdNativeQuery(@Param("id") Long id); // Named Query (using JPL) It is portable. // See de.spring.persistence.example.domain.Ad - @Query("SELECT * FROM AD WHERE AD.ID = :id") + @Query("select a from Ad a where a.id = :id") Ad findByIdQuery(@Param("id") Long id); } diff --git a/SpringJava/JPA/src/main/java/org/resthub/common/service/CrudService.java b/SpringJava/JPA/src/main/java/org/resthub/common/service/CrudService.java new file mode 100644 index 0000000..a9f4332 --- /dev/null +++ b/SpringJava/JPA/src/main/java/org/resthub/common/service/CrudService.java @@ -0,0 +1,94 @@ +package org.resthub.common.service; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.io.Serializable; +import java.util.Set; + +/** + * CRUD Service interface. + * + * @param Your resource POJO to manage, maybe an entity or DTO class + * @param Resource id type, usually Long or String + */ +public interface CrudService { + + /** + * Create new resource. + * + * @param resource Resource to create + * @return new resource + */ + T create(T resource); + + /** + * Update existing resource. + * + * @param resource Resource to update + * @return resource updated + */ + T update(T resource); + + /** + * Delete existing resource. + * + * @param resource Resource to delete + */ + void delete(T resource); + + /** + * Delete existing resource. + * + * @param id Resource id + */ + void delete(ID id); + + /** + * Delete all existing resource. Do not use cascade remove (not a choice -> JPA specs) + */ + void deleteAll(); + + /** + * Delete all existing resource, including linked entities with cascade delete + */ + void deleteAllWithCascade(); + + /** + * Find resource by id. + * + * @param id Resource id + * @return resource + */ + T findById(ID id); + + /** + * Find resources by their ids. + * + * @param ids Resource ids + * @return a list of retrieved resources, empty if no resource found + */ + Iterable findByIds(Set ids); + + /** + * Find all resources. + * + * @return a list of all resources. + */ + Iterable findAll(); + + /** + * Find all resources (pageable). + * + * @param pageRequest page request + * @return resources + */ + Page findAll(Pageable pageRequest); + + /** + * Count all resources. + * + * @return number of resources + */ + Long count(); +} diff --git a/SpringJava/JPA/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java b/SpringJava/JPA/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java new file mode 100644 index 0000000..cd93416 --- /dev/null +++ b/SpringJava/JPA/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java @@ -0,0 +1,146 @@ +package org.resthub.web.controller; + +import java.io.Serializable; +import java.util.Set; + +import org.resthub.common.exception.NotFoundException; +import org.resthub.common.service.CrudService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Abstract REST controller using a service implementation + *

+ *

You should extend this class when you want to use a 3 layers pattern : Repository, Service and Controller + * If you don't have a real service (also called business layer), consider using RepositoryBasedRestController

+ *

+ *

Default implementation uses "id" field (usually a Long) in order to identify resources in web request. + * If your want to identity resources by a slug (human readable identifier), your should override findById() method with for example : + *

+ *

+ * 
+ * {@literal @}Override
+ * public Sample findById({@literal @}PathVariable String id) {
+ * Sample sample = this.service.findByName(id);
+ * if (sample == null) {
+ * throw new NotFoundException();
+ * }
+ * return sample;
+ * }
+ * 
+ * 
+ * + * @param Your resource class to manage, maybe an entity or DTO class + * @param Resource id type, usually Long or String + * @param The service class + * @see RepositoryBasedRestController + */ +public abstract class ServiceBasedRestController implements + RestController { + + protected S service; + + /** + * You should override this setter in order to inject your service with @Inject annotation + * + * @param service The service to be injected + */ + public void setService(S service) { + this.service = service; + } + + /** + * {@inheritDoc} + */ + @Override + public T create(@RequestBody T resource) { + return (T) this.service.create(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public T update(@PathVariable ID id, @RequestBody T resource) { + Assert.notNull(id, "id cannot be null"); + + T retreivedResource = this.findById(id); + if (retreivedResource == null) { + throw new NotFoundException(); + } + + return (T) this.service.update(resource); + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findAll() { + return service.findAll(); + } + + /** + * {@inheritDoc} + */ + @Override + public Page findPaginated(@RequestParam(value = "page", required = false, defaultValue = "1") Integer page, + @RequestParam(value = "size", required = false, defaultValue = "10") Integer size, + @RequestParam(value = "direction", required = false, defaultValue = "") String direction, + @RequestParam(value = "properties", required = false) String properties) { + Assert.isTrue(page > 0, "Page index must be greater than 0"); + Assert.isTrue(direction.isEmpty() || direction.equalsIgnoreCase(Sort.Direction.ASC.toString()) || direction.equalsIgnoreCase(Sort.Direction.DESC.toString()), "Direction should be ASC or DESC"); + if (direction.isEmpty()) { + return this.service.findAll(new PageRequest(page - 1, size)); + } else { + Assert.notNull(properties); + return this.service.findAll(new PageRequest(page - 1, size, new Sort(Sort.Direction.fromString(direction.toUpperCase()), properties.split(",")))); + } + } + + /** + * {@inheritDoc} + */ + @Override + public T findById(@PathVariable ID id) { + T resource = (T) this.service.findById(id); + if (resource == null) { + throw new NotFoundException(); + } + + return resource; + } + + /** + * {@inheritDoc} + */ + @Override + public Iterable findByIds(@RequestParam(value = "ids[]") Set ids) { + Assert.notNull(ids, "ids list cannot be null"); + return this.service.findByIds(ids); + } + + + /** + * {@inheritDoc} + */ + @Override + public void delete() { + this.service.deleteAllWithCascade(); + } + + /** + * {@inheritDoc} + */ + @Override + public void delete(@PathVariable ID id) { + T resource = this.findById(id); + this.service.delete(resource); + } +} + -- 2.1.4