# 通过一个简单示例快速上手Spring Boot+Vue前后端分离

转载于:楠哥教你学Java (opens new window)

# 前言

Spring Boot + Vue 前后端分离的开发方式现在真的是火的不得了,之前楠哥写过一篇前后端分离的教程以及一篇用 Vue + Element UI 搭建后台管理系统界面的教程:

1、还搞不明白前后端分离?看完这篇文章,小白也能马上学会

2、动态加载router,用Vue+Element UI搭建后台管理系统界面

Vue + Element UI 并不是真正的前后端分离,它只有前端服务,并没有后端服务提供数据接口,很多小伙伴在后台留言希望楠哥能写一篇完整的 Spring Boot + Vue 前后端分离教程。大家有需求,楠哥就会尽量满足,所以今天用一个简单易懂的例子,快速教会大家如何实现 Spring Boot + Vue 的前后端分离开发。

既然是前后端分离,那么我们就分开来写两个独立的服务,首先完成后端服务,提供数据接口。然后完成前端服务通过 Ajax 调用后端接口并动态绑定数据。后端服务我们使用 Spring Boot + MyBatis,前端服务使用 Vue + Element UI。

# 后端服务

1、我们以查询图书为例,完成基于 RESTful 的接口开发,数据库建表语句如下所示。

DROP TABLE IF EXISTS `bookcase`;
CREATE TABLE `bookcase` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

LOCK TABLES `bookcase` WRITE;
INSERT INTO `bookcase` VALUES 
(1,'社会'),
(2,'情感'),
(3,'国学'),
(4,'推理'),
(5,'绘画'),
(6,'心理学'),
(7,'传记'),
(8,'科技'),
(9,'计算机'),
(10,'小说');
UNLOCK TABLES;

DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `author` varchar(20) DEFAULT NULL,
  `publish` varchar(20) DEFAULT NULL,
  `pages` int(10) DEFAULT NULL,
  `price` float(10,2) DEFAULT NULL,
  `bookcaseid` int(10) DEFAULT NULL,
  `abled` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_ieh6qsxp6q7oydadktc9oc8t2` (`bookcaseid`),
  CONSTRAINT `FK_ieh6qsxp6q7oydadktc9oc8t2` FOREIGN KEY (`bookcaseid`) REFERENCES `bookcase` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=74 DEFAULT CHARSET=utf8;

LOCK TABLES `book` WRITE;
INSERT INTO `book` VALUES 
(1,'解忧杂货店','东野圭吾','电子工业出版社',102,27.30,9,0),
(2,'追风筝的人','卡勒德·胡赛尼','上海人民出版社',230,33.50,3,0),
(3,'人间失格','太宰治','作家出版社',150,17.30,1,1),
(4,'这就是二十四节气','高春香','电子工业出版社',220,59.00,3,1),
(5,'白夜行','东野圭吾','南海出版公司',300,27.30,4,1),
(6,'摆渡人','克莱儿·麦克福尔','百花洲文艺出版社',225,22.80,1,1),
(7,'暖暖心绘本','米拦弗特毕','湖南少儿出版社',168,131.60,5,1),
(8,'天才在左疯子在右','高铭','北京联合出版公司',330,27.50,6,1),
(9,'我们仨','杨绛','生活.读书.新知三联书店',89,17.20,7,1),
(10,'活着','余华','作家出版社',100,100.00,6,1),
(11,'水浒传','施耐庵','三联出版社',300,50.00,1,1),
(12,'三国演义','罗贯中','三联出版社',300,50.00,2,1),
(13,'红楼梦','曹雪芹','三联出版社',300,50.00,5,1),
(14,'西游记','吴承恩','三联出版社',300,60.00,3,1);
UNLOCK TABLES;

2、新建 Maven 工程,pom.xml 引入相关依赖。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

3、创建实体类。

@Data
public class BookCase {
  private int id;
  private String name;
}

@Data
public class Book {
   private int id;
   private String name;
   private String author;
   private String publish;
   private int pages;
   private double price;
   private BookCase bookCase;
}

@Data
public class BookVO {
    private Integer total;
    private List<Book> data;
    private Integer pageSize;
}

4、创建 Repository 接口。

public interface BookRepository {
    public List<Book> find(Integer index,Integer limit);
    public Integer count();
}

public interface BookCaseRepository {
    public BookCase findById(Integer id);
}

5、在 resources/mapping 路径下创建 Repository 对应的 Mapper.xml。

<mapper namespace="com.southwind.repository.BookRepository">
    <resultMap id="bookMap" type="Book">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="author" column="author"/>
        <result property="publish" column="publish"/>
        <result property="pages" column="pages"/>
        <result property="price" column="price"/>
        <association property="bookCase" javaType="BookCase" select="com.southwind.repository.BookCaseRepository.findById" column="bookcaseid"></association>
    </resultMap>
    <select id="find" resultMap="bookMap">
        select * from book limit #{param1},#{param2}
    </select>
    <select id="count" resultType="Integer">
        select count(*) from book
    </select>
</mapper>

<mapper namespace="com.southwind.repository.BookCaseRepository">
    <select id="findById" parameterType="java.lang.Integer" resultType="BookCase">
        select * from bookcase where id = #{id}
    </select>
</mapper>

6、创建 Service 接口及实现类。

public interface BookService {
    public BookVO findByPage(Integer page);
}

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookRepository bookRepository;
    private Integer limit = 10;    
    
    @Override
    public BookVO findByPage(Integer page) {
        Integer index = (page-1)*limit;
        BookVO bookVO = new BookVO();
        bookVO.setData(bookRepository.find(index,limit));
        bookVO.setTotal(bookRepository.count());
        bookVO.setPageSize(limit);
        return bookVO;
    }
}

7、创建 Handler。

@RestController
@RequestMapping("/book")
public class BookHandler {

    @Autowired
    private BookService bookService;

    @GetMapping("/findByPage/{page}")
    public BookVO findByPage(@PathVariable("page") Integer page){
        return bookService.findByPage(page);
    }
}

8、前端服务调用后端接口时存在跨域问题,在 Spring Boot 中可以通过实现 WebMvcConfigurer 接口来解决跨域问题。

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");
    }
}

9、在 resources 路径下创建配置文件 application.yml。

server:
  port: 8181
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  mapper-locations: classpath:/mapping/*.xml
  type-aliases-package: com.southwind.entity
logging:
  level:
    com.southwind.repository.*: debug

10、创建启动类 Application 并运行。

@SpringBootApplication
@MapperScan("com.southwind.repository")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

11、启动成功后使用 Postman 测试 Handler 接口,如下图所示。

1

端口为 8181 的后端服务创建成功,接下来完成前端服务,并调用后端服务接口 。

# 前端服务

1、创建 Vue 工程,并安装 Element UI 和 axios。

2、我们使用 Element UI 来搭建前端界面,Element UI 提供了数据表格的组件,非常简单,直接查看 Element UI 官方文档即可,官方示例代码如下所示。

<template>
  <el-table :data="tableData" border style="width: 100%">
    <el-table-column fixed prop="date" label="日期" width="150">
    </el-table-column>
    <el-table-column prop="name" label="姓名" width="120">
    </el-table-column>
    <el-table-column prop="province" label="省份" width="120">
    </el-table-column>
    <el-table-column prop="city" label="市区" width="120">
    </el-table-column>
    <el-table-column prop="address" label="地址" width="300">
    </el-table-column>
    <el-table-column prop="zip" label="邮编" width="120">
    </el-table-column>
    <el-table-column fixed="right" label="操作" width="100">
      <template slot-scope="scope">
        <el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button>
        <el-button type="text" size="small">编辑</el-button>
      </template>
    </el-table-column>
  </el-table></template><script>
  export default {    
      methods: {
      handleClick(row) {
          console.log(row);
      }
    },
    data() {      
        return {        
          tableData: [
            {date: '2016-05-03',name: '王小虎',province: '上海',city: '普陀区',address: '上海市普陀区金沙江路 1518 弄',zip: 200333},
            {date: '2016-05-03',name: '王小虎',province: '上海',city: '普陀区',address: '上海市普陀区金沙江路 1518 弄',zip: 200333},
            {date: '2016-05-03',name: '王小虎',province: '上海',city: '普陀区',address: '上海市普陀区金沙江路 1518 弄',zip: 200333},
            {date: '2016-05-03',name: '王小虎',province: '上海',city: '普陀区',address: '上海市普陀区金沙江路 1518 弄',zip: 200333}
           ]
        }
    }
  }
</script>

3、运行结果如下图所示。

2

4、在 Vue 工程 components 路径下创建 Table.Vue,在初始化方法中通过 axios 调用后端服务的数据接口并完成动态绑定,代码如下所示。

<template>
    <div>
        <div>
            <el-table
                    :data="tableData"
                    border
                    style="width: 80%;margin-left: 100px;margin-top: 30px;">
                <el-table-column
                        fixed
                        prop="id"
                        label="编号"
                        width="100">
                </el-table-column>
                <el-table-column
                        prop="name"
                        label="图书"
                        width="150">
                </el-table-column>
                <el-table-column
                        prop="author"
                        label="作者"
                        width="150">
                </el-table-column>
                <el-table-column
                        prop="publish"
                        label="出版社"
                        width="180">
                </el-table-column>
                <el-table-column
                        prop="pages"
                        label="总页数"
                        width="100">
                </el-table-column>
                <el-table-column
                        prop="price"
                        label="价格"
                        width="100">
                </el-table-column>
                <el-table-column
                        prop="bookCase.name"
                        label="分类"
                        width="120">
                </el-table-column>
                <el-table-column
                        fixed="right"
                        label="操作"
                        width="100">
                    <template slot-scope="scope">
                        <el-button type="text" size="small">修改</el-button>
                        <el-button type="text" size="small">删除</el-button>
                    </template>
                </el-table-column>

            </el-table>
            <el-pagination
                    background
                    layout="prev, pager, next"
                    :total="total"
                    :page-size="pageSize"
                    @current-change="change"
            >
            </el-pagination>
        </div>
    </div>
</template>

<script>
    export default {
        methods: {
            change(currentPage) {
                this.currentPage = currentPage
                const _this = this
                axios.get('http://localhost:8181/book/findByPage/'+currentPage).then(function (resp) {
                    _this.tableData = resp.data.data
                })
            }
        },

        data() {
            return {
                total:0,
                pageSize:5,
                tableData: [],
                currentPage:0
            }
        },

        created() {
            const _this = this
            axios.get('http://localhost:8181/book/findByPage/1').then(function (resp) {
                console.log(resp.data)
                _this.pageSize = resp.data.pageSize
                _this.total = resp.data.total
                _this.tableData = resp.data.data
            })
        }
    }
</script>

5、在 router.js 中设置 Table.Vue 路由,代码如下所示。

import Table from '../components/Table'
routes:[
     {
         path: '/table', 
         component: Table, 
         name: '查询图书'
     }
]

6、在终端执行命令 npm run serve 启动 Vue 服务,打开浏览器访问 http://localhost:8080/table,结果如下图所示。

3

这样我们就完成了 Spring Boot + Vue 前后端分离的开发,很简单吧,你学会了吗?