Spring Boot + Vue.js 例子| Spring Data JPA + REST + MySQL 增刪改查

圖片.png

在本教程中,我們將向您展示Vue.js Http Client和Spring Boot Server示例,該示例使用Spring JPA將MySQL,Vue.js作為前端技術(shù)進(jìn)行CRUD發(fā)出請(qǐng)求和接收響應(yīng)。

用到的技術(shù):

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.4.RELEASE
– Spring Boot: 2.0.5.RELEASE

– Vue 2.5.17
– Vue Router 3
– Axios 0.18.0

1. Spring Boot Server

圖片.png

2. Vue.js Client

圖片.png

開始實(shí)戰(zhàn)

1. Spring Boot Server

圖片.png

– Customer 實(shí)體類及customer表
– CustomerRepository 一個(gè)繼承于CrudRepository的接口(相當(dāng)于以前的DAO),他在CustomerController 會(huì)自動(dòng)被注入進(jìn)來。你可以在該接口中實(shí)現(xiàn)自己操作數(shù)據(jù)庫的方法咖驮。
– CustomerController 是一個(gè)REST Controller,里面有各個(gè)請(qǐng)求端點(diǎn)的映射训枢。例如getAllCustomers托修,
postCustomer,deleteCustomer恒界,findByAge睦刃,updateCustomer方法。
– Configuration Spring Datasource 和 Spring JPA properties 的配置在application.properties中
– Dependencies Spring Boot 和MySQL的相關(guān)依賴包在 pom.xml配置

1.1 Dependency-依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
 
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

1.2 數(shù)據(jù)模型

model/Customer.java

package com.grokonez.spring.restapi.mysql.model;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name = "customer")
public class Customer {
 
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
 
  @Column(name = "name")
  private String name;
 
  @Column(name = "age")
  private int age;
 
  @Column(name = "active")
  private boolean active;
 
  public Customer() {
  }
 
  public Customer(String name, int age) {
    this.name = name;
    this.age = age;
    this.active = false;
  }
 
  public long getId() {
    return id;
  }
 
  public void setName(String name) {
    this.name = name;
  }
 
  public String getName() {
    return this.name;
  }
 
  public void setAge(int age) {
    this.age = age;
  }
 
  public int getAge() {
    return this.age;
  }
 
  public boolean isActive() {
    return active;
  }
 
  public void setActive(boolean active) {
    this.active = active;
  }
 
  @Override
  public String toString() {
    return "Customer [id=" + id + ", name=" + name + ", age=" + age + ", active=" + active + "]";
  }
}

1.3 JPA Repository

repo/CustomerRepository.java

package com.grokonez.spring.restapi.mysql.repo;
 
import java.util.List;
 
import org.springframework.data.repository.CrudRepository;
 
import com.grokonez.spring.restapi.mysql.model.Customer;
 
public interface CustomerRepository extends CrudRepository<Customer, Long> {
  List<Customer> findByAge(int age);
}

1.4 REST Controller

controller/CustomerController.java

package com.grokonez.spring.restapi.mysql.controller;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import com.grokonez.spring.restapi.mysql.model.Customer;
import com.grokonez.spring.restapi.mysql.repo.CustomerRepository;
 
@CrossOrigin(origins = "http://localhost:4200")
@RestController
@RequestMapping("/api")
public class CustomerController {
 
  @Autowired
  CustomerRepository repository;
 
  @GetMapping("/customers")
  public List<Customer> getAllCustomers() {
    System.out.println("Get all Customers...");
 
    List<Customer> customers = new ArrayList<>();
    repository.findAll().forEach(customers::add);
 
    return customers;
  }
 
  @PostMapping("/customer")
  public Customer postCustomer(@RequestBody Customer customer) {
 
    Customer _customer = repository.save(new Customer(customer.getName(), customer.getAge()));
    return _customer;
  }
 
  @DeleteMapping("/customer/{id}")
  public ResponseEntity<String> deleteCustomer(@PathVariable("id") long id) {
    System.out.println("Delete Customer with ID = " + id + "...");
 
    repository.deleteById(id);
 
    return new ResponseEntity<>("Customer has been deleted!", HttpStatus.OK);
  }
 
  @GetMapping("customers/age/{age}")
  public List<Customer> findByAge(@PathVariable int age) {
 
    List<Customer> customers = repository.findByAge(age);
    return customers;
  }
 
  @PutMapping("/customer/{id}")
  public ResponseEntity<Customer> updateCustomer(@PathVariable("id") long id, @RequestBody Customer customer) {
    System.out.println("Update Customer with ID = " + id + "...");
 
    Optional<Customer> customerData = repository.findById(id);
 
    if (customerData.isPresent()) {
      Customer _customer = customerData.get();
      _customer.setName(customer.getName());
      _customer.setAge(customer.getAge());
      _customer.setActive(customer.isActive());
      return new ResponseEntity<>(repository.save(_customer), HttpStatus.OK);
    } else {
      return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
  }
}

1.5 CSpring Datasource & JPA 相關(guān)配置信息

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/testdb?useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.generate-ddl=true

2. Vue.js Client

圖片.png

– package.json 他主要有三個(gè)模塊: vue, vue-router, axios.
– 4個(gè)components(組件): CustomersList, Customer, AddCustomer, SearchCustomer.
– router.js 定義routes, 及每個(gè)路由映射到對(duì)應(yīng)的組件
– http-common.js 使用baseUrl和axios HTTP方法的頭來初始化HTTP Client仗处。
– vue.config.js 配置Vue App的端口信息.

有關(guān)如何在此示例中使用Vue Router的更多詳細(xì)信息,請(qǐng)?jiān)L問:
Vue Router示例 - 使用導(dǎo)航欄枣宫,動(dòng)態(tài)路由和嵌套路由

給項(xiàng)目添加Vue Router

– 執(zhí)行命令: npm install vue-router.
– Import router 到 src/main.js:

import Vue from "vue";
import App from "./App.vue";
import router from './router'
 
Vue.config.productionTip = false;
new Vue({
  router, // inject the router to make whole app router-aware
  render: h => h(App)
}).$mount("#app");
定義路由

src/router.js:

import Vue from "vue";
import Router from "vue-router";
import CustomersList from "./components/CustomersList.vue";
import AddCustomer from "./components/AddCustomer.vue";
import SearchCustomers from "./components/SearchCustomers.vue";
import Customer from "./components/Customer.vue";
 
Vue.use(Router);
 
export default new Router({
  mode: "history",
  routes: [
    {
      path: "/",
      name: "customers",
      alias: "/customer",
      component: CustomersList,
      children: [
        {
          path: "/customer/:id",
          name: "customer-details",
          component: Customer,
          props: true
        }
      ]
    },
    {
      path: "/add",
      name: "add",
      component: AddCustomer
    },
    {
      path: "/search",
      name: "search",
      component: SearchCustomers
    }
  ]
});
在App template 添加Navbar and router-view

src/App.vue:

<template>
    <div id="app" class="container-fluid">
        <div class="site-info">
            <h1>grokonez</h1>
            <h3>Vue SpringBoot example</h3>
        </div>
        <nav>
            <router-link class="btn btn-primary" to="/">Customers</router-link>
            <router-link class="btn btn-primary" to="/add">Add</router-link>
            <router-link class="btn btn-primary" to="/search">Search</router-link>
        </nav>
        <br/>
        <router-view/>
    </div>
</template>
 
<script>
export default {
  name: "app"
};
</script>
 
<style>
.site-info {
  color: blue;
  margin-bottom: 20px;
}
 
.btn-primary {
  margin-right: 5px;
}
 
.container-fluid {
  text-align: center;
}
</style>

2.1 初始化HTTP Client

axios 安裝命令: npm install axios.
這時(shí)創(chuàng)建 http-common.js 文件:

import axios from "axios";
export default axios.create({
  baseURL: "http://localhost:8080/api",
  headers: {
    "Content-type": "application/json",
  }
});

2.2 Components

列表組件
components/CustomersList.vue

<template>
    <div class="list row">
        <div class="col-md-6">
            <h4>Customers List</h4>
            <ul>
                <li v-for="(customer, index) in customers" :key="index">
                    <router-link :to="{
                            name: 'customer-details',
                            params: { customer: customer, id: customer.id }
                        }">
                            {{customer.name}}
                    </router-link>
                </li>
            </ul>
        </div>
        <div class="col-md-6">
            <router-view @refreshData="refreshList"></router-view>
        </div>
    </div>
</template>
 
<script>
import http from "../http-common";
 
export default {
  name: "customers-list",
  data() {
    return {
      customers: []
    };
  },
  methods: {
    /* eslint-disable no-console */
    retrieveCustomers() {
      http
        .get("/customers")
        .then(response => {
          this.customers = response.data; // JSON are parsed automatically.
          console.log(response.data);
        })
        .catch(e => {
          console.log(e);
        });
    },
    refreshList() {
      this.retrieveCustomers();
    }
    /* eslint-enable no-console */
  },
  mounted() {
    this.retrieveCustomers();
  }
};
</script>
 
<style>
.list {
  text-align: left;
  max-width: 450px;
  margin: auto;
}
</style>
單個(gè)詳情查看

components/Customer.vue

<template>
  <div v-if="this.customer">
    <h4>Customer</h4>
    <div>
      <label>Name: </label> {{this.customer.name}}
    </div>
    <div>
      <label>Age: </label> {{this.customer.age}}
    </div>
    <div>
      <label>Active: </label> {{this.customer.active}}
    </div>
  
    <span v-if="this.customer.active"
      v-on:click="updateActive(false)"
      class="button is-small btn-primary">Inactive</span>
    <span v-else
      v-on:click="updateActive(true)"
      class="button is-small btn-primary">Active</span>
  
    <span class="button is-small btn-danger" v-on:click="deleteCustomer()">Delete</span>
  </div>
  <div v-else>
    <br/>
    <p>Please click on a Customer...</p>
  </div>
</template>
 
<script>
import http from "../http-common";
 
export default {
  name: "customer",
  props: ["customer"],
  methods: {
    /* eslint-disable no-console */
    updateActive(status) {
      var data = {
        id: this.customer.id,
        name: this.customer.name,
        age: this.customer.age,
        active: status
      };
 
      http
        .put("/customer/" + this.customer.id, data)
        .then(response => {
          this.customer.active = response.data.active;
          console.log(response.data);
        })
        .catch(e => {
          console.log(e);
        });
    },
    deleteCustomer() {
      http
        .delete("/customer/" + this.customer.id)
        .then(response => {
          console.log(response.data);
          this.$emit("refreshData");
          this.$router.push('/');
        })
        .catch(e => {
          console.log(e);
        });
    }
    /* eslint-enable no-console */
  }
};
</script>

添加

components/AddCustomer.vue

<template>
  <div class="submitform">
    <div v-if="!submitted">
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" id="name" required v-model="customer.name" name="name">
        </div>
    
        <div class="form-group">
          <label for="age">Age</label>
          <input type="number" class="form-control" id="age" required v-model="customer.age" name="age">
        </div>
    
        <button v-on:click="saveCustomer" class="btn btn-success">Submit</button>
    </div>
    
    <div v-else>
      <h4>You submitted successfully!</h4>
      <button class="btn btn-success" v-on:click="newCustomer">Add</button>
    </div>
  </div>
</template>
 
<script>
import http from "../http-common";
 
export default {
  name: "add-customer",
  data() {
    return {
      customer: {
        id: 0,
        name: "",
        age: 0,
        active: false
      },
      submitted: false
    };
  },
  methods: {
    /* eslint-disable no-console */
    saveCustomer() {
      var data = {
        name: this.customer.name,
        age: this.customer.age
      };
 
      http
        .post("/customer", data)
        .then(response => {
          this.customer.id = response.data.id;
          console.log(response.data);
        })
        .catch(e => {
          console.log(e);
        });
 
      this.submitted = true;
    },
    newCustomer() {
      this.submitted = false;
      this.customer = {};
    }
    /* eslint-enable no-console */
  }
};
</script>
 
<style>
.submitform {
  max-width: 300px;
  margin: auto;
}
</style>

查詢操作

components/SearchCustomers.vue

    
<template>
  <div class="searchform">
    <h4>Find by Age</h4>
    <div class="form-group">
      <input type="number" class="form-control" id="age" required v-model="age" name="age">
    </div>
 
    <div class="btn-group">
      <button v-on:click="searchCustomers" class="btn btn-success">Search</button>
    </div>
 
    <ul class="search-result">
      <li v-for="(customer, index) in customers" :key="index">
        <h6>{{customer.name}} ({{customer.age}})</h6>
      </li>
    </ul>
  </div>
</template>
 
<script>
import http from "../http-common";
 
export default {
  name: "search-customer",
  data() {
    return {
      age: 0,
      customers: []
    };
  },
  methods: {
    /* eslint-disable no-console */
    searchCustomers() {
      http
        .get("/customers/age/" + this.age)
        .then(response => {
          this.customers = response.data; // JSON are parsed automatically.
          console.log(response.data);
        })
        .catch(e => {
          console.log(e);
        });
    }
    /* eslint-enable no-console */
  }
};
</script>
 
<style>
.searchform {
  max-width: 300px;
  margin: auto;
}
.search-result {
  margin-top: 20px;
  text-align: left;
}
</style>

2.3 為 Vue App配置端口

vue.config.js
module.exports = {
  devServer: {
    port: 4200
  }
}

運(yùn)行代碼:

– Spring Boot Server: mvn clean install and mvn spring-boot:run.
– Vue.js Client: npm run serve.
在瀏覽器中打開: http://localhost:4200/.

源代碼:

SpringBootRestMySQL
vue-springboot

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末婆誓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子也颤,更是在濱河造成了極大的恐慌洋幻,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翅娶,死亡現(xiàn)場(chǎng)離奇詭異文留,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)竭沫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門燥翅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜕提,你說我怎么就攤上這事森书。” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵凛膏,是天一觀的道長(zhǎng)杨名。 經(jīng)常有香客問我,道長(zhǎng)猖毫,這世上最難降的妖魔是什么台谍? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮吁断,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胯府。我一直安慰自己介衔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布骂因。 她就那樣靜靜地躺著炎咖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寒波。 梳的紋絲不亂的頭發(fā)上乘盼,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音俄烁,去河邊找鬼绸栅。 笑死,一個(gè)胖子當(dāng)著我的面吹牛页屠,可吹牛的內(nèi)容都是我干的粹胯。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼辰企,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼风纠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牢贸,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤竹观,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后潜索,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臭增,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年竹习,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了誊抛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡整陌,死狀恐怖芍锚,靈堂內(nèi)的尸體忽然破棺而出昔园,到底是詐尸還是另有隱情,我是刑警寧澤并炮,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布默刚,位于F島的核電站,受9級(jí)特大地震影響逃魄,放射性物質(zhì)發(fā)生泄漏荤西。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一伍俘、第九天 我趴在偏房一處隱蔽的房頂上張望邪锌。 院中可真熱鬧,春花似錦癌瘾、人聲如沸觅丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妇萄。三九已至,卻和暖如春咬荷,著一層夾襖步出監(jiān)牢的瞬間冠句,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工幸乒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留懦底,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓罕扎,卻偏偏與公主長(zhǎng)得像聚唐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腔召,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容