wtorek, 25 października 2016

Handling with LocalDate issues in Spring Boot.




If you have LocalDate field in your entities you can get errors like:


Servlet.service() for servlet [dispatcherServlet] in context with path [] 
threw exception [Request processing failed; nested exception is 
org.springframework.orm.jpa.JpaSystemException: could not deserialize; 
nested exception is org.hibernate.type.SerializationException: 
could not deserialize] with root cause

or:


Failed to read HTTP message: 
org.springframework.http.converter.HttpMessageNotReadableException: 
Could not read document: Can not construct instance of java.time.LocalDate: 
no suitable constructor found, can not deserialize from Object value 
(missing default constructor or creator, or perhaps need to add/enable type 
information?)


nested exception is com.fasterxml.jackson.databind.JsonMappingException: 
Can not construct instance of java.time.LocalDate: 
no suitable constructor found, can not deserialize from Object value 
(missing default constructor or creator, or perhaps need to add/enable 
type information?)



To solve that, insert in pom.xml:


                <dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-java8</artifactId>
  </dependency>
  <dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
   </dependency>


sobota, 22 października 2016

How to create simple REST app using Spring Boot, AngularJS and MariaDB in Eclipse. Part 3

How to delete all selected entities


In previous part we could delete each entity one by one. But sometimes it’s more convenient to select some or all entities and delete them at once.


In this case I added ‘Select All’ button above checkboxes and ‘Delete selected‘ button on the bottom.




To get it to work I needed to change index.html to:


<table class="jh-table table table-striped">
     <thead>
      <tr>
       <th><input type="checkbox" ng-model="isAllSelected"
        ng-click="selectAll()"></th>
       <th>ID</th>
       <th>Description</th>
       <th>Rate</th>
       <th>Active</th>
       <th></th>
      </tr>
     </thead>
     <tbody>
      <tr ng-repeat="vat in vats track by vat.id">
       <td><input type="checkbox" ng-model="vat.checked"
        ng-change="optionSelected()" /></td>
       <td>{{vat.id}}</td>
       <td>{{vat.description}}</td>
       <td>{{vat.rate}}</td>
       <td>{{vat.active}}</td>
       <td class="text-right">
        <div class="btn-group flex-btn-group-container">
         <button class="pull-right btn btn-danger" type="button"
          title="Delete" ng-click="deleteVat(vat)">
          <span class="glyphicon glyphicon-trash"></span>
         </button>
        </div>
       </td>
      </tr>
     </tbody>
    </table>



And add controllers in vat_controller.js:


 $scope.selectAll = function() {
  var toggleStatus = $scope.isAllSelected;
  angular.forEach($scope.vats, function(itm) {
   itm.checked = toggleStatus;
  });

 }

 $scope.optionSelected = function() {
  $scope.isAllSelected = $scope.vats
    .every(function(itm) {
     return itm.checked;
    })
 }
    


Delete buttons work on the left and at the bottom:


$scope.deleteVat = function(vat) {
  if (vat) { // pressed button on the right
   vat.$remove(function() {
    $scope.vats.splice($scope.vats
      .indexOf(vat), 1);
   });
  } else { // pressed button in the bottom
   angular.forEach($scope.vats, function(vat) {
    if (vat.checked) {
     vat.$remove(function() {
      $scope.vats.splice($scope.vats
        .indexOf(vat), 1);
     });
    }
   });
  }
 };

How to create simple REST app using Spring Boot, AngularJS and MariaDB in Eclipse. Part 2

In part 1 we created some basis structure for our app.
Here I’ll show how to display two lists of entities on one page in web browser.


We will need 6 files at the beginning. This allows only to add and remove entities.


/src/main/resources/static/index.html
/src/main/resources/static/webapp/app.js
/src/main/resources/static/webapp/entities/category/category_controller.js
/src/main/resources/static/webapp/entities/category/category_service.js
/src/main/resources/static/webapp/entities/vat/vat_controller.js
/src/main/resources/static/webapp/entities/vat/vat_service.js




/src/main/resources/static/index.html:


<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet"
 href="./bower_components/bootstrap-css-only/css/bootstrap.min.css" />
</head>
<body ng-app="myApp">
 <div class="container" ng-controller="AppVatController">
  <div class="page-header">
   <h1>Vat</h1>
  </div>
  <div class="alert alert-info" role="alert"
   ng-hide="vats && vats.length > 0">There are no vats yet.</div>

  <form class="form-horizontal" role="form"
   ng-submit="addVat(newVatDescription, newVatRate, newVatActive)">
   <div class="table-responsive">
    <table class="jh-table table table-striped">
     <thead>
      <tr>
       <th></th>
       <th>ID</th>
       <th>Description</th>
       <th>Rate</th>
       <th>Active</th>
       <th></th>
      </tr>
     </thead>
     <tbody>
      <tr ng-repeat="vat in vats track by vat.id">
       <td><input type="checkbox" ng-model="vat.checked"
        ng-change="updateVat(vat)" /></td>
       <td>{{vat.id}}</td>
       <td>{{vat.description}}</td>
       <td>{{vat.rate}}</td>
       <td>{{vat.active}}</td>
       <td class="text-right">
        <div class="btn-group flex-btn-group-container">
         <button class="pull-right btn btn-danger" type="button"
          title="Delete" ng-click="deleteVat(vat)">
          <span class="glyphicon glyphicon-trash"></span>
         </button>
        </div>
       </td>
      </tr>
     </tbody>
    </table>
   </div>
   <hr />
   <div class="input-group">
    <input type="text" class="form-control" ng-model="newVatDescription"
     placeholder="Enter vat description..." /> <input type="text"
     class="form-control" ng-model="newVatRate"
     placeholder="Enter vat rate [i.e 0.23]..." /> <input type="text"
     class="form-control" ng-model="newVatActive"
     placeholder="Is it active? [true or false]..." /> <span
     class="input-group-btn">
     <button class="btn btn-default" type="submit"
      ng-disabled="!newVatDescription" title="Add">
      <span class="glyphicon glyphicon-plus"></span>
     </button>
    </span>
   </div>
  </form>




  <hr />

  <div class="container" ng-controller="AppCategoryController">
   <div class="page-header">
    <h1>Categories</h1>
   </div>
   <form class="form-horizontal" role="form"
    ng-submit="addCategory(newCategoryName, newCategoryParentId)">
    <div class="alert alert-info" role="alert"
     ng-hide="categories && categories.length > 0">There are no
     categories yet.</div>
    <div class="table-responsive">
     <table class="jh-table table table-striped">
      <thead>
       <tr>
        <th></th>
        <th>ID</th>
        <th>Name</th>
        <th>Parent Id</th>
       </tr>
      </thead>
      <tbody>
       <tr ng-repeat="category in categories">
        <td><input type="checkbox" ng-model="category.checked"
         ng-change="updateCategory(category)" /></td>
        <td>{{category.id}}</td>
        <td>{{category.name}}</td>
        <td>{{category.parentId}}
         <button class="pull-right btn btn-danger" type="button"
          title="Delete" ng-click="deleteCategory(category)">
          <span class="glyphicon glyphicon-trash"></span>
         </button>
        </td>
       </tr>
      </tbody>
     </table>
    </div>

    <hr />
    <div class="input-group">
     <input type="text" class="form-control" ng-model="newCategoryName"
      placeholder="Enter category name..." /> <input type="text"
      class="form-control" ng-model="newCategoryParentId"
      placeholder="Enter category parentId..." /> <span
      class="input-group-btn">
      <button class="btn btn-default" type="submit"
       ng-disabled="!newCategoryName" title="Add">
       <span class="glyphicon glyphicon-plus"></span>
      </button>
     </span>
    </div>
   </form>
  </div>



  <script type="text/javascript"
   src="./bower_components/angular/angular.min.js"></script>
  <script type="text/javascript"
   src="./bower_components/angular-resource/angular-resource.min.js"></script>
  <script type="text/javascript"
   src="./bower_components/lodash/dist/lodash.min.js"></script>
  <script type="text/javascript" src="./webapp/app.js"></script>
  <script type="text/javascript"
   src="./webapp/entities/vat/vat_controller.js"></script>
  <script type="text/javascript"
   src="./webapp/entities/vat/vat_service.js"></script>
  <script type="text/javascript"
   src="./webapp/entities/category/category_controller.js"></script>
  <script type="text/javascript"
   src="./webapp/entities/category/category_service.js"></script>
</body>
</html>


/src/main/resources/static/webapp/app.js:


(function(angular) {
   angular.module("myApp.vat_controller", []);
   angular.module("myApp.vat_service", []);
   angular.module("myApp.category_controller", []);
   angular.module("myApp.category_service", []);
  angular.module("myApp", ["ngResource", "myApp.vat_controller", "myApp.vat_service", 
   "myApp.category_controller", "myApp.category_service"]);
}(angular));




/src/main/resources/static/webapp/entities/category/category_controller.js


(function(angular) {
  var AppCategoryController = function($scope, Category) {
   Category.query(function(response) {
      $scope.categories = response ? response : [];
    });
    
    $scope.addCategory = function(name, parentId) {
      new Category({
          name: name,
          parentId: parentId,
          checked: false
      }).$save(function(category) {
        $scope.categories.push(category);
      });
      $scope.newCategoryParentId = "";
      $scope.newCategoryName = "";
    };
    
//    $scope.updateCategory = function(category) {
//     category.$update();
//    };
//    
    $scope.deleteCategory = function(category) {
     category.$remove(function() {
        $scope.categories.splice($scope.categories.indexOf(category), 1);
      });
    };
  };
  
  AppCategoryController.$inject = ['$scope', 'Category'];
  angular.module("myApp.category_controller").controller("AppCategoryController", AppCategoryController);
}(angular));





/src/main/resources/static/webapp/entities/category/category_service.js:



(function(angular) {
  var CategoryFactory = function($resource) {
    return $resource('/category/:id', {
      id: '@id'
    }, {
      update: {
        method: "PUT"
      },
      remove: {
        method: "DELETE"
      }
    });
  };
  
  CategoryFactory.$inject = ['$resource'];
  angular.module("myApp.category_service").factory("Category", CategoryFactory);
}(angular));





/src/main/resources/static/webapp/entities/vat/vat_controller.js:


(function(angular) {
  var AppVatController = function($scope, Vat) {
    Vat.query(function(response) {
      $scope.vats = response ? response : [];
    });
    
    $scope.addVat = function(description, rate, active) {
      new Vat({
        description: description,
        rate: rate,
        active: active,
        checked: false
      }).$save(function(vat) {
        $scope.vats.push(vat);
      });
      $scope.newVatDescription = "";
      $scope.newVatRate = "";
      $scope.newVatActive = "";
    };
//    
//    $scope.updateVat = function(vat) {
//      vat.$update();
//    };
//    
    $scope.deleteVat = function(vat) {
      vat.$remove(function() {
        $scope.vats.splice($scope.vats.indexOf(vat), 1);
      });
    };
  };
  
  AppVatController.$inject = ['$scope', 'Vat'];
  angular.module("myApp.vat_controller").controller("AppVatController", AppVatController);
}(angular));





/src/main/resources/static/webapp/entities/vat/vat_service.js:


(function(angular) {
  var VatFactory = function($resource) {
    return $resource('/vats/:id', {
      id: '@id'
    }, {
      update: {
        method: "PUT"
      },
      remove: {
        method: "DELETE"
      }
    });
  };
  
  VatFactory.$inject = ['$resource'];
  angular.module("myApp.vat_service").factory("Vat", VatFactory);
}(angular));


Now it should look like this:



How to create simple REST app using Spring Boot, AngularJS and MariaDB in Eclipse. Part 1



In Eclipse, File - New - Spring Starter Project. Enter Name, next, check Web. Next, Finish.

Open pom.xml and add:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
   <groupId>org.mariadb.jdbc</groupId>
   <artifactId>mariadb-java-client</artifactId>
  </dependency>




Go to /src/main/resources/static/ create bower.json file:


{
  "name": "ng-spring-boot",
  "dependencies": {
    "angular": "~1.3.0",
    "angular-resource": "~1.3.0",
    "bootstrap-css-only": "~3.2.0"
  }
} 

Then, in this folder run:
$ bower install


It will create bower_components catalog with all necessary angular dependences.

Now create /src/main/resources/config/ folder with application.yml file:

# ===================================================================
# Spring Boot configuration.
#
# This configuration will be overriden by the Spring profile you use,
# for example application-dev.yml if you use the "dev" profile.
# ===================================================================

# ===================================================================
# Standard Spring Boot properties.
# Full reference is available at:
# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
# ===================================================================

spring:
    datasource:
        url: jdbc:mariadb://localhost:3306/barfitter
        name: barfitter
        username: barfitter
        password: barfitter
    jpa:
        database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
        database: MYSQL
        show-sql: true
        properties:
            hibernate.cache.use_second_level_cache: true
            hibernate.cache.use_query_cache: false
            hibernate.generate_statistics: true
    mvc:
        favicon:
            enabled: true


You must change your datasource credentials.


Now I create three entities in /src/main/java/finbarre/entities/:


package finbarre.entities;

import javax.persistence.Entity;

import java.math.BigDecimal;

import javax.persistence.*;
import javax.validation.constraints.*;
/**
 * A Vat.
 */
@Entity
@Table(name = "vat")
public class Vat  {


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Column(name = "description", nullable = false)
    private String description;

    @NotNull
    @Column(name = "rate", precision=10, scale=2, nullable = false)
    private BigDecimal rate;

    @Column(name = "active")
    private Boolean active;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public BigDecimal getRate() {
        return rate;
    }

    public void setRate(BigDecimal rate) {
        this.rate = rate;
    }

    public Boolean isActive() {
        return active;
    }

    public void setActive(Boolean active) {
        this.active = active;
    }



    @Override
    public String toString() {
        return "Vat{" +
            "id=" + id +
            ", description='" + description + "'" +
            ", rate='" + rate + "'" +
            ", active='" + active + "'" +
            '}';
    }
}


package finbarre.entities;

import javax.persistence.Entity;
import javax.persistence.*;
import javax.validation.constraints.*;

/**
 * A Category.
 */
@Entity
@Table(name = "category")
public class Category  {


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "parent_id")
    private Integer parentId;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getParentId() {
        return parentId;
    }

    public void setParentId(Integer parentId) {
        this.parentId = parentId;
    }


    @Override
    public String toString() {
        return "Category{" +
            "id=" + id +
            ", name='" + name + "'" +
            ", parentId='" + parentId + "'" +
            '}';
    }
}


package finbarre.entities;

import javax.persistence.Entity;

import java.math.BigDecimal;

import javax.persistence.*;
import javax.validation.constraints.*;



/**
 * A Product.
 */
@Entity
@Table(name = "product")
public class Product {


    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "purch_price_net", precision=10, scale=2)
    private BigDecimal purchPriceNet;

    @NotNull
    @Column(name = "sell_price_gross", precision=10, scale=2, nullable = false)
    private BigDecimal sellPriceGross;

    @Column(name = "active")
    private Boolean active;

    @Column(name = "self_made")
    private Boolean selfMade;

    @Column(name = "purch_price_gross", precision=10, scale=2)
    private BigDecimal purchPriceGross;

    @Column(name = "purch_vat_value", precision=10, scale=2)
    private BigDecimal purchVatValue;

    @Column(name = "sell_price_net", precision=10, scale=2)
    private BigDecimal sellPriceNet;

    @Column(name = "sell_vat_value", precision=10, scale=2)
    private BigDecimal sellVatValue;

    @ManyToOne
    private Vat productPurchPriceRate;

    @ManyToOne
    private Vat productSellPriceRate;

    @ManyToOne
    private Category category;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public BigDecimal getPurchPriceNet() {
        return purchPriceNet;
    }

    public void setPurchPriceNet(BigDecimal purchPriceNet) {
        this.purchPriceNet = purchPriceNet;
    }

    public BigDecimal getSellPriceGross() {
        return sellPriceGross;
    }

    public void setSellPriceGross(BigDecimal sellPriceGross) {
        this.sellPriceGross = sellPriceGross;
    }

    public Boolean isActive() {
        return active;
    }

    public void setActive(Boolean active) {
        this.active = active;
    }

    public Boolean isSelfMade() {
        return selfMade;
    }

    public void setSelfMade(Boolean selfMade) {
        this.selfMade = selfMade;
    }

    public BigDecimal getPurchPriceGross() {
        return purchPriceGross;
    }

    public void setPurchPriceGross(BigDecimal purchPriceGross) {
        this.purchPriceGross = purchPriceGross;
    }

    public BigDecimal getPurchVatValue() {
        return purchVatValue;
    }

    public void setPurchVatValue(BigDecimal purchVatValue) {
        this.purchVatValue = purchVatValue;
    }

    public BigDecimal getSellPriceNet() {
        return sellPriceNet;
    }

    public void setSellPriceNet(BigDecimal sellPriceNet) {
        this.sellPriceNet = sellPriceNet;
    }

    public BigDecimal getSellVatValue() {
        return sellVatValue;
    }

    public void setSellVatValue(BigDecimal sellVatValue) {
        this.sellVatValue = sellVatValue;
    }

    public Vat getProductPurchPriceRate() {
        return productPurchPriceRate;
    }

    public void setProductPurchPriceRate(Vat vat) {
        this.productPurchPriceRate = vat;
    }

    public Vat getProductSellPriceRate() {
        return productSellPriceRate;
    }

    public void setProductSellPriceRate(Vat vat) {
        this.productSellPriceRate = vat;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }



    @Override
    public String toString() {
        return "Product{" +
            "id=" + id +
            ", name='" + name + "'" +
            ", purchPriceNet='" + purchPriceNet + "'" +
            ", sellPriceGross='" + sellPriceGross + "'" +
            ", active='" + active + "'" +
            ", selfMade='" + selfMade + "'" +
            ", purchPriceGross='" + purchPriceGross + "'" +
            ", purchVatValue='" + purchVatValue + "'" +
            ", sellPriceNet='" + sellPriceNet + "'" +
            ", sellVatValue='" + sellVatValue + "'" +
            '}';
    }
}





And in /src/main/java/finbarre/repository/ we need three repositories:


package finbarre.repository;

import finbarre.entities.Vat;

import org.springframework.data.jpa.repository.*;

import java.util.List;

/**
 * Spring Data JPA repository for the Vat entity.
 */
@SuppressWarnings("unused")
public interface VatRepository extends JpaRepository<Vat,Long> {

}


package finbarre.repository;

import finbarre.entities.Category;

import org.springframework.data.jpa.repository.*;

import java.util.List;

/**
 * Spring Data JPA repository for the Category entity.
 */
@SuppressWarnings("unused")
public interface CategoryRepository extends JpaRepository<Category,Long> {

}


package finbarre.repository;

import finbarre.entities.Product;

import org.springframework.data.jpa.repository.*;

import java.util.List;

/**
 * Spring Data JPA repository for the Product entity.
 */
@SuppressWarnings("unused")
public interface ProductRepository extends JpaRepository<Product,Long> {

}


Note: we don’t need to declare new folders. We can change filenames and folders where are they placed only changing their location in headings like packages or imports.


Now we create some simple CRUD controllers for these entities:


package finbarre.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import finbarre.entities.Vat;
import finbarre.repository.VatRepository;

@RestController
@RequestMapping("/vat")
public class VatController {
  @Autowired
   private VatRepository repo;
   
   @RequestMapping(method = RequestMethod.GET)
   public List<Vat> findItems() {
     return repo.findAll();
   }
   
   @RequestMapping(method = RequestMethod.POST)
   public Vat addVat(@RequestBody Vat vat) {
    vat.setId(null);
     return repo.saveAndFlush(vat);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
   public Vat updatedVat(@RequestBody Vat updatedVat, @PathVariable Long id) {
    updatedVat.setId(id);
     return repo.saveAndFlush(updatedVat);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
   public void deleteVat(@PathVariable Long id) {
     repo.delete(id);
   }
}


package finbarre.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import finbarre.entities.Category;
import finbarre.repository.CategoryRepository;

@RestController
@RequestMapping("/category")
public class CategoryController {
  @Autowired
   private CategoryRepository repo;
   
   @RequestMapping(method = RequestMethod.GET)
   public List<Category> findItems() {
     return repo.findAll();
   }
   
   @RequestMapping(method = RequestMethod.POST)
   public Category addCategory(@RequestBody Category category) {
    category.setId(null);
     return repo.saveAndFlush(category);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
   public Category updatedCategory(@RequestBody Category updatedCategory, @PathVariable Long id) {
    updatedCategory.setId(id);
     return repo.saveAndFlush(updatedCategory);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
   public void deleteCategory(@PathVariable Long id) {
     repo.delete(id);
   }
}


package finbarre.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import finbarre.entities.Product;
import finbarre.repository.ProductRepository;

@RestController
@RequestMapping("/product")
public class ProductController {
  @Autowired
   private ProductRepository repo;
   
   @RequestMapping(method = RequestMethod.GET)
   public List<Product> findItems() {
     return repo.findAll();
   }
   
   @RequestMapping(method = RequestMethod.POST)
   public Product addProduct(@RequestBody Product product) {
    product.setId(null);
     return repo.saveAndFlush(product);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
   public Product updatedProduct(@RequestBody Product updatedProduct, @PathVariable Long id) {
    updatedProduct.setId(id);
     return repo.saveAndFlush(updatedProduct);
   }
   
   @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
   public void deleteProduct(@PathVariable Long id) {
     repo.delete(id);
   }
}





Now you can in i.e Advanced REST Client [Chrome extension] enter http://localhost:8080/product and see products from database.