JPA improvements
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 3 Jul 2016 12:37:54 +0000 (14:37 +0200)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Sun, 3 Jul 2016 12:37:54 +0000 (14:37 +0200)
SpringJava/JPA/src/main/java/de/spring/persistence/example/domain/Ad.java
SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdDescriptionRepository.java
SpringJava/JPA/src/main/java/de/spring/persistence/example/repository/AdRepository.java
SpringJava/JPA/src/main/java/org/resthub/common/service/CrudService.java [new file with mode: 0644]
SpringJava/JPA/src/main/java/org/resthub/web/controller/ServiceBasedRestController.java [new file with mode: 0644]

index 6784538..be28d06 100644 (file)
@@ -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
index 120b2c8..d59ade2 100644 (file)
@@ -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<AdDescription, Long> {
 
        // Custom Query method
-       Page<AdDescription> findByAd(Ad ad);
+       Page<AdDescription> findByAd(Ad ad, Pageable pageable);
 }
index b625fed..24b9c47 100644 (file)
@@ -10,11 +10,11 @@ public interface AdRepository extends PagingAndSortingRepository<Ad, Long> {
        
        // 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 (file)
index 0000000..a9f4332
--- /dev/null
@@ -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 <T>  Your resource POJO to manage, maybe an entity or DTO class
+ * @param <ID> Resource id type, usually Long or String
+ */
+public interface CrudService<T, ID extends Serializable> {
+
+    /**
+     * 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<T> findByIds(Set<ID> ids);
+
+    /**
+     * Find all resources.
+     *
+     * @return a list of all resources.
+     */
+    Iterable<T> findAll();
+
+    /**
+     * Find all resources (pageable).
+     *
+     * @param pageRequest page request
+     * @return resources
+     */
+    Page<T> 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 (file)
index 0000000..cd93416
--- /dev/null
@@ -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
+ * <p/>
+ * <p>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</p>
+ * <p/>
+ * <p>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 :
+ * <p/>
+ * <pre>
+ * <code>
+ * {@literal @}Override
+ * public Sample findById({@literal @}PathVariable String id) {
+ * Sample sample = this.service.findByName(id);
+ * if (sample == null) {
+ * throw new NotFoundException();
+ * }
+ * return sample;
+ * }
+ * </code>
+ * </pre>
+ *
+ * @param <T>  Your resource class to manage, maybe an entity or DTO class
+ * @param <ID> Resource id type, usually Long or String
+ * @param <S>  The service class
+ * @see RepositoryBasedRestController
+ */
+public abstract class ServiceBasedRestController<T, ID extends Serializable, S extends CrudService> implements
+        RestController<T, ID> {
+
+    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<T> findAll() {
+        return service.findAll();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Page<T> 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<T> findByIds(@RequestParam(value = "ids[]") Set<ID> 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);
+    }
+}
+