深入实践Spring

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
数据管理 DMS,安全协同 3个实例 3个月
推荐场景:
学生管理系统数据库
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介:

82a324c58bf7664b5b31b58e0614d7eaf6636e8a


深入实践Spring Boot


陈韶健 著






图书在版编目(CIP)数据

深入实践Spring Boot / 陈韶健著. —北京:机械工业出版社,2016.10

ISBN 978-7-111-55088-4

I. 深… II. 陈… III. JAVA语言-程序设计 IV. TP312

中国版本图书馆CIP数据核字(2016)第244089号





深入实践Spring Boot

出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037)

责任编辑:李 艺 责任校对:殷 虹

印  刷: 版  次:2016年11月第1版第1次印刷

开  本:186mm×240mm 1/16 印  张:17.25

书  号:ISBN 978-7-111-55088-4 定  价:59.00元

凡购本书,如有缺页、倒页、脱页,由本社发行部调换

客服热线:(010)88379426 88361066 投稿热线:(010)88379604

购书热线:(010)68326294 88379649 68995259 读者信箱:hzit@hzbook.com

版权所有 ? 侵权必究

封底无防伪标均为盗版

本书法律顾问:北京大成律师事务所 韩光/邹晓东





Preface?前  言

Spring Boot作为Java编程语言的一个全新开发框架,在国内外才刚刚兴起,还未得到普及使用。相比于以往的一些开发框架,Spring Boot不但使用更加简单,而且功能更加丰富,性能更加稳定而健壮。使用Spring Boot开发框架,不仅能提高开发速度,增强生产效率,从某种意义上,可以说是解放了程序员的劳动,而且一种新技术的使用,更能增强系统的稳定性和扩展系统的性能指标。本书就是本着提高开发效率,增强系统性能,促进新技术的普及使用这一目的而写的。

Spring Boot是在Spring框架基础上创建的一个全新框架,其设计目的是简化Spring应用的搭建和开发过程,它不但具有Spring的所有优秀特性,而且具有如下显著的特点:

为Spring开发提供更加简单的使用和快速开发的技巧。

具有开箱即用的默认配置功能,能根据项目依赖自动配置。

具有功能更加强大的服务体系,包括嵌入式服务、安全、性能指标、健康检查等。

绝对没有代码生成,可以不再需要XML配置,即可让应用更加轻巧和灵活。

Spring Boot对于一些第三方技术的使用,提供了非常完美的整合,使你在简单的使用中,不知不觉运用了非常高级和先进的技术。

虽然Spring Boot具有这么多优秀的特性,但它使用起来并不复杂,而且非常简单,所以不管是Java程序开发初学者,还是经验丰富的开发人员,使用Spring Boot都是一个理想的选择。

Spring Boot发展迅速,自从2014年4月发布了1.0.0版本,至今已经发布了1.4.0正式版。现在,Spring Boot正在不同的角落中悄然兴起,估计用不了多久,它将成为Java开发的又一个热潮,为众多Java开发者追捧。

本书将以一些非常切合生产实际的应用实例,带你一起使用Spring Boot框架,开始一段愉快的快速开发和探索之旅。

关于本书

本书以丰富的实例,介绍了如何使用Spring Boot开发框架进行基础应用和分布式应用等方面的开发,并且介绍了如何使用Spring Boot开发的应用搭建一个高性能的服务平台,同时还对Spring Boot的一些核心功能的源代码进行了分析,从而加深对Spring Boot的理解。书中对从最基本的入门知识,到数据库的使用,以及界面设计、安全设计等领域都做了详细的介绍和探讨,并在分布式应用系统领域,以平台级应用系统的实例,介绍了如何创建和使用SSO管理系统、分布式文件系统,如何使用Spring Cloud进行云应用方面的开发,以及如何使用Docker发布和构建高可用的分布式系统服务平台。同时,对Spring Boot的程序加载、自动配置、数据管理,和Spring Cloud的配置管理、发现服务和负载均衡服务等核心功能的源代码做了深入剖析,这样在认识其实现原理的基础上,能更好地使用其相应的功能。

全书分为三个部分:第一部分(第1~5章)介绍基础应用方面的开发,包含简单入门知识、数据库使用、界面设计和安全设计等内容;第二部分(第6~9章)介绍了Spring Boot在分布式系统开发和云应用开发等方面的应用以及使用微服务构建高可用的服务平台;第三部分(第10~12章)对Spring Boot的程序加载、自动配置和数据管理的实现原理,以及Spring Cloud的配置管理、发现服务和负载均衡服务等实现原理进行了深入的剖析。

本书章节编排

第1章为Spring Boot入门,介绍开发环境的搭建和开发工具的选择及安装配置,并使用一个非常简单的实例,说明如何轻易地使用Spring Boot开发框架。

第2章使用Spring Boot框架演示了以不同于以往的方式,以及如何轻易地使用数据库,并实际演示使用MySQL、MongoDB、Redis和Neo4j等数据库。

第3章使用Thymeleaf模板结合一些流行的JavaScript插件,介绍了使用Spring Boot进行界面设计的方法和技巧。

第4章对使用Spring Boot提高传统关系型数据库的性能方面做了一些探讨和尝试,并扩展了使用JPA资源库的功能。

第5章介绍了如何使用Spring Boot结合Spring Security进行安全设计,包括登录认证和角色管理、权限管理等内容。

第6章介绍如何使用Spring Security结合OAuth2进行SSO(Single Sign On)的设计,并演示如何在分布式应用系统中使用认证授权和安全管理的功能。

第7章介绍如何使用Spring Boot框架结合分布式文件系统FastDFS,并使用定制方式和富文本编辑器的方式演示了使用图片上传和建立本地图片库的方法。

第8章介绍云应用开发,包括配置管理、发现服务和监控服务的使用,以及如何使用动态路由和断路器的功能,创建高可用的微服务应用。

第9章介绍如何使用Docker引擎和docker-compose工具来发布应用和管理服务,以及如何构建一个高性能的服务平台和怎样使用Docker实施负载均衡。

第10章分析了Spring Boot的应用程序加载和自动配置原理,以及如何以改造加载配置的方式来提高应用的性能。

第11章分析了Spring Boot使用数据库的实现原理,并演示怎样利用一些技术手段提高和扩展访问数据库的功能。

第12章简要分析了微服务中配置管理、发现服务和负载均衡服务的实现原理和部分核心源代码,并使用一个实例说明配置管理中分布式消息的实现机制和原理。

附录A~附录D介绍了Neo4j、MongoDB、Redis、RabbitMQ等服务器的安装、配置和基本使用方法。

读者对象

本书适于所有Java编程语言开发人员,所有对Spring Boot感兴趣并希望使用Spring Boot开发框架进行开发的人员,已经使用过Spring Boot框架但希望更好地使用Spring Boot的开发人员,以及系统设计师、架构师等设计人员。同时,本书对运维人员和DBA等也具有一定的参考价值。

实例代码

本书的实例代码可以通过https://github.com/chenfromsz?tab=repositories查看和下载,推荐根据每章的提示使用IntelliJ IDEA通过GitHub检出各章的实例工程,这样可以保留原来工程的配置,并且能够直接使用。

反馈与勘误

读者如有反馈意见可以通过https://github.com/chenfromsz/correct/issues发起新话题与作者进行交互,在这也可能会发布一些勘误信息,以便纠正不可避免的错误。

致谢

首先,非常感谢华阳信通公司,虽然本书的编写过程大都在业余时间完成,但是公司强大的平台给本书的实例提供了更加方便的分享、验证和测试条件。同时在本书的编写过程中,也得到了我们的开发团队和众多朋友的大力支持和帮助,在此表示衷心的感谢!最后感谢华章公司的杨福川和李艺,他们在本书编辑的过程中,提出了一些宝贵而有益的建议,并为本书的出版做了许多工作。

由于时间仓促和水平有限,书中难免出现一些纰漏或不正确的地方,敬请大家批评指正!






Contents 目  录

前 言

第一部分 基础应用开发

第1章 Spring Boot入门  3

1.1 配置开发环境  3

1.1.1 安装JDK  3

1.1.2 安装InterlliJ IDEA  4

1.1.3 安装Apache Maven  4

1.1.4 安装Git客户端  5

1.2 创建项目工程  8

1.2.1 使用Maven新建项目  8

1.2.2 使用Spring Initializr新建项目  11

1.3 使用Spring Boot  14

1.3.1 Maven依赖管理  14

1.3.2 一个简单的实例  17

1.4 运行与发布  18

1.4.1 在IDEA环境中运行  18

1.4.2 将应用打包发布  19

1.5 关于Spring Boot配置  22

1.6 小结  23

第2章 在Spring Boot中使用数据库  24

2.1 使用MySQL  24

2.1.1 MySQL依赖配置  25

2.1.2 实体建模  25

2.1.3 实体持久化  27

2.1.4 MySQL测试  29

2.2 使用Redis  33

2.2.1 Redis依赖配置  33

2.2.2 创建Redis服务类  34

2.2.3 Redis测试  36

2.3 使用MongoDB  38

2.3.1 MongoDB依赖配置  38

2.3.2 文档建模  39

2.3.3 文档持久化  40

2.3.4 MongoDB测试  41

2.4 使用Neo4j  43

2.4.1 Neo4j依赖配置  43

2.4.2 节点和关系实体建模  43

2.4.3 节点实体持久化  45

2.4.4 Neo4j测试  46

2.5 小结  49

第3章 Spring Boot界面设计  50

3.1 模型设计  50

3.1.1 节点实体建模  51

3.1.2 关系实体建模  51

3.1.3 分页查询设计  52

3.2 控制器设计  53

3.2.1 新建控制器  53

3.2.2 查看控制器  53

3.2.3 修改控制器  54

3.2.4 删除控制器  55

3.2.5 分页查询控制器  55

3.3 使用Thymeleaf模板  56

3.3.1 Thymeleaf配置  56

3.3.2 Thymeleaf功能简介  57

3.4 视图设计  60

3.4.1 列表视图设计  60

3.4.2 新建视图设计  64

3.4.3 查看视图设计  68

3.4.4 修改视图设计  70

3.4.5 删除视图设计  72

3.5 运行与发布  73

3.6 小结  74

第4章 提高数据库访问性能  75

4.1 使用Druid  75

4.1.1 配置Druid依赖  76

4.1.2 关于XML配置  76

4.1.3 Druid数据源配置  77

4.1.4 开启监控功能  78

4.2 扩展JPA功能  80

4.2.1 扩展JPA接口  81

4.2.2 装配自定义的扩展接口  83

4.2.3 使用扩展接口  85

4.3 使用Redis做缓存  86

4.3.1 使用Spring Cache注解  86

4.3.2 使用RedisTemplate  88

4.4 Web应用模块  91

4.4.1 引用数据管理模块  91

4.4.2 Web应用配置  92

4.5 运行与发布  94

4.6 小结  95

第5章 Spring Boot安全设计  96

5.1 依赖配置管理  96

5.2 安全策略配置  97

5.2.1 权限管理规则  98

5.2.2 登录成功处理器  99

5.2.3 防攻击策略  100

5.2.4 记住登录状态  102

5.3 登录认证设计  103

5.3.1 用户实体建模  103

5.3.2 用户身份验证  104

5.3.3 登录界面设计  106

5.3.4 验证码验证  108

5.4 权限管理设计  109

5.4.1 权限管理配置  109

5.4.2 权限管理过滤器  110

5.4.3 权限配置资源管理器  111

5.4.4 权限管理决断器  112

5.5 根据权限设置链接  113

5.6 运行与发布  116

5.6.1 系统初始化  116

5.6.2 系统运行与发布  118

5.7 小结  119

第二部分 分布式应用开发

第6章 Spring Boot SSO  123

6.1 模块化设计  123

6.2 登录认证模块  124

6.2.1 使用OAuth2  124

6.2.2 创建数字证书  125

6.2.3 认证服务端配置  125

6.3 安全配置模块  128

6.4 SSO客户端  129

6.4.1 客户端配置  129

6.4.2 登录登出设计  130

6.5 共享资源服务  132

6.5.1 提供共享资源接口  133

6.5.2 使用共享资源  134

6.5.3 查询登录用户的详细信息  135

6.6 运行与发布  136

6.7 小结  138

第7章 使用分布式文件系统  139

7.1 FastDFS安装  139

7.1.1 下载安装包  141

7.1.2 安装服务  141

7.1.3 Tracker Server配置  142

7.1.4 Storage Server配置  145

7.1.5 启动服务  148

7.1.6 客户端测试  148

7.2 FastFDS客户端  149

7.2.1 客户端配置  150

7.2.2 客户端服务类  150

7.3 使用定制方式上传图片  151

7.3.1 实体建模  151

7.3.2 上传图片  152

7.4 使用富文本编辑器上传图片  156

7.4.1 使用富文本编辑器  156

7.4.2 实现文件上传  157

7.5 使用本地文件库  158

7.5.1 本地文件库建模  159

7.5.2 文件保存方法  159

7.5.3 文件库管理  161

7.6 运行与发布  163

7.7 小结  164

第8章 云应用开发  165

8.1 使用配置管理  166

8.1.1 创建配置管理服务器  167

8.1.2 使用配置管理的客户端  168

8.1.3 实现在线更新  171

8.1.4 更新所有客户端的配置  172

8.2 使用发现服务  174

8.2.1 创建发现服务器  174

8.2.2 使用发现服务的客户端配置  175

8.2.3 发现服务器测试  175

8.3 使用动态路由和断路器  176

8.3.1 依赖配置  176

8.3.2 共享REST资源  177

8.3.3 通过路由访问REST资源  180

8.3.4 使用断路器功能  182

8.3.5 路由器和断路器测试  183

8.4 使用监控服务  184

8.4.1 创建监控服务器  184

8.4.2 监控服务器测试  185

8.5 运行与发布  187

8.6 小结  187

第9章 构建高性能的服务平台  188

9.1 使用Docker  188

9.1.1 Docker安装  189

9.1.2 Docker常用指令  190

9.1.3 使用Docker发布服务  191

9.2 创建和管理一个高性能的服务体系  194

9.2.1 安装docker-compose  194

9.2.2 docker-compose常用指令  195

9.2.3 使用docker-compose管理服务  195

9.3 使用Docker的其他负载均衡实施方法  199

9.3.1 使用Nginx与Docker构建负载均衡服务  199

9.3.2 阿里云的负载均衡设计实例  199

9.4 小结  201

第三部分 核心技术源代码分析

第10章 Spring Boot自动配置实现原理  205

10.1 Spring Boot主程序的功能  205

10.1.1 SpringApplication的run方法  206

10.1.2 创建应用上下文  207

10.1.3 自动加载  208

10.2 Spring Boot自动配置原理  209

10.2.1 自动配置的即插即用原理  210

10.2.2 自动配置的约定优先原理  211

10.3 提升应用的性能  211

10.3.1 更改加载配置的方式  212

10.3.2 将Tomcat换成Jetty  214

10.4 性能对照测试  215

10.5 小结  217

第11章 Spring Boot数据访问实现原理  218

11.1 连接数据源的源代码分析  218

11.1.1 数据源类型和驱动  219

11.1.2 支持的数据库种类  220

11.1.3 与数据库服务器建立连接  221

11.2 数据存取功能实现原理  222

11.2.1 实体建模源代码分析  222

11.2.2 持久化实现原理  225

11.3 扩展数据存取的功能  227

11.3.1 扩展JPA功能  227

11.3.2 扩展Neo4j功能  228

11.4 小结  230

第12章 微服务核心技术实现原理  231

12.1 配置管理实现原理  232

12.1.1 在线更新流程  232

12.1.2 更新消息的分发原理  233

12.2 发现服务源代码剖析  235

12.2.1 服务端的服务注册功能  236

12.2.2 客户端注册和提取服务列表  238

12.3 负载均衡源代码剖析  240

12.4 分布式消息实现原理演示  244

12.4.1 消息生产者  244

12.4.2 消息消费者  245

12.5 小结  247

附录A 安装Neo4j  248

附录B 安装MongoDB  251

附录C 安装Redis  253

附录D 安装RabbitMQ  256

结束语  262

第一部分 Part 1

基础应用开发

第1章 Spring Boot入门

第2章 在Spring Boot中使用数据库

第3章 Spring Boot界面设计

第4章 提高数据库访问性能

第5章 Spring Boot安全设计


这一部分从搭建开发环境,简单入门,到使用数据库、界面设计、安全管理等一系列内容,介绍了使用Spring Boot框架进行基础应用开发的方法。

第1章介绍了开发环境的搭建和开发工具的选择和安装,并以一个非常简单的实例,演示了如何使用Spring Boot框架创建工程和发布应用。

第2章介绍了如何用Spring Boot特有的方式,使用当前流行的数据库:MySQL、Redis、MongoDB、Neo4j等。

第3章介绍如何使用Thymeleaf模板结合一些流行的JavaScript插件,设计应用界面。

第4章使用Druid数据库连接池和Redis做缓存来尝试提升关系型数据库的访问性能,并扩展了JPA的资源库功能。

第5章在Spring Boot中使用Spring Security为应用系统进行安全设计,实现了登录认证和权限管理方面的功能。


第1章

Spring Boot入门

在使用Spring Boot框架进行各种开发体验之前,要先配置好开发环境。首先安装JDK,然后选择一个开发工具,如Eclipse IDE和IntelliJ IDEA(以下简称IDEA)都是不错的选择。对于开发工具的选择,本书极力推荐使用IDEA,因为它为Spring Boot提供了许多更好和更贴切的支持,本书的实例都是使用IDEA创建的。同时,还需要安装Apache Maven和Git客户端。所有这些都准备好之后,我们就能开始使用Spring 

Boot了。

1.1 配置开发环境

下面的开发环境配置主要以使用Windows操作系统为例,如果你使用的是其他操作系统,请对照其相关配置进行操作。

1.1.1 安装JDK

JDK(Java SE Development Kit)需要1.8及以上版本,可以从Java的官网http://www.oracle.com/technetwork/java/javase/downloads/index.html下载安装包。如果访问官网速度慢的话,也可以通过百度搜索JDK,然后在百度软件中心下载符合你的Windows版本和配置的JDK1.8安装包。

安装完成后,配置环境变量JAVA_HOME,例如,使用路径D:\Program Files\Java\jdk1.8.0_25(如果你安装的是这个目录的话)。JAVA_HOME配置好之后,将%JAVA_HOME%\bin加入系统的环境变量path中。完成后,打开一个命令行窗口,输入命令java–version,如果能正确输出版本号则说明安装成功了。输出版本的信息如下:

C:\Users\Alan>java-version

java version "1.8.0_25"

Java(TM) SE Runtime Environment (build 1.8.0_25-b18)

Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

1.1.2 安装InterlliJ IDEA

IDEA需要14.0以上的版本,可以从其官网http://www.jetbrains.com/下载免费版,本书的实例是使用IDEA14.1.15版本开发的。IDEA已经包含Maven插件,版本是3.0.5,这已经能够适用我们开发的要求。安装完成后,打开IDEA,将显示如图1-1所示的欢迎界面,在这里可以看到IDEA的版本号。


图1-1 InterlliJ IDEA欢迎界面

1.1.3 安装Apache Maven

为了能够在命令行窗口中使用Maven来管理工程,可以安装一个Maven管理工具。通过Maven的官网http://maven.apache.org/download.cgi下载3.0.5以上的版本,下载完后解压缩即可,例如,解压到D:盘上是不错的做法,然后将Maven的安装路径(如D:\apache-maven-3.2.3\bin)也加入Windows的环境变量path中。安装完成后,在命令行窗口中执行指令:mvn–v,将输出如下的版本信息以及系统的一些环境信息。

C:\Users\Alan>mvn-v

Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-12T04:58:1

0+08:00)

Maven home: D:\apache-maven-3.2.3\bin\..

Java version: 1.8.0_25, vendor: Oracle Corporation

Java home: D:\Program Files\Java\jdk1.8.0_25\jre

Default locale: zh_CN, platform encoding: GBK

OS name: "windows 7", version: "6.1", arch: "amd64", family: "dos"

建议更改IDEA中Maven资源库的存放路径,可以先在Maven安装路径中创建一个资源库目录,如repository。然后打开Maven的配置文件,即安装目录conf中的settings.xml,找到下列代码,将路径更改为repository所在的位置,并保存在注释符下面。

例如找到下列代码行:

<localRepository>/path/to/local/repo</localRepository>

复制出来改为如下所示:

<localRepository>D:\apache-maven-3.2.3\repository</localRepository>

改好后可以拷贝一份settings.xml放置在${user.home}/.m2/下面,这样做可以不用修改IDEA的Maven这个配置。在图1-2所示的Maven配置界面中,User Settings File保持了默认位置,Local Repository使用了上面设置的路径D:\apache-maven-3.2.3\repository,而Maven程序还是使用了IDEA自带的版本。

1.1.4 安装Git客户端

由于本书的实例工程都存放在GitHub(https://github.com/)中,所以还需要在GitHub中免费注册一个用户(可以通过E-mail直接注册免费用户),以方便在IDEA中从GitHub检出本书的实例工程。当然,如果不想注册,通过普通下载的方法也能取得实例工程的源代码。GitHub是世界级的代码库服务器,如果你愿意,也可以将它作为你的代码库服务器,在这里还可以搜索到全世界的开发者分享出来的源程序。图1-3是打开GitHub的首页。


图1-2 Maven设置


图1-3 GitHub首页

IDEA还需要Git客户端程序的支持。可以从其官网https://git-scm.com/download/下载Git客户端安装包。安装非常简单,按提示单击“下一步”并选择好安装路径即可。安装完成后,在Windows的资源管理器中,单击鼠标右键弹出的菜单中将会多出如下几个选择菜单:

Git Init Here

Git Gui

Git Bash

其中Git Bash是一个带有UNIX指令的命令行窗口,在这里可以执行一些Git指令,用来提交或者检出项目。

在IDEA中对Git的设置,只要指定git.exe执行文件的位置即可。图1-4是IDEA中Git客户端的配置,其中Git的路径被设置在D:\Program Files\Git\bin\git.exe中,这主要由安装Git客户端的位置而定。


图1-4 Git设置

如果已经在GitHub中注册了用户,即可以打开如图1-5所示的GitHub配置,输入用户名和密码,然后单击Test按钮,如果设置正确的话将会返回连接成功的提示。


图1-5 GitHub配置

上面IDEA的一些设置界面都可以单击工具栏上的Settings按钮打开,打开File菜单,选择Settings同样也可以打开。

1.2 创建项目工程

现在,可以尝试使用IDEA来创建一个项目工程。如果是第一次打开IDEA,可以选择Create New Project创建一个新工程。如果已经打开了IDEA,在File菜单中选择New Project,也能打开New Project对话框,如图1-6所示。使用IDEA创建一个Spring Boot项目有很多方法,这里只介绍使用Maven和Spring Initializr这两种方法来创建一个新项目。一般使用Maven来新建一个项目,因为这样更容易按我们的要求配置一个项目。

1.2.1 使用Maven新建项目

使用Maven新建一个项目主要有以下三个步骤。


图1-6 新建一个Maven项目

1.?选择项目类型

在图1-6中的Project SDK下拉列表框中选择前面安装的Java 1.8,如果下拉列表框中不存在Java 1.8,可以单击New按钮,找到安装Java的位置,选择它。然后在左面侧边栏的项目类型中,选择Maven项目,即可使用Maven作为项目的管理工具。至于Maven中的archetype,因为我们并不打算使用其中任何一种类型,所以不用勾选,然后单击Next进入下一步。

2.?输入GroupId和ArtifactId

在GroupId输入框中输入“springboot.example”,在ArtifactId输入框中输入“spring-boot-hello”,Version输入框中保持默认值,如图1-7所示,单击Next进入下一步。

3.?指定项目名称和存放路径

在Project location编辑框中选择和更改存放路径,在Project name输入框中输入与ArtifactId相同的项目名称:“spring-boot-hello”,如图1-8所示。

单击Finish,完成项目创建,这样将在当前窗口中打开一个新项目,如图1-9所示。其中,在工程根目录中生成了一个pom.xml,即Maven的项目对象模型(Project Object Model),并生成了源代码目录java、资源目录resources和测试目录test等,即生成了一个项目的一些初始配置和目录结构。


图1-7 输入GroupId和ArtifactId


图1-8 指定项目名称和存放路径


图1-9 初始创建的项目

下一节将使用这个项目工程来创建第一个使用Spring Boot开发框架的应用实例。

1.2.2 使用Spring Initializr新建项目

新建一个Spring Boot项目,也可以使用Spring Initializr的方式,这种方式很简单,如图1-10所示。注意Initializr Service URL为https://start.spring.io,这将会连接网络,以查询Spring Boot的当前可用版本和组件列表。使用这种方式新建项目大体上也需要三个步骤。

1.?选择类型

可以使用默认选项,注意Type为Maven Project,Java Version为1.8,Packaging为Jar,如图1-11所示。单击Next进入下一步。

2.?选择Spring Boot版本和组件

选择Spring Boot版本和Spring Boot组件,例如,在Spring Boot Version中选择1.3.5,并勾选Web项目组件,如图1-12所示,然后单击Next进入下一步。


图1-10 新建一个Spring Boot项目


图1-11 选择项目类型


图1-12 选择版本和组件

3.?输入项目名称

选择存放路径后输入项目名称,如图1-13所示,这里使用demo作为项目的名称。


图1-13 输入项目名称

单击Finish,将创建一个初始化项目,如图1-14所示。这个项目不但有完整的目录结构,还有一个完整的Maven配置,并且生成了一个默认的主程序,几乎所有的准备工作都已经就绪,并且可以立即运行起来(虽然没有提供任何可用的服务)。这也是Spring Boot引以为傲的地方,即创建一个应用可以不用编写任何代码,只管运行即可。


图1-14 使用Spring Initializr创建的初始项目

1.3 使用Spring Boot

任何应用的开发都需要对项目的创建、运行和发布等进行管理,使用Spring Boot框架进行开发,可以选择使用Maven或Gradle等项目管理工具。在这里我们使用的是Maven。

1.3.1 Maven依赖管理

使用Maven,通过导入Spring Boot的starter模块,可以将许多程序依赖包自动导入工程中。使用Maven的parent POM,还可以更容易地管理依赖的版本和使用默认的配置,工程中的模块也可以很方便地继承它。例如,使用1.2.1节创建的工程,修改pom.xml文件,使用如代码清单1-1所示的简单Maven配置,基本上就能为一个使用Spring Boot开发框架的Web项目开发提供所需的相关依赖。

代码清单1-1 Spring Boot Web基本依赖配置

<?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>springboot.example</groupId>

    <artifactId>spring-boot-hello</artifactId>

    <version>1.0-SNAPSHOT</version>


    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>1.3.2.RELEASE</version>

    </parent>


    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

    </dependencies>

</project>

这里只使用了一个依赖配置spring-boot-starter-web和一个parent配置spring-boot-starter-parent,在工程的外部库(External Libraries)列表中,它自动引入的依赖包如代码清单1-2所示。

代码清单1-2 Maven加载的依赖列表

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.1.3" level="project" />

<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.1.3" level="project" />

<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.13" level="project" />

<orderEntry type="library" name="Maven: org.slf4j:jcl-over-slf4j:1.7.13" level="project" />

<orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.13" level="project" />

<orderEntry type="library" name="Maven: org.slf4j:log4j-over-slf4j:1.7.13" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-core:4.2.4.RELEASE" level="project" />

<orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.16" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-tomcat:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-core:8.0.30" level="project" />

<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-el:8.0.30" level="project" />

<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-logging-juli:8.0.30" level="project" />

<orderEntry type="library" name="Maven: org.apache.tomcat.embed:tomcat-embed-websocket:8.0.30" level="project" />

<orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-validation:1.3.2.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.hibernate:hibernate-validator:5.2.2.Final" level="project" />

<orderEntry type="library" name="Maven: javax.validation:validation-api:1.1.0.Final" level="project" />

<orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.0.Final" level="project" />

<orderEntry type="library" name="Maven: com.fasterxml:classmate:1.1.0" level="project" />

<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.6.5" level="project" />

<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.6.5" level="project" />

<orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.6.5" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-web:4.2.4.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-aop:4.2.4.RELEASE" level="project" />

<orderEntry type="library" name="Maven: aopalliance:aopalliance:1.0" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-beans:4.2.4.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-context:4.2.4.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-webmvc:4.2.4.RELEASE" level="project" />

<orderEntry type="library" name="Maven: org.springframework:spring-expression:4.2.4.RELEASE" level="project" />

在工程的外部库列表中,Spring Boot已经导入了整个springframework依赖,以及autoconf?igure、logging、slf4j、jackson、tomcat插件等,所有这些都是一个Web项目可能需要用到的东西(包括你已经考虑到的和没有考虑到的),它真是一个聪明的助手。

1.3.2 一个简单的实例

Spring Boot的官方文档中提供了一个最简单的Web实例程序,这个实例只使用了几行代码,如代码清单1-3所示。虽然简单,但实际上这已经可以算作是一个完整的Web项目了。

代码清单1-3 Spring Boot简单实例

package springboot.example;


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;


@SpringBootApplication

@RestController

public class Application {

    @RequestMapping("/")

    String home() {

        return "hello";

    }


    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);

    }

}

这个简单实例,首先是一个Spring Boot应用的程序入口,或者叫作主程序,其中使用了一个注解@SpringBootApplication来标注它是一个Spring Boot应用,main方法使它成为一个主程序,将在应用启动时首先被执行。其次,注解@RestController同时标注这个程序还是一个控制器,如果在浏览器中访问应用的根目录,它将调用home方法,并输出字符串:hello。

1.4 运行与发布

本章实例工程的完整代码可以使用IDEA直接从GitHub的https://github.com/chen-fromsz/spring-boot-hello.git中检出,如图1-15所示,单击Clone按钮将整个项目复制到本地。


图1-15 检出实例工程

1.4.1 在IDEA环境中运行

在IDEA中打开Run菜单,选择Edit Conf?iguration打开Run/Debug Conf?igurations对话框,在配置界面的左边侧边栏中选择增加一个Application或Spring Boot配置项目,然后在工作目录中选择工程所在的根目录,主程序选择代码清单1-3创建的类:springboot.example.Application,并将配置保存为hello,如图1-16所示。

然后选择Run或Debug运行hello配置项目。如果启动成功,将在控制台中输出类似如下信息:

"D:\Program Files\Java\jdk1.8.0_25\bin\java"

.....

 :: Spring Boot ::        (v1.3.2.RELEASE)

......

Starting Servlet Engine: Apache Tomcat/8.0.30

......

Tomcat started on port(s): 8080 (http)

......

从上面的输出中可以看出,Tomcat默认开启了8080端口。要访问这个应用提供的服务,可以在浏览器的地址栏中输入http://localhost:8080/。这样就可以看到我们期望的输出字符:hello。


图1-16 Spring Boot应用配置

1.4.2 将应用打包发布

上面操作演示了在IDEA环境中如何运行一个应用。如果我们想把应用发布出去,需要怎么做呢?可以将代码清单1-1中的Maven配置增加一个发布插件来实现。如代码清单1-4所示,增加了一个打包插件:spring-boot-maven-plugin,并增加了一行打包的配置:<packaging>jar</packaging>,这行配置指定将应用工程打包成jar文件。

代码清单1-4 包含打包插件的Maven配置

<?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>springboot.example</groupId>

    <artifactId>spring-boot-hello</artifactId>

    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>


    <parent>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-parent</artifactId>

        <version>1.3.2.RELEASE</version>

    </parent>


    <dependencies>

        <dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-web</artifactId>

        </dependency>

    </dependencies>

    

    <build>

        <plugins>

            <plugin>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-maven-plugin</artifactId>

                <executions>

                    <execution>

                        <goals>

                            <goal>repackage</goal>

                        </goals>

                    </execution>

                </executions>

            </plugin>

        </plugins>

    </build>

</project>

这样就可以在IDEA中增加一个打包的配置,打开Run/Debug Conf?igurations对话框,选择增加配置一个Maven打包项目,在工作目录中选择工程所在根目录,在命令行中输入package,并将配置保存为mvn,如图1-17所示。

运行mvn打包项目,就可以将实例工程打包,打包的文件将输出在工程的target目录中。

如果已经按照1.1.3节的说明安装了Maven,也可以直接使用Maven的命令打包。打开一个命令行窗口,将路径切换到工程根目录中,直接在命令行输入mvn package,同样也能将项目打包成jar文件。执行结果如下:


图1-17 Maven打包配置

......

[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ spring-boot-hello ---

[INFO] Building jar: E:\ideworkspace\spring-boot-hello\target\spring-boot-hello-

1.0-SNAPSHOT.jar

[INFO]

[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ spring-b

oot-hello ---

[INFO] ------------------------------------------------------------------------

[INFO] BUILD SUCCESS

[INFO] ------------------------------------------------------------------------

[INFO] Total time: 21.450 s

[INFO] Finished at: 2016-05-08T16:54:44+08:00

[INFO] Final Memory: 23M/118M

[INFO] ------------------------------------------------------------------------

打包成功后,在工程的target目录中将会生成jar文件spring-boot-hello-1.0-SNAPSHOT.jar。在命令行窗口中切换到target目录中,运行如下指令,就能启动应用。

java -jar spring-boot-hello-1.0-SNAPSHOT.jar

如果希望按照传统的做法,将工程发布成war文件,应当将代码清单1-4的Maven配置<packaging>jar</packaging>改成<packaging>war</packaging>,这样就可以打包成war文件。打包完成后将war文件放置在Tomcat的webapp路径中,启动Tomcat就能自动运行程序。

这里需要注意的是,如果自主使用Tomcat运行应用,在安装JDK时必须配置JAVA_HOME环境变量,同时JDK要求1.8以上的版本,Tomcat必须是8.0以上的版本。

我更加喜欢打包成jar,然后使用Spring Boot的嵌入插件Tomcat运行应用。本书所有实例都可以打包成jar直接运行。即使对于一个包含很多页面、图片、脚本等资源的复杂应用系统,这种方法也是可行的,并且打包成jar,更方便项目发布在Docker上运行,这些将在后面的章节中详细介绍。

1.5 关于Spring Boot配置

关于Spring Boot配置,可以在工程的resources文件夹中创建一个application.properties或application.yml文件,这个文件会被发布在classpath中,并且被Spring Boot自动读取。这里推荐使用application.yml文件,因为它提供了结构化及其嵌套的格式,例如,可以按如下所示配置上面的工程,将默认端口改为80,并且将Tomcat的字符集定义为UTF-8。

server:

    port: 80

    tomcat:

        uri-encoding: UTF-8

如果要使用application.properties文件,上面的配置就要改成如下所示的样子,其结果完全相同。

server.port = 80

server.tomcat.uri-enconding = UTF-8

使用这个配置文件可以直接使用Spring Boot预定义的一些配置参数,关于其他配置参数的详细说明和描述可以查看官方的文档说明:https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html。在后面的开发中将在用得到的地方选择使用这些预定义的配置参数。即使没有预定义的配置参数可用,也能很容易地按照应用的需要自定义一些配置参数,这将在后续的章节中详细介绍。

1.6 小结

本章主要介绍了Spring Boot开发环境的搭建,以及一些开发工具的安装配置,内容难免有点枯燥。然后创建并运行一个非常简单的实例工程,让性急的读者一睹Spring Boot的芳容。

本章实例工程只是使用Spring Boot框架进行开发的非常简单的入门指引。因为Spring Boot开发框架是一个非常轻量级的开发框架,所以也有人把它叫作微框架,从入门指引中可以看出,使用Spring Boot框架开发应用不但入门容易,而且其蕴藏的无比强大的功能,使开发过程也变得更加容易。

下面,让我们使用Spring Boot框架进行一些更加有趣的开发吧。这一章只是小试牛刀而已,在后续章节中将使用Spring Boot框架来开始一些真正的开发。







第2章

在Spring Boot中使用数据库

使用数据库是开发基本应用的基础。借助于开发框架,我们已经不用编写原始的访问数据库的代码,也不用调用JDBC(Java Data Base Connectivity)或者连接池等诸如此类的被称作底层的代码,我们将在高级的层次上访问数据库。而Spring Boot更是突破了以前所有开发框架访问数据库的方法,在前所未有的更加高级的层次上访问数据库。因为Spring Boot包含一个功能强大的资源库,为使用Spring Boot的开发者提供了更加简便的接口进行访问。

本章将介绍怎样使用传统的关系型数据库,以及近期一段时间异军突起的NoSQL(Not Only SQL)数据库。

本章的实例工程使用了分模块的方式构建,各模块的定义如表2-1所示。

表2-1 实例工程模块定义

项  目 工  程 功  能

MySQL模块 mysql 使用MySQL

Redis模块 redis 使用Redis

MongoDB模块 mongodb 使用MongoDB

Neo4j模块 neo4j 使用Neo4j


2.1 使用MySQL

对于传统关系型数据库来说,Spring Boot使用JPA(Java Persistence API)资源库来实现对数据库的操作,使用MySQL也是如此。简单地说,JPA就是为POJO(Plain Ordinary Java Object)提供持久化的标准规范,即将Java的普通对象通过对象关系映射(Object-Relational Mapping,ORM)持久化到数据库中。

2.1.1 MySQL依赖配置

为了使用JPA和MySQL,首先在工程中引入它们的Maven依赖,如代码清单2-1所示。其中,指定了在运行时调用MySQL的依赖。

代码清单2-1 JPA和Mysql依赖配置

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-jpa</artifactId>

    </dependency>

    <dependency>

        <groupId>mysql</groupId>

        <artifactId>mysql-connector-java</artifactId>

        <scope>runtime</scope>

    </dependency>

</dependencies>

2.1.2 实体建模

首先创建一些普通对象,用来与数据库的表建立映射关系,接着演示如何使用JPA对数据库进行增删查改等存取操作。

假如现在有三个实体:部门、用户和角色,并且它们具有一定的关系,即一个用户只能隶属于一个部门,一个用户可以拥有多个角色。它们的关系模型如图2-1所示。


图2-1 MySQL实体-关系模型示例

Spring Boot的实体建模与使用Spring框架时的定义方法一样,同样比较方便的是使用了注解的方式来实现。

部门实体的建模如代码清单2-2所示,其中注解@Table指定关联的数据库的表名,注解@Id定义一条记录的唯一标识,并结合注解@GeneratedValue将其设置为自动生成。部门实体只有两个字段:id和name。程序中省略了Getter和Setter方法的定义,这些方法可以使用IDEA的自动生成工具很方便地生成。

代码清单2-2 部门实体建模

@Entity

@Table(name = "deparment")

public class Deparment {

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String name;


    public Deparment() {

    }

        ……

}

用户实体包含三个字段:id、name和createdate,用户实体建模如代码清单2-3所示。其中注解@ManyToOne定义它与部门的多对一关系,并且在数据库表中用字段did来表示部门的ID,注解@ManyToMany定义与角色实体的多对多关系,并且用中间表user_role来存储它们各自的ID,以表示它们的对应关系。日期类型的数据必须使用注解@DateTimeFormat来进行格式化,以保证它在存取时能提供正确的格式,避免保存失败。注解@JsonBackReference用来防止关系对象的递归访问。

代码清单2-3 用户实体建模

@Entity

@Table(name = "user")

public class User implements java.io.Serializable{

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String name;

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

    private Date createdate;


    @ManyToOne

    @JoinColumn(name = "did")

    @JsonBackReference

    private Department deparment;


    @ManyToMany(cascade = {}, fetch = FetchType.EAGER)

    @JoinTable(name = "user_role",

            joinColumns = {@JoinColumn(name = "user_id")},

            inverseJoinColumns = {@JoinColumn(name = "roles_id")})

    private List<Role> roles;


    public User() {

    }

……

角色实体建模比较简单,只要按设计的要求,定义id和name字段即可,当然同样必须保证id的唯一性并将其设定为自动生成。角色实体的建模如代码清单2-4所示。

代码清单2-4 角色实体建模

@Entity

@Table(name = "role")

public class Role implements java.io.Serializable{

    @Id

    @GeneratedValue(strategy = GenerationType.IDENTITY)

    private Long id;

    private String name;


    public Role() {

    }

……

2.1.3 实体持久化

通过上面三个实体的定义,实现了使用Java的普通对象(POJO)与数据库表建立映射关系(ORM),接下来使用JPA来实现持久化。

用户实体使用JPA进行持久化的例子如代码清单2-5所示。它是一个接口,并继承于JPA资源库JpaRepository接口,使用注解@Repository将这个接口也定义为一个资源库,使它能被其他程序引用,并为其他程序提供存取数据库的功能。

使用相同的方法,可以定义部门实体和角色实体的资源库接口。接口同样继承于JpaRepository接口,只要注意使用的参数是各自的实体对象即可。

代码清单2-5 用户实体持久化

@Repository

public interface UserRepository extends JpaRepository<User, Long> {

}

这样就实现存取数据库的功能了。现在可以对数据库进行增删查改、进行分页查询和指定排序的字段等操作。

或许你还有疑问,我们定义的实体资源库接口并没有声明一个方法,也没有对接口有任何实现的代码,甚至连一条SQL查询语句都没有写,这怎么可能?

是的,使用JPA就是可以这么简单。我们来看看JpaRe-pository的继承关系,你也许会明白一些。如图2-2所示,JpaRepository继承于PagingAndSortingRepository,它提供了分页和排序功能,PagingAndSortingRepository继承于Crud-Repository,它提供了简单的增删查改功能。

因为定义的接口继承于JpaRepository,所以它传递性地继承上面所有这些接口,并拥有这些接口的所有方法,这样就不难理解为何它包含那么多功能了。这些接口提供的一些方法如下:

<S extends T> S save(S var1);

T findOne(ID var1);

long count();

void delete(ID var1);

void delete(T var1);

void deleteAll();

Page<T> findAll(Pageable var1);

List<T> findAll();

List<T> findAll(Sort var1);

List<T> findAll(Iterable<ID> var1);

void deleteAllInBatch();

T getOne(ID var1);

......

JPA还提供了一些自定义声明方法的规则,例如,在接口中使用关键字f?indBy、readBy、getBy作为方法名的前缀,拼接实体类中的属性字段(首个字母大写),并可选择拼接一些SQL查询关键字来组合成一个查询方法。例如,对于用户实体,下列查询关键字可以这样使用:

And,例如f?indByIdAndName(Long id,String name);

Or,例如f?indByIdOrName(Long id,String name);

Between,例如f?indByCreatedateBetween(Date start,Date end);

LessThan,例如f?indByCreatedateLessThan(Date start);

GreaterThan,例如f?indByCreatedateGreaterThan(Date start);

IsNull,例如f?indByNameIsNull();

IsNotNull,例如f?indByNameIsNotNull();

NotNull,与IsNotNull等价;

Like,例如f?indByNameLike(String name);

NotLike,例如f?indByNameNotLike(String name);

OrderBy,例如f?indByNameOrderByIdAsc(String name);

Not,例如f?indByNameNot(String name);

In,例如f?indByNameIn(Collection<String>nameList);

NotIn,例如f?indByNameNotIn(Collection<String>nameList)。

又如下列对用户实体类自定义的方法声明,它们都是符合JPA规则的,这些方法也不用实现,JPA将会代理实现这些方法。

User findByNameLike(String name);

User readByName(String name);

List<User> getByCreatedateLessThan(Date star);

2.1.4 MySQL测试

现在,为了验证上面设计的正确性,我们用一个实例来测试一下。

首先,增加一个使用JPA的配置类,如代码清单2-6所示。其中@EnableTransac-tionManagement启用了JPA的事务管理;@EnableJpaRepositories启用了JPA资源库并指定了上面定义的接口资源库的位置;@EntityScan指定了定义实体的位置,它将导入我们定义的实体。注意,在测试时使用的JPA配置类可能与这个配置略有不同,这个配置的一些配置参数是从配置文件中读取的,而测试时使用的配置类把一些配置参数都包含在类定义中了。

代码清单2-6 JPA配置类

@Order(Ordered.HIGHEST_PRECEDENCE)

@Configuration

@EnableTransactionManagement(proxyTargetClass = true)

@EnableJpaRepositories(basePackages = "dbdemo.**.repository")

@EntityScan(basePackages = "dbdemo.**.entity")

public class JpaConfiguration {


    @Bean

    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){

        return new PersistenceExceptionTranslationPostProcessor();

    }


}

其次,在MySQL数据库服务器中创建一个数据库test,然后配置一个可以访问这个数据库的用户及其密码。数据库的表结构可以不用创建,在程序运行时将会按照实体的定义自动创建。如果还没有创建一个具有完全权限访问数据库test的用户,可以在连接MySQL服务器的查询窗口中执行下面指令,这个指令假设你将在本地中访问数据库。

grant all privileges on test.* to 'root'@'localhost' identified by '12345678';

然后,在Spring Boot的配置文件application.yml中使用如代码清单2-7所示的配置,用来设置数据源和JPA的工作模式。

代码清单2-7 数据源和JPA配置

spring:

    datasource:

        url: jdbc:mysql:// localhost:3306/test?characterEncoding=utf8

        username: root

        password: 12345678

    jpa:

        database: MYSQL

        show-sql: true

    #Hibernate ddl auto (validate|create|create-drop|update)

        hibernate:

            ddl-auto: update

            naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy

        properties:

            hibernate:

                dialect: org.hibernate.dialect.MySQL5Dialect

配置中将ddl-atuo设置为update,就是使用Hibernate来自动更新表结构的,即如果数据表不存在则创建,或者如果修改了表结构,在程序启动时则执行表结构的同步更新。

最后,编写一个测试程序,如代码清单2-8所示。测试程序首先初始化数据库,创建一个部门,命名为“开发部”,创建一个角色,命名为admin,创建一个用户,命名为user,同时将它的所属部门设定为上面创建的部门,并将现有的所有角色都分配给这个用户。然后使用分页的方式查询所有用户的列表,并从查到的用户列表中,打印出用户的名称、部门的名称和第一个角色的名称等信息。

代码清单2-8 MySQL测试程序

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = {JpaConfiguration.class})

public class MysqlTest {

    private static Logger logger = LoggerFactory.getLogger(MysqlTest.class);


    @Autowired

    UserRepository userRepository;

    @Autowired

    DepartmentRepository departmentRepository;

    @Autowired

    RoleRepository roleRepository;


    @Before

    public void initData(){

        userRepository.deleteAll();

        roleRepository.deleteAll();

        departmentRepository.deleteAll();


        Department department = new Department();

        department.setName("开发部");

        departmentRepository.save(department);

        Assert.notNull(department.getId());


        Role role = new Role();

        role.setName("admin");

        roleRepository.save(role);

        Assert.notNull(role.getId());


        User user = new User();

        user.setName("user");

        user.setCreatedate(new Date());

        user.setDeparment(department);


        List<Role> roles = roleRepository.findAll();

        Assert.notNull(roles);

        user.setRoles(roles);


        userRepository.save(user);

        Assert.notNull(user.getId());

    }


    @Test

    public void findPage(){

        Pageable pageable = new PageRequest(0, 10, new Sort(Sort.Direction.ASC, 

"id"));

        Page<User> page = userRepository.findAll(pageable);

        Assert.notNull(page);

        for(User user : page.getContent()) {

            logger.info("====user==== user name:{}, department name:{}, role 

name:{}",

                user.getName(), user.getDeparment().getName(), user.getRoles().

get(0).getName());

        }

    }

}

好了,现在可以使用JUnit来运行这个测试程序了,在IDEA的Run/Debug Conf?iguration配置中增加一个JUint配置项,模块选择mysql,工作目录选择模块所在的根目录,程序选择dbdemo.mysql.test.MysqlTest,并将配置项目名称保存为mysqltest,如图2-3所示。

用Debug方式运行测试配置项目mysqltest,可以在控制台中看到执行的过程和结果。如果状态栏中显示为绿色,并且提示“All Tests passed”,则表示测试全部通过。在控制台中也可以查到下列打印信息:

dbdemo.mysql.test.MysqlTest - ====user==== user name:user, department name:开发部, role name:admin

这时如果在MySQL服务器中查看数据库test,不但可以看到表结构都已经创建了,还可以看到上面测试生成的一些数据。

这是不是很激动人心?在Spring Boot使用数据库,就是可以如此简单和有趣。到目前为止,我们不仅没有写过一条查询语句,也没有实现一个访问数据库的方法,但是已经能对数据库执行所有的操作,包括一般的增删查改和分页查询。


图2-3 JUint测试配置

2.2 使用Redis

关系型数据库在性能上总是存在一些这样那样的缺陷,所以大家有时候在使用传统关系型数据库时,会与具有高效存取功能的缓存系统结合使用,以提高系统的访问性能。在很多流行的缓存系统中,Redis是一个不错的选择。Redis是一种可以持久存储的缓存系统,是一个高性能的key-value数据库,它使用键-值对的方式来存储数据。

2.2.1 Redis依赖配置

需要使用Redis,可在工程的Maven配置中加入spring-boot-starter-redis依赖,如代码清单2-9所示。其中gson是用来转换Json数据格式的工具,mysql是引用了上一节的模块,这里使用2.1节定义的实体对象来存取数据,演示在Redis中的存取操作。

代码清单2-9 Redis模块的Maven依赖配置

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-redis</artifactId>

    </dependency>

    <dependency>

        <groupId>com.google.code.gson</groupId>

        <artifactId>gson</artifactId>

        <version>2.2.4</version>

    </dependency>

    <dependency>

        <groupId>springboot.db</groupId>

        <artifactId>mysql</artifactId>

        <version>${project.version}</version>

    </dependency>

</dependencies>

2.2.2 创建Redis服务类

Redis提供了下列几种数据类型可供存取:

string;

hash;

list;

set及zset。

在实例中,将使用string即字符串的类型来演示数据的存取操作。对于Redis,Spring Boot没有提供像JPA那样相应的资源库接口,所以只能仿照上一节中Repository的定义编写一个实体User的服务类,如代码清单2-10所示。这个服务类可以存取对象User以及由User组成的列表List,同时还提供了一个删除的方法。所有这些方法都是使用RedisTemplate来实现的。

代码清单2-10 用户实体的Redis服务类

@Repository

public class UserRedis {

    @Autowired

    private RedisTemplate<String, String> redisTemplate;


    public void add(String key, Long time,User user) {

        Gson gson = new Gson();

        redisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit.

MINUTES);

    }


    public void add(String key, Long time, List<User> users) {

        Gson gson = new Gson();

        redisTemplate.opsForValue().set(key, gson.toJson(users), time, TimeUnit.

MINUTES);

    }


    public User get(String key) {

        Gson gson = new Gson();

        User user = null;

        String userJson = redisTemplate.opsForValue().get(key);

        if(!StringUtils.isEmpty(userJson))

            user = gson.fromJson(userJson, User.class);

        return user;

    }


    public List<User> getList(String key) {

        Gson gson = new Gson();

        List<User> ts = null;

        String listJson = redisTemplate.opsForValue().get(key);

        if(!StringUtils.isEmpty(listJson))

            ts = gson.fromJson(listJson, new TypeToken<List<User>>(){}.getType());

        return ts;

    }


    public void delete(String key){

        redisTemplate.opsForValue().getOperations().delete(key);

    }

}

Redis没有表结构的概念,所以要实现MySQL数据库中表的数据(即普通Java对象映射的实体数据)在Redis中存取,必须做一些转换,使用JSON格式的文本作为Redis与Java普通对象互相交换数据的存储格式。这里使用Gson工具将类对象转换为JSON格式的文本进行存储,要取出数据时,再将JSON文本数据转化为Java对象。

因为Redis使用了key-value的方式存储数据,所以存入时要生成一个唯一的key,而要查询或者删除数据时,就可以使用这个唯一的key进行相应的操作。

保存在Redis数据库中的数据默认是永久存储的,可以指定一个时限来确定数据的生命周期,超过指定时限的数据将被Redis自动清除。在代码清单2-10中我们以分钟为单位设定了数据的存储期限。

另外,为了能正确调用RedisTemplate,必须对其进行一些初始化工作,即主要对它存取的字符串进行一个JSON格式的系列化初始配置,如代码清单2-11所示。

代码清单2-11 RedisTemplate初始化

@Configuration

public class RedisConfig {


    @Bean

    public RedisTemplate<String, String> redisTemplate(

            RedisConnectionFactory factory) {

        StringRedisTemplate template = new StringRedisTemplate(factory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper om = new ObjectMapper();

        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        template.setValueSerializer(jackson2JsonRedisSerializer);

        template.afterPropertiesSet();

        return template;

    }


}

2.2.3 Redis测试

如果还没有安装Redis服务器,可以参照本书附录C提供的方法安装,然后在工程的配置文件application.yml中配置连接Redis服务器等参数,如代码清单2-12所示。其中host和port分别表示Redis数据库服务器的IP地址和开放端口,database可以不用指定,由Redis根据存储情况自动选定(注:测试时这些配置是集成在一个配置类中实现的)。

代码清单2-12 Redis配置

spring:

    redis:

    # database: 1

        host: 192.168.1.214

        port: 6379

        pool:

            max-idle: 8

            min-idle: 0

            max-active: 8

            max-wait: -1

现在编写一个JUint测试程序,来演示如何在Redis服务器中存取数据,如代码清单2-13所示。测试程序创建一个部门对象并将其命名为“开发部”,创建一个角色对象并把它命名为admin,创建一个用户对象并把它命名为user,同时设定这个用户属于“开发部”,并把admin这个角色分配给这个用户。接着测试程序使用类名等参数生成一个key,并使用这个key清空原来的数据,然后用这个key存储现在这个用户的数据,最后使用这个key查询用户,并将查到的信息打印出来。

代码清单2-13 Redis测试程序

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = {RedisConfig.class, UserRedis.class})

public class RedisTest {

    private static Logger logger = LoggerFactory.getLogger(RedisTest.class);


    @Autowired

    UserRedis userRedis;


    @Before

    public void setup(){

        Deparment deparment = new Deparment();

        deparment.setName("开发部");


        Role role = new Role();

        role.setName("admin");


        User user = new User();

        user.setName("user");

        user.setCreatedate(new Date());

        user.setDeparment(deparment);


        List<Role> roles = new ArrayList<>();

        roles.add(role);


        user.setRoles(roles);


        userRedis.delete(this.getClass().getName()+":userByname:"+user.getName());

        userRedis.add(this.getClass().getName()+":userByname:"+user.getName(), 10L, user);


    }


    @Test

    public void get(){

        User user = userRedis.get(this.getClass().getName()+":userByname:

user");

        Assert.notNull(user);

        logger.info("======user====== name:{}, deparment:{}, role:{}",

            user.getName(), user.getDeparment().getName(), user.getRoles().get(0).

getName());

    }

}

要运行这个测试程序,可以在IDEA的Run/Debug Conf?iguration配置中增加一个JUint配置项目,模块选择redis,工作目录选择模块所在的根目录,类选择这个测试程序即dbdemo.redis.test.RedisTest,并将配置保存为redistest。

使用Debug方式运行测试项目redistest。如果测试通过,会输出一个用户的用户名、所属部门和拥有角色等简要信息,如下所示:

dbdemo.redis.test.RedisTest - ======user====== name:user, deparment:开发部, role:admin

对于Redis的使用,还可以将注解方式与调用数据库的方法相结合,那样就不用再编写像上面那样的服务类,并且使用起来更加简单,这将在后面的章节中介绍。

2.3 使用MongoDB

在当前流行的NoSQL数据库中,MongoDB是大家接触比较早而且用得比较多的数据库。MongoDB是文档型的NoSQL数据库,具有大数据量、高并发等优势,但缺点是不能建立实体关系,而且也没有事务管理机制。

2.3.1 MongoDB依赖配置

在Spring Boot中使用MongoDB也像使用JPA一样容易,并且同样拥有功能完善的资源库。同样的,要使用MongoDB,首先必须在工程的Maven中引入它的依赖,如代码清单2-14所示。除了MongoDB本身的依赖之外,还需要一些附加的工具配套使用。

代码清单2-14 使用MongoDB的Maven依赖配置

<dependencies>

    <dependency>

        <groupId>org.springframework.data</groupId>

        <artifactId>spring-data-mongodb</artifactId>

    </dependency>

    <dependency>

        <groupId>org.pegdown</groupId>

        <artifactId>pegdown</artifactId>

        <version>1.4.1</version>

    </dependency>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-hateoas</artifactId>

    </dependency>

    <dependency>

        <groupId>com.fasterxml.jackson.core</groupId>

        <artifactId>jackson-annotations</artifactId>

    </dependency>

</dependencies>

2.3.2 文档建模

MongoDB是文档型数据库,使用MongoDB也可以像使用关系型数据库那样为文档建模。如代码清单2-15所示,为用户文档建模,它具有用户名、密码、用户名称、邮箱和注册日期等字段,有一个用来保存用户角色的数据集,还定义了一个构造函数,可以很方便地用来创建一个用户实例。

代码清单2-15 用户文档建模

@Document(collection = "user")

public class User {

    @Id

    private String userId;

    @NotNull @Indexed(unique = true)

    private String username;

    @NotNull

    private String password;

    @NotNull

    private String name;

    @NotNull

    private String email;

    @NotNull

    private Date registrationDate = new Date();

    private Set<String> roles = new HashSet<>();


    public User() { }


    @PersistenceConstructor

    public User(String userId, String username, String password, String name, String email,

            Date registrationDate, Set<String> roles) {

        this.userId = userId;

        this.username = username;

        this.password = password;

        this.name = name;

        this.email = email;

        this.registrationDate = registrationDate;

        this.roles = roles;


    }

……

2.3.3 文档持久化

MongoDB也有像使用JPA那样的资源库,如代码清单2-16所示,为用户文档创建了一个Repository接口,继承于MongoRepository,实现了文档持久化。

代码清单2-16 用户文档持久化

public interface UserRepository extends MongoRepository<User, String> {

    User findByUsername(String username);

}

MongoRepository的继承关系如图2-4所示,看起来跟JPA的资源库的继承关系没有什么两样,它也包含访问数据库的丰富功能。

代码清单2-17是用在测试中的使用MongoDB的一个配置类定义,其中@PropertySource指定读取数据库配置文件的位置和名称,@EnableMongoRepositories启用资源库并设定定义资源库接口放置的位置,这里使用环境变量Environment来读取配置文件的一些数据库配置参数,然后使用一个数据库客户端,连接MongoDB服务器。

代码清单2-17 TestDataSourceConfig配置类

@Configuration

@EnableMongoRepositories(basePackages = "dbdemo.mongo.repositories")

@PropertySource("classpath:test.properties")

public class TestDataSourceConfig extends AbstractMongoConfiguration {


    @Autowired private Environment env;


    @Override

    public String getDatabaseName(){

        return env.getRequiredProperty("mongo.name");

    }


    @Override

    @Bean

    public Mongo mongo() throws Exception {

        ServerAddress serverAddress = new ServerAddress(env.getRequiredProperty

("mongo.host"));

        List<MongoCredential> credentials = new ArrayList<>();

        return new MongoClient(serverAddress, credentials);

    }


}

2.3.4 MongoDB测试

如果还没有安装MongoDB服务器,可以参照附录B的方法安装并启动一个MongoDB服务器。然后,使用如代码清单2-18所示的配置方法配置连接服务器的一些参数,该配置假定你的MongoDB服务器安装在本地,并使用默认的数据库端口:27017。

代码清单2-18 MongoDB数据库配置

# MongoDB

mongo.host=localhost

mongo.name=test

mongo.port=27017

这样就可以编写一个JUint测试例子来测试UserRepository接口的使用情况,如代码清单2-19所示。测试例子首先使用用户文档类创建一个用户对象实例,然后使用资源库接口调用save方法将用户对象保存到数据库中,最后使用f?indAll方法查询所有用户的列表,并使用一个循环输出用户的简要信息。

代码清单2-19 MongoDB测试

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = {TestDataSourceConfig.class})

@FixMethodOrder

public class RepositoryTests {

    private static Logger logger = LoggerFactory.getLogger(RepositoryTests.class);


    @SuppressWarnings("SpringJavaAutowiringInspection") @Autowired

    UserRepository userRepository;


    @Before

    public void setup(){

        Set<String> roles = new HashSet<>();

        roles.add("manage");

        User user = new User("1","user","12345678","name","email@com.cn",new Date(), 

roles);

        userRepository.save(user);

    }


    @Test

    public void findAll(){

        List<User> users = userRepository.findAll();

        Assert.notNull(users);

        for(User user : users){

            logger.info("===user=== userid:{}, username:{}, pass:{}, registra

tionDate:{}",

                user.getUserId(), user.getName(), user.getPassword(), user.

getRegistrationDate());

        }

    }

}

现在可以在IDEA的Run/Debug Conf?iguration配置中增加一个JUint测试项目,模块选择mongodb,工作目录选择模块所在的工程根目录,类选择上面编写的测试例子,即dbdemo.mongo.test.RepositoryTests,并将配置保存为mongotest。

使用Debug方式运行测试项目mongotest。如果通过测试,将输出查到的用户的简要信息,如下所示:

dbdemo.mongo.test.RepositoryTests - ===user=== userid:1, username:name, pass:12345678, registrationDate:Tue Jun 07 14:26:02 CST 2016

这时使用MongoDB数据库客户端输入下面的查询指令,也可以查到这条文档的详细信息,这是一条JSON结构的文本信息。

> db.user.find()

{ "_id" : "1", "_class" : "dbdemo.mongo.models.User", "username" : "user", "password" : "12345678", "name" : "name", "email" : "email@com.cn", "registrationDate" : ISODate("2016-04-13T06:27:02.423Z"), "roles" : [ "manage" ] }

2.4 使用Neo4j

有没有既具有传统关系型数据库的优点,又具备NoSQL数据库优势的一种数据库呢?Neo4j就是一种这样的数据库。Neo4j是一个高性能的NoSQL图数据库,并且具备完全事务特性。Neo4j将结构化数据存储在一张图上,图中每一个节点的属性表示数据的内容,每一条有向边表示数据的关系。Neo4j没有表结构的概念,它的数据用节点的属性来表示。

2.4.1 Neo4j依赖配置

在Spring Boot中使用Neo4j非常容易,因为有spring-data-neo4j提供了强大的支持。首先,在工程的Maven管理中引入Neo4j的相关依赖,如代码清单2-20所示。

代码清单2-20 使用Neo4j的Maven依赖配置

<dependencies>

    <dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-data-rest</artifactId>

    </dependency>

    <dependency>

        <groupId>org.springframework.data</groupId>

        <artifactId>spring-data-neo4j</artifactId>

        <version>4.0.0.RELEASE</version>

    </dependency>

    <dependency>

        <groupId>com.voodoodyne.jackson.jsog</groupId>

        <artifactId>jackson-jsog</artifactId>

        <version>1.1</version>

        <scope>compile</scope>

    </dependency>

</dependencies>

2.4.2 节点和关系实体建模

虽然Neo4j没有表结构的概念,但它有节点和关系的概念。例如,现在有演员和电影两个实体,它们的关系表现为一个演员在一部电影中扮演一个角色。那么就可以创建演员和电影两个节点实体,和一个角色关系实体。它们的实体-关系模型如图2-5所示。这个实体-关系模型的定义比起关系型数据库的实体-关系模型的定义要简单得多,但是它更加形象和贴切地表现了实体之间的关系。更难能可贵的是,这个实体-关系模型是可以不经过任何转换而直接存入数据库的,也就是说,在Neo4j图数据库中保存的数据与图2-5所示的相同,它仍然是一张图。这对于业务人员和数据库设计人员来说,它的意义相同。所以使用Neo4j数据库,将在很大程度上减轻了设计工作和沟通成本。

像JPA使用了ORM一样,Neo4j使用了对象-图形映射(Object-Graph Mapping,OGM)的方式来建模。代码清单2-21是演员节点实体建模,使用注解@JsonIdentityInfo是防止查询数据时引发递归访问效应,注解@NodeEntity标志这个类是一个节点实体,注解@GraphId定义了节点的一个唯一性标识,它将在创建节点时由系统自动生成,所以它是不可缺少的。这个节点预定义了其他两个属性,name和born。节点的属性可以随需要增加或减少,这并不影响节点的使用。

代码清单2-21 演员节点实体建模

@JsonIdentityInfo(generator=JSOGGenerator.class)

@NodeEntity

public class Actor {

    @GraphId Long id;

    private String name;

    private int born;


    public Actor() { }

……

代码清单2-22是电影节点实体建模,注解@Relationship表示List<Role>是一个关系列表,其中type设定了关系的类型,direction设定这个关系的方向,Relationship.INCOMING表示以这个节点为终点。addRole定义了增加一个关系的方法。

代码清单2-22 电影节点实体建模

@JsonIdentityInfo(generator=JSOGGenerator.class)

@NodeEntity

public class Movie {

    @GraphId Long id;

    String title;

    String year;

    String tagline;

    @Relationship(type="ACTS_IN", direction = Relationship.INCOMING)

    List<Role> roles = new ArrayList<>();


    public Role addRole(Actor actor, String name){

        Role role = new Role(actor,this,name);

        this.roles.add(role);

        return role;

    }


    public Movie() { }

……

代码清单2-23是角色的关系实体建模,注解@RelationshipEntity表明这个类是一个关系实体,并用type指定了关系的类型,其中@StartNode指定起始节点的实体,

@EndNode指定终止节点的实体,这说明了图中一条有向边的起点和终点的定义。其中定义了一个创建关系的构造函数Role(Actor actor,Movie movie,String name),这里的name参数用来指定这个关系的属性。

代码清单2-23 角色关系实体建模

@JsonIdentityInfo(generator=JSOGGenerator.class)

@RelationshipEntity(type = "ACTS_IN")

public class Role {

    @GraphId

    Long id;

    String role;

    @StartNode

    Actor actor;

    @EndNode

    Movie movie;


    public Role() {

    }


    public Role(Actor actor, Movie movie, String name) {

        this.actor = actor;

        this.movie = movie;

        this.role = name;

    }

......

2.4.3 节点实体持久化

像对其他数据库的访问和存取等操作一样,spring-data-neo4j提供了功能丰富的资源库可供调用,因此,对于演员和电影节点实体,可以创建它们对应的资源库接口,实现实体的持久化。代码清单2-24是电影资源库接口的定义,它继承于GraphRepository接口,实现了电影实体的持久化。使用相同方法可以对演员的节点实体实现持久化。关系实体却不用实现持久化,当保存节点实体时,节点实体的关系将会同时保存。

代码清单2-24 电影实体持久化

@Repository

public interface MovieRepository extends GraphRepository<Movie> {

    Movie findByTitle(@Param("title") String title);

}

其中GraphRepository接口的继承关系也遵循了Spring Boot资源库定义的规则,即使用与JPA相同的标准规范,所以它同样包含使用数据库的丰富功能,如图2-6所示。

2.4.4 Neo4j测试

代码清单2-24是Neo4j的数据库配置类,其中@Enable-TransactionManagement启用了事务管理,@EnableNeo4jRe-positories启用了Neo4j资源库并指定了我们定义的资源库接口的位置,在重载的SessionFactory函数中设定了定义实体的位置,这将促使定义的实体被作为域对象导入,RemoteServer设定连接Neo4j服务器的URL、用户名和密码,这些参数要依据安装Neo4j服务器的情况来设置。如果还没有安装Neo4j服务器,可参考附录A的方法进行安装,安装完成后启动服务器以备使用。

代码清单2-25 Neo4j配置类

@Configuration

@EnableTransactionManagement

@EnableNeo4jRepositories(basePackages = { "dbdemo.neo4j.repositories" })

public class Neo4jConfig extends Neo4jConfiguration {

    @Override

    public Neo4jServer neo4jServer() {

        return new RemoteServer("http://192.168.1.221:7474","neo4j","12345678");

    }


    @Override

    public SessionFactory getSessionFactory() {

        return new SessionFactory("dbdemo.neo4j.domain");

    }

}

现在可以编写一个测试程序来验证和演示上面编写的代码的功能,如代码清

单2-26所示。这个测试程序分别创建了三部电影和三个演员,以及三个演员在三部电影中各自扮演的角色,然后按照电影标题查出一部电影,按照其内在的关系输出这部电影的信息和每个演员扮演的角色。这些数据的内容参照了Neo4j帮助文档中提供的示例数据。

代码清单2-26 使用Neo4j的JUint测试程序

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(classes = {Neo4jConfig.class})

public class MovieTest {

    private static Logger logger = LoggerFactory.getLogger(MovieTest.class);


    @Autowired

    MovieRepository movieRepository;


    @Before

    public void initData(){

        movieRepository.deleteAll();


        Movie matrix1 = new Movie();

        matrix1.setTitle("The Matrix");

        matrix1.setYear("1999-03-31");


        Movie matrix2 = new Movie();

        matrix2.setTitle("The Matrix Reloaded");

        matrix2.setYear("2003-05-07");


        Movie matrix3 = new Movie();

        matrix3.setTitle("The Matrix Revolutions");

        matrix3.setYear("2003-10-27");


        Actor keanu = new Actor();

        keanu.setName("Keanu Reeves");


        Actor laurence = new Actor();

        laurence.setName("Laurence Fishburne");


        Actor carrieanne = new Actor();

        carrieanne.setName("Carrie-Anne Moss");


        matrix1.addRole(keanu,  "Neo");

        matrix1.addRole(laurence, "Morpheus");

        matrix1.addRole(carrieanne,  "Trinity");

        movieRepository.save(matrix1);

        Assert.notNull(matrix1.getId());


        matrix2.addRole(keanu, "Neo");

        matrix2.addRole(laurence, "Morpheus");

        matrix2.addRole(carrieanne,  "Trinity");

        movieRepository.save(matrix2);

        Assert.notNull(matrix2.getId());


        matrix3.addRole(keanu, "Neo");

        matrix3.addRole(laurence, "Morpheus");

        matrix3.addRole(carrieanne, "Trinity");

        movieRepository.save(matrix3);

        Assert.notNull(matrix3.getId());

    }


    @Test

    public void get(){

        Movie movie = movieRepository.findByTitle("The Matrix");

        Assert.notNull(movie);

        logger.info("===movie=== movie:{}, {}",movie.getTitle(), movie.getYear());

        for(Role role : movie.getRoles()){

            logger.info("====== actor:{}, role:{}", role.getActor().getName(), role.getRole());

        }

    }

}

在IDEA的Run/Debug Conf?iguration配置中增加一个JUint的配置项目,模块选择neo4j,工作目录选择模块所在的根目录,测试程序选择MovieTest这个类,并将配置保存为neo4jtest。

使用Debug模式运行测试项目neo4jtest,如果测试通过,将在控制台中看到输出查询的这部电影和所有演员及其扮演的角色,如下所示:

=== movie=== movie:The Matrix, 1999-03-31

====== actor:Keanu Reeves, role:Neo

====== actor:Laurence Fishburne, role:Morpheus

====== actor:Carrie-Anne Moss, role:Trinity

这时,在数据库客户端的控制台上,单击左面侧边栏的关系类型ACTS_IN,可以看到一个很酷的图形,图中每部电影和每个演员是一个节点,节点的每条有向边代表了这个演员在那部电影中扮演的角色,如图2-7所示。


图2-7 演员和电影的角色关系图

2.5 小结

这一章,我们一口气学习使用了4种数据库:MySQL、Redis、MongoDB、Neo4j,除了Redis以外,都使用了由Spring Boot提供的资源库来访问数据库并对数据库执行了一般的存取操作。可以看出,在Spring Boot框架中使用数据库非常简单、容易,这主要得益于Spring Boot资源库的强大功能,Spring Boot资源库整合了第三方资源,它把复杂的操作变成简单的调用,它把所有“辛苦、繁重的事情”都包揽了,然后将“微笑和鲜花”献给了我们。为此,我们应该说声谢谢,谢谢开发Spring Boot框架及所有第三方提供者的程序员们,因为有了他们辛勤的付出,才有了我们今天使用上的便利。

本章实例的完整代码可以在IDEA中直接从GitHub中检出:https://github.com/chenfromsz/spring-boot-db.git。

本章实例都是使用JUint的方式来验证的,为了能使用友好的界面来运行应用,下一章将介绍如何使用Thymeleaf来进行界面设计。





第3章

Spring Boot界面设计

用Spring Boot框架设计Web显示界面,我们还是使用MVC(Model View Controller,模型-视图-控制器)的概念,将数据管理、事件控制和界面显示进行分层处理,实现多层结构设计。界面设计,即视图的设计,主要是组织和处理显示的内容,界面上的事件响应最终交给了控制器进行处理,由控制器决定是否调用模型进行数据的存取操作,然后再将结果返回给合适的视图显示。

本章的实例工程使用分模块管理,如表3-1所示,即将数据管理独立成为一个工程模块,专门负责数据库管理方面的功能。而界面设计模块主要负责控制器和视图设计方面的功能。

表3-1 实例工程模块列表

项  目 工  程 功  能

数据管理模块 data 实现使用Neo4j数据库

界面设计模块 webui 控制器和视图设计


3.1 模型设计

数据管理模块实现了MVC中模型的设计,主要负责实体建模和数据库持久化等方面的功能。在本章的实例中,将使用上一章的Neo4j数据库的例子,对电影数据进行管理。回顾一下,有两个节点实体(电影和演员)和一个关系实体(角色)。其中,关系实体体现了节点实体之间的关系,即一个演员在一部电影中扮演一个角色。实体建模和持久化与上一章的实现差不多。只不过为了适应本章的内容,电影节点实体和角色关系实体的建模在属性上做了些许调整。另外针对Neo4j数据库的分页查询也做了一些调整和优化。

3.1.1 节点实体建模

如代码清单3-1所示,在电影节点实体建模中做了一些调整,即增加一个photo属性,用来存放电影剧照,并将关系类型更改为“扮演”。需要注意的是,Neo4j还没有日期格式的数据类型,所以在读取日期类型的数据时,使用注解@DateTimeFormat进行格式转换,而在保存时,使用注解@DateLong将它转换成Long类型的数据进行存储。

代码清单3-1 电影节点实体建模

@JsonIdentityInfo(generator=JSOGGenerator.class)

@NodeEntity

public class Movie {

    @GraphId

    Long id;

    private String name;

    private String photo;

    @DateLong

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

    private Date createDate;


    @Relationship(type="扮演", direction = Relationship.INCOMING)

    List<Role> roles = new ArrayList<>();


    public Role addRole(Actor actor, String name){

        Role role = new Role(actor,this,name);

        this.roles.add(role);

        return role;

    }


    public Movie() { }

    ......

}

3.1.2 关系实体建模

电影实体对应的角色关系实体建模的关系类型也同样做了调整而改为“扮演”,如代码清单3-2所示。

代码清单3-2 角色关系实体建模

@JsonIdentityInfo(generator=JSOGGenerator.class)

@RelationshipEntity(type = "扮演")

public class Role {

    @GraphId

    Long id;

    String name;

    @StartNode

    Actor actor;

    @EndNode

    Movie movie;


    public Role() {

}

……

}

3.1.3 分页查询设计

对于新型的Neo4j数据库来说,由于它的资源库遵循了JPA的规范标准来设计,在分页查询方面有的地方还不是很完善,所以在分页查询中,设计了一个服务类来处理,如代码清单3-3所示。其中,使用Class<T>传入调用的实体对象,使用Pageable传入页数设定和排序字段设定的参数,使用Filters传入查询的一些节点属性设定的参数。

代码清单3-3 Neo4j分页查询服务类

@Service

public class PagesService<T> {

    @Autowired

    private Session session;


    public Page<T> findAll(Class<T> clazz, Pageable pageable, Filters filters){

        Collection data = this.session.loadAll(clazz, filters, convert

(pageable.getSort()), new Pagination(pageable.getPageNumber(), pageable.getPageSize()), 1);

        return updatePage(pageable, new ArrayList(data));

    }

......

3.2 控制器设计

怎样将视图上的操作与模型——数据管理模块联系起来,这中间始终是控制器在起着通信桥梁的作用,它响应视图上的操作事件,然后根据需要决定是否访问数据管理模块,最后再将结果返回给合适的视图,由视图处理显示。下面将按照电影控制器的设计来说明控制器中增删查改的实现方法,演员控制器的设计与此类似,不再赘述。

3.2.1 新建控制器

接收新建电影的请求,以及输入一部电影的数据后的最后提交,由新建控制器进行处理。在控制器上将执行两个操作,第一个操作将返回一个新建电影的视图,第二个操作接收界面中的输入数据,并调用数据管理模块进行保存,如代码清单3-4所示。其中,create函数将返回一个新建电影的视图,它不调用数据管理模块,save函数将需要保存的数据通过调用数据管理模块存储至数据库中,并返回一个成功标志。注意,为了简化设计,将电影剧照的图片文件做了预定义处理。

代码清单3-4 新建电影控制器

@RequestMapping("/new")

    public ModelAndView create(ModelMap model){

        String[] files = {"/images/movie/西游记.jpg","/images/movie/西游记续集.jpg"};

        model.addAttribute("files",files);

        return new ModelAndView("movie/new");

    }


    @RequestMapping(value="/save", method = RequestMethod.POST)

    public String save(Movie movie) throws Exception{

        movieRepository.save(movie);

        logger.info("新增->ID={}", movie.getId());

        return "1";

    }

3.2.2 查看控制器

查看一个电影的详细信息时,控制器首先使用请求的电影ID向数据管理模块请求数据,然后将取得的数据输出到一个显示视图上,如代码清单3-5所示。

代码清单3-5 查看电影控制器

@RequestMapping(value="/{id}")

    public ModelAndView show(ModelMap model, @PathVariable Long id) {

        Movie movie = movieRepository.findOne(id);

        model.addAttribute("movie",movie);

        return new ModelAndView("movie/show");

}

3.2.3 修改控制器

若要实现对电影的修改及保存操作,需要先将电影的数据展示在视图界面上,然后接收界面的操作,调用数据管理模块将更改的数据保存至数据库中,如代码清单3-6所示。其中,为了简化设计,将剧照中的图片文件和电影角色名称做了预定义处理。修改数据时,由于从界面传回的电影对象中,丢失了其角色关系的数据(这是OGM的缺点),所以再次查询一次数据库,以取得一个电影的完整数据,然后再执行修改的操作。

代码清单3-6 修改电影控制器

@RequestMapping(value="/edit/{id}")

    public ModelAndView update(ModelMap model, @PathVariable Long id){

        Movie movie = movieRepository.findOne(id);

        String[] files = {"/images/movie/西游记.jpg","/images/movie/西游记续集.jpg"};

        String[] rolelist = {"唐僧","孙悟空","猪八戒","沙僧"};

        Iterable<Actor> actors = actorRepository.findAll();


        model.addAttribute("files",files);

        model.addAttribute("rolelist",rolelist);

        model.addAttribute("movie",movie);

        model.addAttribute("actors",actors);


        return new ModelAndView("movie/edit");

    }


    @RequestMapping(method = RequestMethod.POST, value="/update")

    public String update(Movie movie, HttpServletRequest request) throws Exception{

        String rolename = request.getParameter("rolename");

        String actorid = request.getParameter("actorid");


        Movie old = movieRepository.findOne(movie.getId());

        old.setName(movie.getName());

        old.setPhoto(movie.getPhoto());

        old.setCreateDate(movie.getCreateDate());


        if(!StringUtils.isEmpty(rolename) && !StringUtils.isEmpty(actorid)) {

            Actor actor = actorRepository.findOne(new Long(actorid));

            old.addRole(actor, rolename);

        }

        movieRepository.save(old);

        logger.info("修改->ID="+old.getId());

        return "1";

}

3.2.4 删除控制器

删除电影时,从界面上接收电影的ID参数,然后调用数据管理模块将电影删除,如代码清单3-7所示。

代码清单3-7 删除电影控制器

@RequestMapping(value="/delete/{id}",method = RequestMethod.GET)

    public String delete(@PathVariable Long id) throws Exception{

        Movie movie = movieRepository.findOne(id);

        movieRepository.delete(movie);

        logger.info("删除->ID="+id);

        return "1";

}

3.2.5 分页查询控制器

列表数据的查询使用分页的方法,按提供的查询字段参数、页码、页大小及其排序字段等参数,通过调用数据管理模块进行查询,然后返回一个分页对象Page,如代码清单3-8所示。这里的分页查询调用了3.1.3节定义的分页查询服务类。

代码清单3-8 电影分页查询控制器

    @RequestMapping(value="/list")

public Page<Movie> list(HttpServletRequest request) throws Exception{

    String name = request.getParameter("name");

    String page = request.getParameter("page");

    String size = request.getParameter("size");

    Pageable pageable = new PageRequest(page==null? 0: Integer.parseInt(page), 

size==null? 10:Integer.parseInt(size),

            new Sort(Sort.Direction.DESC, "id"));


    Filters filters = new Filters();

    if (!StringUtils.isEmpty(name)) {

        Filter filter = new Filter("name", name);

        filters.add(filter);

    }


    return pagesService.findAll(Movie.class, pageable, filters);

}

3.3 使用Thymeleaf模板

完成了模型和控制器的设计之后,接下来的工作就是视图设计了。在视图设计中主要使用Thymeleaf模板来实现。在进行视图设计之前,先了解一下Thymeleaf模板的功能。

Thymeleaf是一个优秀的面向Java的XML/XHTML/HTML 5页面模板,并具有丰富的标签语言和函数。使用Spring Boot框架进行界面设计,一般都会选择Thymeleaf模板。

3.3.1 Thymeleaf配置

要使用Thymeleaf模板,首先,必须在工程的Maven管理中引入它的依赖:“spring-boot-starter-thymeleaf”,如代码清单3-9所示。

代码清单3-9 Thymeleaf依赖配置

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

其次,必须配置使用Thymeleaf模板的一些参数。在一般的Web项目中都会使用如代码清单3-10所示的配置,其中,pref?ix指定了HTML文件存放在webapp的/WEB-INF/views/目录下面,或者也可以指定其他路径,其他一些参数的设置其实是使用了Thymeleaf的默认设置。

在实例中,为了更方便将项目发布成jar文件,我们将使用Thymeleaf自动配置中的默认配置选项,即只要在资源文件夹resoueces中增加一个templates目录即可,这个目录用来存放HTML文件。

代码清单3-10 Thymeleaf配置

spring:

    thymeleaf:

        prefix: /WEB-INF/views/

        suffix: .html

        mode: HTML5

        encoding: UTF-8

        content-type: text/html

        cache: false

如果工程中增加了Thymeleaf的依赖,而没有进行任何配置,或者增加默认目录,启动应用时就会报错。

3.3.2 Thymeleaf功能简介

在HTML页面上使用Thymeleaf标签语言,用一个简单的关键字“th”来标注。使用Thymeleaf标签语言的典型例子如下:

<h3 th:text="${actor.name}"></h3>

<img th:src="@{/images/logo.png}"/>

其中,th:text指定了在标签<h3>中显示的文本,它的值来自于关键字“$”所引用的内存变量,th:src设定了标签<img>的图片文件的链接地址,既可以是绝对路径,也可以是相对路径。下面列出了Thymeleaf的一些主要标签和函数。

th:text,显示文本。

th:utext:和th:text的区别是针对"unescaped text"。

th:attr:设置标签属性。

th:if or th:unless:条件判断语句。

th:switch,th:case:选择语句。

th:each:循环语句。


#dates:日期函数。

#calendars:日历函数。

#numbers:数字函数。

#strings:字符串函数。

#objects:对象函数。

#bools:逻辑函数。

#arrays:数组函数。

#lists:列表函数。

本章的实例工程将在视图设计中使用Thymeleaf的下列几个主要功能,而有关Thymeleaf的详细说明和介绍可以访问它的官方网站http://www.thymeleaf.org/,以获得更多的帮助。

1.?使用功能函数

Thymeleaf有一些日期功能函数、字符串函数、数组函数、列表函数等,代码清单3-11是Thymeleaf使用日期函数的一个例子,#dates.format是一个日期格式化的使用实例,它将电影的创建日期格式化为中文环境的使用格式“'yyyy-MM-dd HH:mm:ss'”。

代码清单3-11 Thymeleaf使用函数

th:value="${movie.createDate} ? ${#dates.format(movie.createDate,'yyyy-MM-dd HH:mm:ss')} :''"

2.?使用编程语句

Thymeleaf有条件语句、选择语句、循环语句等。代码清单3-12使用each循环语句来显示一个数据列表,即在下拉列表框中使用循环语句来显示所有的演员列表。

代码清单3-12 th:each循环

<select name="actorid" id="actorid">

<option value="">选择演员</option>

<option th:each="actor:${actors}"

    th:value="${actor.id}"

    th:text="${actor.name}">

</option>

</select>

3.?使用页面框架模板

Thymeleaf的页面框架模板是比较优秀的功能。预先定义一个layout,它具有页眉、页脚、提示栏、导航栏和内容显示等区域,如代码清单3-13所示。其中,layout:fragment=

" prompt"是一个提示栏,它可以让引用的视图替换显示的内容;fragments/nav :: nav是一个导航栏并指定了视图文件,也就是说它不能被引用的视图替换内容;layout:fragment="content"是一个主要内容显示区域,它也能由引用的视图替换显示内容;fragments/footer :: footer是一个页脚定义并且也指定了视图文件,即不被引用的视图替换显示内容。这样设计出来的页面模板框架如图3-1所示。

代码清单3-13 layout模板

<div class="headerBox">

    <div class="topBox">

        <div class="topLogo f-left">

            <a href="#"><img th:src="@{/images/logo.png}"/></a>

        </div>

        <div class="new-nav">

            <h3>电影频道</h3>

        </div>

    </div>

</div>

<div class="locationLine" layout:fragment=" prompt ">

    当前位置:首页 &gt; <em>页面</em>

</div>

<table class="globalMainBox" style="position:relative;z-index:1">

    <tr>

        <td class="columnLeftBox" valign="top">

            <div th:replace="fragments/nav :: nav"></div>

        </td>

        <td class="whiteSpace"></td>

        <td class="rightColumnBox" valign="top">

            <div layout:fragment="content"></div>

        </td>

    </tr>

</table>


<div class="footBox" th:replace="fragments/footer :: footer"></div>


图3-1 页面框架模板

有了页面模板之后,就可以在一个主页面视图上引用上面的layout,并替换它的提示栏prompt和主要内容显示区域content,其他页眉、页脚和导航栏却保持同样的内容,如代码清单3-14所示。这样就可以设计出一个使用共用模板的具有统一风格特征的界面。

代码清单3-14 使用layout模板的视图设计

<html xmlns:th="http://www.thymeleaf.org" layout:decorator="fragments/layout">

……

<div class="locationLine" layout:fragment=" prompt ">

当前位置:首页 &gt; <em >电影管理</em>

</div>


<div class="statisticBox w-782"  layout:fragment="content">

......

</div>

3.4 视图设计

视图设计包括列表视图、新建视图、查看视图、修改视图和删除视图设计等5个方面有关数据的增删查改的内容。

我们知道,视图上的数据存取不是直接与模型打交道,而是通过控制器来处理。在视图中对于控制器的请求,大多使用jQuery的方式来实现。jQuery是一个优秀的JavaScript程序库,并且具有很好的兼容性,几乎兼容了现有的所有浏览器。

下面的视图设计将以电影的视图设计为例说明,演员的视图设计与此类似,不再赘述。

3.4.1 列表视图设计

电影的列表视图是电影视图的主页,它引用了3.3节使用Thymeleaf设计的页面框架模板layout.html,在这里主要实现对数据的分页查询请求和列表数据显示,并提供了一部电影的新建、查看、修改和删除等超链接。

1.?分页设计

电影的列表视图的分页设计使用了“jquery.pagination.js”分页插件,编写如代码清单3-15所示的脚本,其中getOpt定义了分页工具条的一些基本属性,pageaction通过“./list”调用控制器取得分页数据列表,f?illData函数将列表数据填充到HTML控件tbodyContent中。

代码清单3-15 分页设计的js编码

// 分页的参数设置

var getOpt = function(){

    var opt = {

        items_per_page: 10, // 每页记录数

        num_display_entries: 3, // 中间显示的页数,默认为10

        current_page:0, // 当前页

        num_edge_entries:1, // 头尾显示的页数,默认为0

        link_to:"javascript:void(0)",

        prev_text:"上页",

        next_text:"下页",

        load_first_page:true,

        show_total_info:true ,

        show_first_last:true,

        first_text:"首页",

        last_text:"尾页",

        hasSelect:false,

        callback: pageselectCallback // 回调函数

    }

    return opt;

}

// 分页开始

var currentPageData = null ;

var pageaction = function(){

    $.get('./list?t='+new Date().getTime(),{

        name:$("#name").val()

    },function(data){

        currentPageData = data.content;

        $(".pagination").pagination(data.totalElements, getOpt());

    });

}


var pageselectCallback = function(page_index, jq, size){

    if(currentPageData!=null){

        fillData(currentPageData);

        currentPageData = null;

    }else

        $.get('./list?t='+new Date().getTime(),{

            size:size,page:page_index,name:$("#name").val()

        },function(data){

            fillData(data.content);

        });

}

// 填充分页数据

function fillData(data){

    var $list = $('#tbodyContent').empty();

    $.each(data,function(k,v){

        var html = "" ;

        html += '<tr> ' +

                '<td>'+ (v.id==null?'':v.id) +'</td>' +

                '<td>'+ (v.name==null?'':v.name) +'</td>' +

                '<td>'+ (v.createDate==null?'': getSmpFormatDateByLong(v.create

Date,true)) +'</td>' ;

        html += '<td><a class="c-50a73f mlr-6" href="javascript:void(0)" onclick=

"detail(\''+ v.id+'\')">查看</a><a class="c-50a73f mlr-6" href=

"javascript:void(0)" onclick="edit(\''+ v.id+'\')">修改</a><a class="c-50a73f mlr-6" href="javascript:void(0)" onclick="del(\''+ v.id+'\')">删除</a></td>' ;

        html +='</tr>' ;


        $list.append($(html));

    });

}

// 分页结束

2.?列表页面设计

电影列表的显示页面主要定义了列表字段的名称和提供显示数据内容的控件ID,即tbodyContent,如代码清单3-16所示。

代码清单3-16 电影列表页面HTML编码

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org" layout:decorator="fragments/layout">

<head>

    <title>电影管理</title>


    <link th:href="@{/scripts/pagination/pagination.css}" rel="stylesheet" type="text/css" />

    <link th:href="@{/scripts/artDialog/default.css}" rel="stylesheet" type="text/css" />

    <link th:href="@{/scripts/My97DatePicker/skin/WdatePicker.css}" rel="stylesheet" type="text/css" />

    <link th:href="@{/styles/index.css}" rel="stylesheet"  type="text/css"/>


    <script th:src="@{/scripts/pagination/jquery.pagination.js}"></script>

    <script th:src="@{/scripts/jquery.smartselect-1.1.min.js}"></script>

    <script th:src="@{/scripts/My97DatePicker/WdatePicker.js}"></script>

    <script th:src="@{/scripts/movie/list.js}"></script>

</head>

<body>

<div class="locationLine" layout:fragment="prompt">

    当前位置:首页 &gt; <em >电影管理</em>

</div>


<div class="statisticBox w-782"  layout:fragment="content">

    <form id="queryForm" method="get">

    <div class="radiusGrayBox782">

        <div class="radiusGrayTop782"></div>

        <div class="radiusGrayMid782">

            <div class="dataSearchBox forUserRadius">

                <ul>

                    <li>

                        <label class="preInpTxt f-left">电影名称</label>

                        <input type="text" class="inp-list f-left w-200" place

holder="按电影名称搜索" id="name"  name="name"/>

                    </li>

                    <li>

                        <a href="javascript:void(0)" class="blueBtn-62X30 f-right" 

id="searchBtn">查询</a>

                    </li>

                </ul>

            </div>

        </div>

    </div>

    </form>

    <div class="newBtnBox">

        <input type="hidden" id="m_ck" />

        <a id="addBtn" class="blueBtn-62X30" href="javascript:void(0)">新增</a>

    </div>

    <div class="dataDetailList mt-12">

        <table id="results" class="dataListTab">

            <thead>

            <tr>

                <th>ID</th>

                <th>电影</th>

                <th>出版日期</th>

                <th>操作</th>

            </tr>

            </thead>

            <tbody id="tbodyContent">

            </tbody>

        </table>

        <div class="tableFootLine">

            <div class="pagebarList pagination"/>

        </div>

    </div>

</div>


</body>

</html>

3.?列表视图设计效果

电影数据列表视图设计的最终显示效果如图3-2所示。


图3-2 电影列表视图设计效果图

3.4.2 新建视图设计

1.?新建对话框设计

新建电影时,在电影主页中打开一个对话框显示新建的操作界面,对话框设计引用了“artDialog.js”的对话框插件,然后编写一个脚本来打开对话框,如代码清单3-17所示。其中“./new”是连接控制器的请求URL,注意这里使用了相对路径,这个URL通过“$.get”请求返回新建电影的HTML页面,请求链接中的ts参数传递的是当前时间,这是为了保证该链接是一个全新的链接,以使浏览器能显示一个最新的内容页面。

代码清单3-17 新建电影对话框设计js编码

function create(){

    $.get("./new",{ts:new Date().getTime()},function(data){

        art.dialog({

            lock:true,

            opacity:0.3,

            title: "新增",

            width:'750px',

            height: 'auto',

            left: '50%',

            top: '50%',

            content:data,

            esc: true,

            init: function(){

                artdialog = this;

            },

            close: function(){

                artdialog = null;

            }

        });

    });

}

2.?新建页面设计

新建电影的页面设计,如代码清单3-18所示,这里只是部分HTML编码,其中的日期控件使用“WdatePicker.js”插件来实现。对于一部电影来说,我们需要输入名称、剧照和日期三个属性,其中剧照的图片下拉列表框使用“imageselect.js”图片下拉列表框插件来实现,并且为了简化设计,剧照中的图片文件使用了预先定义的文件,这里只要选择使用哪一个图片即可。

代码清单3-18 新建电影页面HTML编码

<link th:href="@{/styles/imageselect.css}" rel="stylesheet" type="text/css" />

<script th:src="@{/scripts/imageselect.js}"></script>

<script th:src="@{/scripts/movie/new.js}"></script>


<form id="saveForm" action="./save" method="post">

    <table class="addNewInfList">

        <tr>

            <th>名称</th>

            <td><input class="inp-list w-200 clear-mr f-left" type="text"   id=

"name" name="name"  maxlength="120" /></td>

            <th>剧照</th>

            <td width="240">

                <select name="photo" id="photo">

                    <option th:each="file:${files}"

                            th:value="${file}"

                            th:text="${file}">

                    </option>

                </select>

            </td>

        </tr>

        <tr>

            <th>日期</th>

            <td>

                <input  onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" 

type="text" class="inp-list w-200 clear-mr f-left" id="createDate" name="createDate"/>

            </td>

        </tr>

    </table>

    <div class="bottomBtnBox">

        <a class="btn-93X38 saveBtn" href="javascript:void(0)">确定</a>

        <a class="btn-93X38 backBtn" href="javascript: closeDialog()">返回</a>

    </div>

</form>

<script type="text/javascript">

    $(document).ready(function(){

        $('select[name=photo]').ImageSelect({dropdownWidth:425});

    });

</script>

3.?表单验证与提交设计

验证新建电影表单的提交时使用“jquery.validate.min.js”插件中的验证方法来实现,如代码清单3-19所示。保存时调用经典的“$.ajax”方式利用POST方法进行提交,其中headers: {"Content-type": "application/x-www-form-urlencoded;charset=UTF-8"}用于保证数据在传输过程中中文字符的正确性。在表单验证中,只对name和createDate两个属性进行简单的非空验证,表单的参数传递使用一个表单序列化函数serialize()来实现,它将表单控件上的对象序列化为一个个含有“键-值”对的字符串进行提交。

代码清单3-19 新建电影中表单验证和提交的js编码

$(function(){

        $('#saveForm').validate({

                rules: {

            name       :{required:true},

            createDate      :{required:true}

                },messages:{

            name :{required:"必填"},

            createDate :{required:"必填"}

        }

        });

    $('.saveBtn').click(function(){

        if($('#saveForm').valid()){

            $.ajax({

                type: "POST",

                url: "./save",

                data: $("#saveForm").serialize(),

                headers: {"Content-type": "application/x-www-form-urlencoded;

charset=UTF-8"},

                success: function (data) {

                    if (data == 1) {

                        alert("保存成功");

                        pageaction();

                        closeDialog();

                    } else {

                        alert(data);

                    }

                },

                error:function(data){

                    var e;

                    $.each(data,function(v){

                        e += v + " ";

                    });

                    alert(e);

                }

            });

        }else{

            alert('数据验证失败,请检查!');

        }

    });

});

4.?新建视图设计效果

新建电影的视图设计最后的显示效果如图3-3所示。


图3-3 新建电影视图设计效果图

3.4.3 查看视图设计

1.?查看对话框设计

在电影的主页中单击一部电影的查看链接,将打开一个查看电影的对话框,对话框的设计如代码清单3-20所示,其中“./{id}”是提取数据的链接,它将向控制器请求数据,并以HTML页面方式显示出来。

代码清单3-20 查看电影对话框js编码

function detail(id){

    $.get("./"+id,{ts:new Date().getTime()},function(data){

        art.dialog({

            lock:true,

            opacity:0.3,

            title: "查看信息",

            width:'750px',

            height: 'auto',

            left: '50%',

            top: '50%',

            content:data,

            esc: true,

            init: function(){

                artdialog = this;

            },

            close: function(){

                artdialog = null;

            }

        });

    });

}

2.?查看页面设计

电影查看页面的设计,即将数据展示出来的HTML编码,如代码清单3-21所示,需要注意的是,日期数据需要进行格式化,而演员表则使用Thymeleaf中的一个“th:each”循环语句来输出。

代码清单3-21 电影查看页面HTML编码

<div class="addInfBtn">

    <h3 class="itemTit"><span>电影信息</span></h3>

    <table class="addNewInfList">

        <tr>

            <th>名称</th>

            <td width="240"><input class="inp-list w-200 clear-mr f-left" type=

"text" th:value="${movie.name}" id="name" name="name" maxlength="16" /></td>

            <th>日期</th>

            <td>

                <input  onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" type="text" class="inp-list w-200 clear-mr f-left" th:value="${movie.createDate} ? ${#dates.format(movie.createDate,'yyyy-MM-dd HH:mm:ss')} :''" id="createDate" name="createDate"/>

            </td>

        </tr>


        <tr>

            <th>剧照</th>

            <td width="240">

                <img th:src="${movie.photo}"/>

            </td>

            <th>演员表</th>

            <td width="240">

                <ul>

                    <li th:each="role:${movie.roles}" th:text="${role.actor.

name}+' 饰 '+${role.name}"></li>

                </ul>

            </td>

        </tr>

    </table>

    <div class="bottomBtnBox">

        <a class="btn-93X38 backBtn" href="javascript:closeDialog(0)">返回</a>

    </div>

</div>

3.?查看视图的设计效果

电影查看视图设计最终完成的效果如图3-4所示。


图3-4 查看电影视图设计效果图

3.4.4 修改视图设计

1.?修改对话框设计

在电影的主页中修改一部电影,首先打开一个修改电影的对话框,这个对话框的设计如代码清单3-22所示。其中通过“$.get”访问“./edit/{id}”取得数据和修改视图的HTML页面元素。

代码清单3-22 修改电影对话框js编码

function edit(id){

    $.get("./edit/"+id,{ts:new Date().getTime()},function(data){

        art.dialog({

            lock:true,

            opacity:0.3,

            title: "修改",

            width:'750px',

            height: 'auto',

            left: '50%',

            top: '50%',

            content:data,

            esc: true,

            init: function(){

                artdialog = this;

            },

            close: function(){

                artdialog = null;

            }

        });

    });

}

2.?修改页面设计

修改电影视图的页面设计如代码清单3-23所示,其中剧照的下拉列表框中增加了“选中”的代码:th:selected="${movie.photo == f?ile}",即如果电影中的剧照与下拉框列表中的剧照相同,则选中它。

在修改界面上,还增加了“增加角色”和“选择演员”的编辑项。为了简化设计这里的角色名称我们也使用了预先定义的数据。

代码清单3-23 修改电影页面HTML编码

<link th:href="@{/styles/imageselect.css}" rel="stylesheet" type="text/css" />

<script th:src="@{/scripts/imageselect.js}"></script>

<script th:src="@{/scripts/movie/edit.js}"></script>


<form id="saveForm" method="post">

    <input type="hidden" name="id" id="id" th:value="${movie.id}"/>

<div class="addInfBtn" >

    <h3 class="itemTit"><span>编辑信息</span></h3>

    <table class="addNewInfList">

        <tr>

            <th>电影名称</th>

            <td width="240"><input class="inp-list w-200 clear-mr f-left" type=

"text" th:value="${movie.name}" id="name" name="name" maxlength="16" /></td>

            <th>电影剧照</th>

            <td width="240">

                <select name="photo" id="photo">

                    <option th:each="file:${files}"

                            th:value="${file}"

                            th:text="${file}"

                            th:selected="${movie.photo == file}">

                    </option>

                </select>

            </td>

        </tr>


        <tr>

            <th>出版日期</th>

            <td>

                <input  onfocus="WdatePicker({dateFmt:'yyyy-MM-dd HH:mm:ss'})" 

type="text" class="inp-list w-200 clear-mr f-left" th:value="${movie.createDate} ? ${#dates.format(movie.createDate,'yyyy-MM-dd HH:mm:ss')} :''" id="createDate" name="createDate"/>

            </td>


        </tr>

        <tr>

            <th>增加角色</th>

            <td width="240">

                <select name="rolename" id="rolename">

                    <option value="">增加角色</option>

                    <option th:each="role:${rolelist}"

                            th:value="${role}"

                            th:text="${role}">

                    </option>

                </select>

            </td>

            <th>选择演员</th>

            <td width="240">

                <select name="actorid" id="actorid">

                    <option value="">选择演员</option>

                    <option th:each="actor:${actors}"

                            th:value="${actor.id}"

                            th:text="${actor.name}">

                    </option>

                </select>

            </td>

        </tr>

    </table>


    <div class="bottomBtnBox">

        <a class="btn-93X38 saveBtn" href="javascript:void(0)">确定</a>

        <a class="btn-93X38 backBtn" href="javascript:closeDialog(0)">返回</a>

    </div>

</div>


</form>

<script type="text/javascript">

    $(document).ready(function(){

        $('select[name=photo]').ImageSelect({dropdownWidth:425});

    });

</script>

3.?修改视图的设计效果

最终完成的修改电影视图的显示效果如图3-5所示。


图3-5 修改电影视图设计效果图

3.4.5 删除视图设计

1.?删除确认对话框

如果有删除的操作,首先要给出确认提示框,只有用户单击确定后才能删除数据,否则将不做任何操作。确认提示框是调用了Windows中的确认对话框,如代码清单3-24所示。

代码清单3-24 删除确认对话框js编码

function del(id){

    if(!confirm("您确定删除此记录吗?")){

        return false;

    }

    $.get("./delete/"+id,{ts:new Date().getTime()},function(data){

        if(data==1){

            alert("删除成功");

            pageaction();

        }else{

            alert(data);

        }

    });

}

2.?删除确认设计效果

执行删除操作的确认效果如图3-6所示。

3.5 运行与发布

本章实例工程的完整代码可以通过IDEA从GitHub中检出:https://github.com/chenfromsz/spring-boot-ui.git。Spring Boot需要一个启动程序作为应用的入口,在webui模块中,我们设计了一个入口程序,如代码清单3-25所示。使用这个入口程序,就可以调试和发布工程了。

代码清单3-25 Web应用启动主程序

package com.test.webui;


import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.annotation.ComponentScan;


@SpringBootApplication

@ComponentScan(basePackages = "com.test")

public class WebuiApp {

    public static void main(String[] args) {

        SpringApplication.run(WebuiApp.class, args);

    }

}

通过在IDEA中打开Run/Debug Conf?igurations对话框,增加一个Spring Boot配置,模块选择webui,工作目录选择模块webui所在的路径,主程序选择WebuiApp,并将配置保存为webui。然后在IDEA中运行该配置项目webui,即可启动应用进行调试。

如果要发布应用,可以在IDEA的Run/Debug Conf?igurations对话框中增加一个Maven打包配置项目,工作目录选择工程的根目录,命令行中输入指令:clean package-D skipTests,并将配置保存为mvn。然后运行这个配置项目mvn进行打包,打包成功后,在“webui/target”目录中将生成webui-1.0-SNAPSHOT.jar。要运行这个程序包,可以打开一个命令行窗口,将路径切换到webui-1.0-SNAPSHOT.jar所在的目录,使用下列指令即可运行应用。

java -jar webui-1.0-SNAPSHOT.jar

最后可使用下面的URL进行访问:

http://localhost

在实例中增加了一些数据之后,在Neo4j数据库客户端中单击“扮演”关系,也可以看到电影和演员的关系图,如图3-7所示。

3.6 小结

本章介绍了使用MVC的多层结构方式,以及在Spring Boot进行Web界面设计的方法,并且使用Thymeleaf模板设计了一个Web应用的页面框架。Web界面设计的一些细节,更多的是使用了HTML编码和JavaScript脚本,而HTML离不开CSS的支持,JavaScript更是借助于jQuery及其各种插件的功能。读者如需深入了解这方面的知识和技术,可查找相关的知识进行学习和研究。这里主要使用Thymeleaf模板工具来设计整体界面以及组织和处理数据的显示。

有了显示界面之后,对数据库的操作就更为方便和直观了。下一章将介绍如何使用一些技术手段来提升访问数据库的性能,以及怎样扩展访问数据库的功能。

相关文章
|
3月前
|
存储 监控 Java
Spring入门
【1月更文挑战第13天】 一、环境要求 二、构建模块 三、程序开发 1、引入依赖 2、创建java类 3、创建配置文件 4、创建测试类测试 5、运行测试程序 四、程序分析 五、启用Log4j2日志框架 1、Log4j2日志概述 2、引入Log4j2依赖 3、加入日志配置文件 4、测试 5、使用日志
77 2
|
4月前
|
XML 安全 Java
Spring+SpringMVC+Mybatis入门(一)
一、Spring 1、Spring环境搭建 调整项目环境 修改JDK版本
|
4月前
|
SQL Java 数据库连接
Spring+SpringMVC+Mybatis入门(三)
• 持久层:将内存中的对象数据转移到数据库中的过程 • MyBatis、Hibernate、Spring-jpa • ORM(Object Relational Mapping):对象关系映射框架 • 类<=>表 • 属性<=>字段
|
4月前
|
JSON 缓存 前端开发
Spring+SpringMVC+Mybatis入门(二)
模型-视图-控制器(MVC)是一个以设计页面应用程序为基础的设计思想。它主要通过分离模型、试图及控制器在应用程序的角色将业务逻辑从界面中解耦。通常模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(service或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据以及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中抽离出来,允许他们单独改变而不会相互影响。
|
6月前
|
Java 数据库连接 API
Spring学习(三)
Spring学习
19 0
|
6月前
|
XML Java 数据格式
Spring学习(二)
Spring学习
26 0
|
6月前
|
XML 前端开发 Java
Spring学习(一)
Spring学习
18 0
|
8月前
|
Java 数据库连接 Spring
Spring入门-1
一、Spring配置文件 1、Bean标签基本配置
|
8月前
|
存储 Java Spring
Spring入门-3
五、Spring集成Junit 1、原始Junit测试Spring的问题 在测试类中,每个测试方法都有以下两行代码
|
8月前
|
XML 存储 Java