Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
57 views

Java Coffee

Uploaded by

sonam
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
57 views

Java Coffee

Uploaded by

sonam
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 12

Hi folks,

#Letest #update #Real_Time


#Spring Boot with #RESTful APIs, #JPA, #MySQL, #Lombok, #FreeMarker and #VueJS
---#This tutorial will walk you through the steps of building a full-stack CRUD web
app and RESTful APIs web services example with Spring Boot, Lombok, JPA and
Hibernate, MySQL, FreeMarker, VueJS and Axios
#Init project structure
You can create and init a new Spring Boot project by using Spring CLI or Spring
Initializr. Learn more about using these tools here

The final project structure as below

├── src
│ └── main
│ ├── java
│ │ └── com
│ │ └── hellokoding
│ │ └── springboot
│ │ └── restful
│ │ ├── product
│ │ │ ├── Product.java
│ │ │ ├── ProductAPI.java
│ │ │ ├── ProductController.java
│ │ │ ├── ProductRespository.java
│ │ │ └── ProductService.java
│ │ └── Application.java
│ └── resources
│ ├── static
│ │ ├── products.css
│ │ └── products.js
│ ├── templates
│ │ └── products.html
│ └── application.properties
├── Dockerfile
├── docker-compose.yml
└── pom.xml

#Project dependencies
pom.xml

<?xml version="1.0" encoding="UTF-8"?>


<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hellokoding.springboot</groupId>
<artifactId>crud-mysql-vuejs</artifactId>
<version>1.0-SNAPSHOT</version>

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

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

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

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

<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

#Create JPA Entity


Product.java

package com.hellokoding.springboot.restful.product;

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.math.BigDecimal;
import java.util.Date;

@Entity

@Data
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private String description;

private BigDecimal price;

@CreationTimestamp
private Date createdAt;

@UpdateTimestamp
private Date updatedAt;
}
@Data is a Lombok annotation which generates field getters and setters, toString,
equals and hashCode methods for you at compile time

@Entity is a JPA annotation which specifies the class as an entity (so the class
name can be used in JPQL queries) and as a table in the database (the @Entity class
name will match with the underlying table name if the @Table annotation is omitted)

Create Spring Data JPA Repository


ProductRespository.java

package com.hellokoding.springboot.restful.product;

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

public interface ProductRespository extends JpaRepository<Product, Long> {


}
Implement Service
ProductService.java

package com.hellokoding.springboot.restful.product;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service

@RequiredArgsConstructor
public class ProductService {
private final ProductRespository productRespository;

public List<Product> findAll() {


return productRespository.findAll();
}

public Optional<Product> findById(Long id) {


return productRespository.findById(id);
}

public Product save(Product stock) {


return productRespository.save(stock);
}

public void deleteById(Long id) {


productRespository.deleteById(id);
}
}
RequiredArgsConstructor is a Lombok annotation which generates a constructor with
required fields (final fields and @NonNull fields). For the above ProductService
class, Lombok will generate

@Service
public class ProductService {
private final ProductRespository productRespository;

public ProductService(ProductRespository productRespository) {


this.productRespository = productRespository;
}

...
}
For classes which only have single constructor, since Spring 4.3, you no longer
need to specify an explicit injection annotation such as @Autowired, Spring does
that for you

If your editor has not been installed Lombok plugin, you may get a highlighted
error on the productRespository field. Either compiling the project or installing
the plugin will resolve the problem

#Create REST APIs


ProductAPI.java

package com.hellokoding.springboot.restful.product;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.Optional;

@RestController
@RequestMapping("/api/v1/products")
@Slf4j
@RequiredArgsConstructor
public class ProductAPI {
private final ProductService productService;

@GetMapping
public ResponseEntity<List<Product>> findAll() {
return ResponseEntity.ok(productService.findAll());
}

@PostMapping
public ResponseEntity create(@Valid @RequestBody Product product) {
return ResponseEntity.ok(productService.save(product));
}

@GetMapping("/{id}")
public ResponseEntity<Product> findById(@PathVariable Long id) {
Optional<Product> stock = productService.findById(id);
if (!stock.isPresent()) {
log.error("Id " + id + " is not existed");
ResponseEntity.badRequest().build();
}

return ResponseEntity.ok(stock.get());
}

@PutMapping("/{id}")
public ResponseEntity<Product> update(@PathVariable Long id, @Valid @RequestBody
Product product) {
if (!productService.findById(id).isPresent()) {
log.error("Id " + id + " is not existed");
ResponseEntity.badRequest().build();
}

return ResponseEntity.ok(productService.save(product));
}

@DeleteMapping("/{id}")
public ResponseEntity delete(@PathVariable Long id) {
if (!productService.findById(id).isPresent()) {
log.error("Id " + id + " is not existed");
ResponseEntity.badRequest().build();
}

productService.deleteById(id);

return ResponseEntity.ok().build();
}
}

#Create Web Controller


ProductController.java

package com.hellokoding.springboot.restful.product;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ProductController {
@GetMapping("/")
public String list(){
return "products";
}
}
#Create FreeMarker View Template
products.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-
fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Full stack CRUD Example with Spring Boot, MySQL and VueJS</title>
<link href="https://unpkg.com/bootstrap@3.4.0/dist/css/bootstrap.min.css"
rel="stylesheet">
<link href="/products.css" rel="stylesheet"/>
</head>
<body>
<div class="container">
<h1>Product CRUD</h1>
<main id="app">
<router-view></router-view>
</main>
</div>

<template id="product">
<div>
<h2>{{ product.name }}</h2>
<b>Description: </b>
<div>{{ product.description }}</div>
<b>Price:</b>
<div>{{ product.price }}<span class="glyphicon glyphicon-euro"></span></div>
<br/>
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span>
<a>
<router-link to="/">Back to product list</router-link>
</a>
</div>
</template>

<template id="product-list">
<div>
<div class="actions">
<a class="btn btn-default">
<router-link :to="{path: '/add-product'}">
<span class="glyphicon glyphicon-plus"></span>
Add product
</router-link>
</a>
</div>
<div class="filters row">
<div class="form-group col-sm-3">
<input placeholder="Search" v-model="searchKey" class="form-control" id="search-
element" requred/>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Price</th>
<th class="col-sm-2">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="product in filteredProducts">
<!-- tr v-for="product in products" -->
<!-- tr v-for="product in products | filterBy searchKey in 'name'" -->
<td>
<a>
<router-link :to="{name: 'product', params: {product_id:
product.id}}">{{ product.name }}</router-link>
</a>
</td>
<td>{{ product.description }}</td>
<td>
{{ product.price }}
<span class="glyphicon glyphicon-euro" aria-hidden="true"></span>
</td>
<td>
<a class="btn btn-warning btn-xs">
<router-link :to="{name: 'product-edit', params: {product_id:
product.id}}">Edit</router-link>
</a>
<a class="btn btn-danger btn-xs">
<router-link :to="{name: 'product-delete', params: {product_id:
product.id}}">Delete</router-link>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<template id="add-product">
<div>
<h2>Add new product</h2>
<form @submit="createProduct">
<div class="form-group">
<label for="add-name">Name</label>
<input class="form-control" id="add-name" v-model="product.name" required/>
</div>
<div class="form-group">
<label for="add-description">Description</label>
<textarea class="form-control" id="add-description" rows="10" v-
model="product.description"></textarea>
</div>
<div class="form-group">
<label for="add-price">Price, <span class="glyphicon glyphicon-
euro"></span></label>
<input type="number" class="form-control" id="add-price" v-model="product.price"/>
</div>
<button type="submit" class="btn btn-primary">Create</button>
<a class="btn btn-default">
<router-link to="/">Cancel</router-link>
</a>
</form>
</div>
</template>

<template id="product-edit">
<div>
<h2>Edit product</h2>
<form @submit="updateProduct">
<div class="form-group">
<label for="edit-name">Name</label>
<input class="form-control" id="edit-name" v-model="product.name" required/>
</div>
<div class="form-group">
<label for="edit-description">Description</label>
<textarea class="form-control" id="edit-description" rows="3" v-
model="product.description"></textarea>
</div>
<div class="form-group">
<label for="edit-price">Price, <span class="glyphicon glyphicon-
euro"></span></label>
<input type="number" class="form-control" id="edit-price" v-model="product.price"/>
</div>
<button type="submit" class="btn btn-primary">Save</button>
<a class="btn btn-default">
<router-link to="/">Cancel</router-link>
</a>
</form>
</div>
</template>

<template id="product-delete">
<div>
<h2>Delete product {{ product.name }}</h2>
<form @submit="deleteProduct">
<p>The action cannot be undone.</p>
<button type="submit" class="btn btn-danger">Delete</button>
<a class="btn btn-default">
<router-link to="/">Cancel</router-link>
</a>
</form>
</div>
</template>

<script src="https://unpkg.com/vue@2.5.22/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router@3.0.2/dist/vue-router.js"></script>
<script src="https://unpkg.com/axios@0.18.0/dist/axios.min.js"></script>
<script src="/products.js"></script>

</body>
</html>

#Static Files
products.js

var products = [];

function findProduct (productId) {


return products[findProductKey(productId)];
}
function findProductKey (productId) {
for (var key = 0; key < products.length; key++) {
if (products[key].id == productId) {
return key;
}
}
}

var productService = {
findAll(fn) {
axios
.get('/api/v1/products')
.then(response => fn(response))
.catch(error => console.log(error))
},

findById(id, fn) {
axios
.get('/api/v1/products/' + id)
.then(response => fn(response))
.catch(error => console.log(error))
},

create(product, fn) {
axios
.post('/api/v1/products', product)
.then(response => fn(response))
.catch(error => console.log(error))
},

update(id, product, fn) {


axios
.put('/api/v1/products/' + id, product)
.then(response => fn(response))
.catch(error => console.log(error))
},

deleteProduct(id, fn) {
axios
.delete('/api/v1/products/' + id)
.then(response => fn(response))
.catch(error => console.log(error))
}
}

var List = Vue.extend({


template: '#product-list',
data: function () {
return {products: [], searchKey: ''};
},
computed: {
filteredProducts() {
return this.products.filter((product) => {
return product.name.indexOf(this.searchKey) > -1
|| product.description.indexOf(this.searchKey) > -1
|| product.price.toString().indexOf(this.searchKey) > -1
})
}
},
mounted() {
productService.findAll(r => {this.products = r.data; products = r.data})
}
});

var Product = Vue.extend({


template: '#product',
data: function () {
return {product: findProduct(this.$route.params.product_id)};
}
});

var ProductEdit = Vue.extend({


template: '#product-edit',
data: function () {
return {product: findProduct(this.$route.params.product_id)};
},
methods: {
updateProduct: function () {
productService.update(this.product.id, this.product, r => router.push('/'))
}
}
});

var ProductDelete = Vue.extend({


template: '#product-delete',
data: function () {
return {product: findProduct(this.$route.params.product_id)};
},
methods: {
deleteProduct: function () {
productService.deleteProduct(this.product.id, r => router.push('/'))
}
}
});

var AddProduct = Vue.extend({


template: '#add-product',
data() {
return {
product: {name: '', description: '', price: 0}
}
},
methods: {
createProduct() {
productService.create(this.product, r => router.push('/'))
}
}
});

var router = new VueRouter({


routes: [
{path: '/', component: List},
{path: '/product/:product_id', component: Product, name: 'product'},
{path: '/add-product', component: AddProduct},
{path: '/product/:product_id/edit', component: ProductEdit, name: 'product-edit'},
{path: '/product/:product_id/delete', component: ProductDelete, name: 'product-
delete'}
]
});

new Vue({
router
}).$mount('#app')

#products.css

.actions {
margin-bottom: 20px;
margin-top: 20px;
}

#Application Configurations
Application.java

package com.hellokoding.springboot.restful;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
application.properties

spring.datasource.url=jdbc:mysql://hk-mysql:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=hellokoding
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.hibernate.ddl-auto=create
spring.jpa.database-platform=org.hibernate.dialect.MySQL57Dialect
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true

spring.freemarker.suffix=.html
hk-mysql refers to Docker Compose service defined in the below docker-compose.yml
file

spring.jpa.hibernate.ddl-auto=create allows JPA/Hibernate auto create database and


table schema for you

On production environment, you may like to disable the DDL Auto feature by using
spring.jpa.hibernate.ddl-auto=validate or spring.jpa.hibernate.ddl-auto=none
(default). Check out this example as one of the approaches
Flyway Example of Database Migration/Evolution with Spring Boot, JPA and Hibernate

#Run with Docker


Prepare Dockerfile for Java/Spring Boot application and docker-compose.yml for
MySQL Server

#Dockerfile

FROM maven:3.5-jdk-8
docker-compose.yml
version: '3'
services:
hk-mysql:
container_name: hk-mysql
image: mysql/mysql-server:5.7
environment:
MYSQL_DATABASE: test
MYSQL_ROOT_PASSWORD: hellokoding
MYSQL_ROOT_HOST: '%'
ports:
- "3306:3306"
restart: always

crud-mysql-vuejs:
build: .
volumes:
- .:/app
- ~/.m2:/root/.m2
working_dir: /app
ports:
- 8080:8080
command: mvn clean spring-boot:run
depends_on:
- hk-mysql
Type the below command at the project root directory, make sure your local Docker
is running

#docker-compose up
Run with your local MySQL Server
You can run the app with your local MySQL Server by updating hk-mysql on
application.properties to localhost and type the below command at the project root
directory

mvn clean spring-boot:run


Test the app
Access to localhost:8080 and start playing around with the app

You might also like