Spring Boot 学习笔记 - 钢钢更新

背景介绍

该文档是在慕课网实战课程《Spring Boot企业微信点餐系统》基础上总结而成,旨在记录Spring Boot一些相关知识,文章中涉及的代码都经过验证,可以直接使用。
该文档作为个人参考资料,会长期更新。

慕课网课程地址:Spring Boot企业微信点餐系统

数据库设计

微信点餐数据库 - SQL.md

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
-- 类目
create table `product_category` (
`category_id` int not null auto_increment,
`category_name` varchar(64) not null comment '类目名字',
`category_type` int not null comment '类目编号',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`category_id`)
);

-- 商品
create table `product_info` (
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '单价',
`product_stock` int not null comment '库存',
`product_description` varchar(64) comment '描述',
`product_icon` varchar(512) comment '小图',
`product_status` tinyint(3) DEFAULT '0' COMMENT '商品状态,0正常1下架',
`category_type` int not null comment '类目编号',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`product_id`)
);

-- 订单
create table `order_master` (
`order_id` varchar(32) not null,
`buyer_name` varchar(32) not null comment '买家名字',
`buyer_phone` varchar(32) not null comment '买家电话',
`buyer_address` varchar(128) not null comment '买家地址',
`buyer_openid` varchar(64) not null comment '买家微信openid',
`order_amount` decimal(8,2) not null comment '订单总金额',
`order_status` tinyint(3) not null default '0' comment '订单状态, 默认为新下单',
`pay_status` tinyint(3) not null default '0' comment '支付状态, 默认未支付',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`order_id`),
key `idx_buyer_openid` (`buyer_openid`)
);

-- 订单商品
create table `order_detail` (
`detail_id` varchar(32) not null,
`order_id` varchar(32) not null,
`product_id` varchar(32) not null,
`product_name` varchar(64) not null comment '商品名称',
`product_price` decimal(8,2) not null comment '当前价格,单位分',
`product_quantity` int not null comment '数量',
`product_icon` varchar(512) comment '小图',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`detail_id`),
key `idx_order_id` (`order_id`)
);

-- 卖家(登录后台使用, 卖家登录之后可能直接采用微信扫码登录,不使用账号密码)
create table `seller_info` (
`id` varchar(32) not null,
`username` varchar(32) not null,
`password` varchar(32) not null,
`openid` varchar(64) not null comment '微信openid',
`create_time` timestamp not null default current_timestamp comment '创建时间',
`update_time` timestamp not null default current_timestamp on update current_timestamp comment '修改时间',
primary key (`id`)
) comment '卖家信息表';


Spring Boot项目结构


POM依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.imooc</groupId>
<artifactId>sell</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>sell</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

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

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

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

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

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

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

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

<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>2.7.0</version>
</dependency>

<dependency>
<groupId>cn.springboot</groupId>
<artifactId>best-pay-sdk</artifactId>
<version>1.1.0</version>
</dependency>

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

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

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

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.0</version>
</dependency>


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

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


</project>


应用配置

全局配置文件

application.yml

1
2
3
spring:
profiles:
active: dev

开发配置文件

application-dev.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
jpa:
show-sql: true
jackson:
default-property-inclusion: non_null
redis:
host: 192.168.30.113
port: 6379
server:
context-path: /sell
#logging:
# pattern:
# console: "%d - %msg%n"
## path: /var/log/tomcat/
# file: /var/log/tomcat/sell.log
# level:
# com.imooc.LoggerTest: debug

wechat:
mpAppId: wxd898fcb01713c658
mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
openAppId: wx6ad144e54af67d87
openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
mchId: 1483469312
mchKey: 06C56A89949D617xxxxxxxxxxx
keyPath: /var/weixin_cert/h5.p12
notifyUrl: http://sell.natapp4.cc/sell/pay/notify
templateId:
orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ

projectUrl:
wechatMpAuthorize: http://sell.natapp4.cc
wechatOpenAuthorize: http://sell.natapp4.cc
sell: http://sell.natapp4.cc
logging:
level:
com.imooc.dataobject.mapper: trace
mybatis:
mapper-locations: classpath:mapper/*.xml

生产配置文件

application-prod.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://192.168.30.113/sell?characterEncoding=utf-8&useSSL=false
jackson:
default-property-inclusion: non_null
redis:
host: 192.168.30.113
port: 6379
server:
context-path: /sell
#logging:
# pattern:
# console: "%d - %msg%n"
## path: /var/log/tomcat/
# file: /var/log/tomcat/sell.log
# level:
# com.imooc.LoggerTest: debug

wechat:
mpAppId: wxd898fcb01713c658
mpAppSecret: 47ccc303338cee6e62894fxxxxxxxxxxx
openAppId: wx6ad144e54af67d87
openAppSecret: 91a2ff6d38a2bbccfb7e9xxxxxx
mchId: 1483469312
mchKey: 06C56A89949D617xxxxxxxxxxx
keyPath: /var/weixin_cert/h5.p12
notifyUrl: http://sell.natapp4.cc/sell/pay/notify
templateId:
orderStatus: e-Cqq67QxD6YNI41iRiqawEYdFavW_7pc7LyEMb-yeQ

projectUrl:
wechatMpAuthorize: http://sell.natapp4.cc
wechatOpenAuthorize: http://sell.natapp4.cc
sell: http://sell.natapp4.cc
logging:
level:
com.imooc.dataobject.mapper: trace
mybatis:
mapper-locations: classpath:mapper/*.xml

配置文件类

BlogProperties.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.mindex.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@ConfigurationProperties(prefix = "com.mindex.blog")
@Component
public class BlogProperties {
private String name;
private String desc;
}

引用配置信息

BlogPropertiesTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.mindex.config;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class BlogPropertiesTest {

@Autowired
private BlogProperties blogProperties;

@Test
public void getProperties() throws Exception {
Assert.assertEquals("轮子王", blogProperties.getName());
Assert.assertEquals("用行动改变世界", blogProperties.getDesc());
}

}


自定义配置文件

WechatAccountConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.imooc.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;

@Data
@Component
@ConfigurationProperties(prefix = "wechat")
public class WechatAccountConfig {

private String mpAppId;
private String mpAppSecret;
private String openAppId;
private String openAppSecret;
private String mchId;
private String mchKey;
private String keyPath;
private String notifyUrl;
private Map<String, String> templateId;
}

引用自定义的配置文件

WechatMpConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.imooc.config;

import me.chanjar.weixin.mp.api.WxMpConfigStorage;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class WechatMpConfig {

@Autowired
private WechatAccountConfig accountConfig;

@Bean
public WxMpService wxMpService() {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
return wxMpService;
}

@Bean
public WxMpConfigStorage wxMpConfigStorage() {
WxMpInMemoryConfigStorage wxMpConfigStorage = new WxMpInMemoryConfigStorage();
wxMpConfigStorage.setAppId(accountConfig.getMpAppId());
wxMpConfigStorage.setSecret(accountConfig.getMpAppSecret());
return wxMpConfigStorage;
}
}


日志处理

SLF4j

Logback

logback-spring.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?xml version="1.0" encoding="UTF-8" ?>

<configuration>

<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
%d - %msg%n
</pattern>
</layout>
</appender>

<appender name="fileInfoLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>DENY</onMatch>
<onMismatch>ACCEPT</onMismatch>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/kwang/imooc/sell/log/info.%d.log</fileNamePattern>
</rollingPolicy>
</appender>


<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--路径-->
<fileNamePattern>/Users/kwang/imooc/sell/log/error.%d.log</fileNamePattern>
</rollingPolicy>
</appender>

<root level="info">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileInfoLog" />
<appender-ref ref="fileErrorLog" />
</root>

</configuration>


Swagger2 文档工具

引入POM依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>

创建Swagger配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.mindex.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class Swagger2Configuration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 自行修改为自己的包路径
.apis(RequestHandlerSelectors.basePackage("com.mindex.controller"))
.paths(PathSelectors.any())
.build();
}

private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("api文档")
.description("Restful 风格接口")
.version("1.0")
.build();
}
}

在controller中引入Swagger注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.mindex.controller;

import com.mindex.entities.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;

import java.util.*;

@RestController
@RequestMapping(value = "/users")
@Api(value = "/users", tags = "测试接口模块")
public class UserController {

//创建线程安全的Map
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());

@ApiOperation(value = "获取用户列表", notes = "")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<User> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> userList = new ArrayList<User>(users.values());
return userList;
}

@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
@RequestMapping(value = "/", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return "success";
}


@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}

@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = true, dataType = "User")
})
@RequestMapping(value = "/{id}", method = RequestMethod.POST)
public String putUser(@PathVariable Long id, @ModelAttribute User user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}

@ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return "success";
}
}

启动tomcat查看文档

http://localhost:8080/swagger-ui.html


IDEA插件

JRebel插件

引入POM依赖

1
2
3
4
5
6
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>1.5.1.RELEASE</version>
<scope>provided</scope>
</dependency>

配置Application.java

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
@ComponentScan(basePackages = "com.mindex")
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}

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

配置maven project选项

选中Lifecycle-clean及compile

安装JRebel插件

配置JRebel插件

在IDEA里新建一个部署配置项。

运行测试

Lombok插件

好处:安装了Lombok插件和pom引用依赖后,可以简化代码,例如:无需再写get/set/toString方法,打印日志时直接使用log关键字等。

安装Lombok插件

安装步骤在这里

引用pom依赖

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

好处1:只要使用@Data@Getter@Setter@ToString等注解,无需再写繁琐的get/set/toString方法,Lombok会在编译时自动加入代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.imooc.dataobject;

import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

@Entity
@DynamicUpdate
@Data
public class ProductCategory {

@Id
@GeneratedValue
private Integer categoryId;

private String categoryName;
private Integer categoryType;
private Date createTime;
private Date updateTime;

public ProductCategory(String categoryName, Integer categoryType) {
this.categoryName = categoryName;
this.categoryType = categoryType;
}

public ProductCategory() {
}
}

好处2:输出日志时,可以直接使用log关键字输出,支持参数引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.imooc;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = LoggerTest.class)
@Slf4j
public class LoggerTest {

// 无需再写LoggerFactory
// private final Logger logger = LoggerFactory.getLogger(LoggerTest.class);

@Test
public void test1() {
String name = "imooc";
String password = "12345";
log.debug("debug...");
log.info("name: {}, password: {}", name, password);
log.error("error...");
log.warn("warning...");
}
}


项目运行类(主入口)

SellApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.imooc;

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

@SpringBootApplication
public class SellApplication {

public static void main(String[] args) {
SpringApplication.run(SellApplication.class, args);
}
}


enums枚举类

ResultEnum.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.imooc.enums;

import lombok.Getter;

@Getter
public enum ResultEnum {
PARAM_ERROR(1,"参数不正确"),
PRODUCT_NOT_EXIST(10, "商品不存在"),
PRODUCT_STOCK_ERROR(11, "库存不正确"),
ORDER_NOT_EXIST(12, "订单不存在"),
;

private Integer code;
private String message;

ResultEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}


util工具类

可以把常用的方法放在util包里,比如拼接vo视图、生成唯一编码等;

构造结果VO视图

ResultVOUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.imooc.utils;

import com.imooc.VO.ResultVO;

public class ResultVOUtil {

public static ResultVO success(Object object) {
ResultVO resultVO = new ResultVO();
resultVO.setData(object);
resultVO.setCode(0);
resultVO.setMsg("成功");
return resultVO;
}

public static ResultVO success() {
return success(null);
}

public static ResultVO error(Integer code, String msg) {
ResultVO resultVO = new ResultVO();
resultVO.setCode(code);
resultVO.setMsg(msg);
return resultVO;
}
}

生成随机id

KeyUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.imooc.utils;

import java.util.Random;

public class KeyUtil {
/**
* 生成唯一的主键
* 格式: 时间+随机数
*
* @return
*/
public static synchronized String genUniqueKey() {
Random random = new Random();

Integer number = random.nextInt(900000) + 100000;
return System.currentTimeMillis() + String.valueOf(number);
}
}

object -> json

JsonUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.imooc.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonUtil {

public static String toJson(Object object) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setPrettyPrinting();
Gson gson = gsonBuilder.create();
return gson.toJson(object);
}
}

Cookie工具类

CookieUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.imooc.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

public class CookieUtil {

/**
* 设置
* @param response
* @param name
* @param value
* @param maxAge
*/
public static void set(HttpServletResponse response,
String name,
String value,
int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}

/**
* 获取cookie
* @param request
* @param name
* @return
*/
public static Cookie get(HttpServletRequest request,
String name) {
Map<String, Cookie> cookieMap = readCookieMap(request);
if (cookieMap.containsKey(name)) {
return cookieMap.get(name);
}else {
return null;
}
}

/**
* 将cookie封装成Map
* @param request
* @return
*/
private static Map<String, Cookie> readCookieMap(HttpServletRequest request) {
Map<String, Cookie> cookieMap = new HashMap<>();
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie: cookies) {
cookieMap.put(cookie.getName(), cookie);
}
}
return cookieMap;
}
}

比较金额(double类型)是否相等

MathUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.imooc.utils;

public class MathUtil {

private static final Double MONEY_RANGE = 0.01;

/**
* 比较2个金额是否相等
* @param d1
* @param d2
* @return
*/
public static Boolean equals(Double d1, Double d2) {
Double result = Math.abs(d1 - d2);
if (result < MONEY_RANGE) {
return true;
}else {
return false;
}
}
}


VO视图层

要返回的数据格式如下:

第一层VO

ResultVO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.imooc.VO;

import lombok.Data;

/**
* http请求返回的最外层对象
*/
@Data
public class ResultVO<T> {

/* 状态码 */
private Integer code;

/* 提示信息 */
private String msg;

/* 具体内容 */
private T data;
}

第二层VO

ProductVO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.imooc.VO;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;

/**
* 商品(包含类目)
*/
@Data
public class ProductVO {

@JsonProperty("name")
private String categoryName;

@JsonProperty("type")
private Integer categoryType;

@JsonProperty("foods")
private List<ProductInfoVO> productInfoVOList;
}

第三层VO

ProductInfoVO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.imooc.VO;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.math.BigDecimal;

/**
* 商品详情
*/
@Data
public class ProductInfoVO {

@JsonProperty("id")
private String productId;

@JsonProperty("name")
private String productName;

@JsonProperty("price")
private BigDecimal productPrice;

@JsonProperty("description")
private String productDescription;

@JsonProperty("icon")
private String productIcon;
}


DTO层

可以把DTO理解成数据库视图。

OrderDTO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.imooc.dto;

import com.imooc.dataobject.OrderDetail;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

@Data
public class OrderDTO {
List<OrderDetail> orderDetailList;
private String orderId;
private String buyerName;
private String buyerPhone;
private String buyerAddress;
private String buyerOpenid;
private BigDecimal orderAmount;
private Integer orderStatus;
private Integer payStatus;
private Date createTime;
private Date updateTime;
}

CartDTO.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.imooc.dto;

import lombok.Data;

@Data
public class CartDTO {

private String productId;
private Integer productQuantity;

public CartDTO(String productId, Integer productQuantity) {
this.productId = productId;
this.productQuantity = productQuantity;
}
}


Exception异常处理

自定义异常

SellException.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.imooc.exception;

import com.imooc.enums.ResultEnum;

public class SellException extends RuntimeException {

private Integer code;

public SellException(ResultEnum resultEnum) {
super(resultEnum.getMessage());
this.code = resultEnum.getCode();
}

public SellException(Integer code, String message) {
super(message);
this.code = code;
}
}

ResponseBankException.java

1
2
3
4
package com.imooc.exception;

public class ResponseBankException extends RuntimeException {
}

SellerAuthorizeException.java

1
2
3
4
package com.imooc.exception;

public class SellerAuthorizeException extends RuntimeException {
}

自定义异常处理器

SellExceptionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.imooc.handler;

import com.imooc.VO.ResultVO;
import com.imooc.config.ProjectUrlConfig;
import com.imooc.exception.ResponseBankException;
import com.imooc.exception.SellException;
import com.imooc.exception.SellerAuthorizeException;
import com.imooc.utils.ResultVOUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class SellExceptionHandler {

@Autowired
private ProjectUrlConfig projectUrlConfig;

//拦截登录异常
//http://sell.natapp4.cc/sell/wechat/qrAuthorize?returnUrl=http://sell.natapp4.cc/sell/seller/login
@ExceptionHandler(value = SellerAuthorizeException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public ModelAndView handlerAuthorizeException() {
return new ModelAndView("redirect:"
.concat(projectUrlConfig.getWechatOpenAuthorize())
.concat("/sell/wechat/qrAuthorize")
.concat("?returnUrl=")
.concat(projectUrlConfig.getSell())
.concat("/sell/seller/login"));
}

@ExceptionHandler(value = SellException.class)
@ResponseBody
public ResultVO handlerSellerException(SellException e) {
return ResultVOUtil.error(e.getCode(), e.getMessage());
}

@ExceptionHandler(value = ResponseBankException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleResponseBankException() {

}
}

统一异常处理

假设访问一个不存在的页面,抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.mindex.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ThymeleafTest {

@ResponseBody
@RequestMapping("/hello")
public String hello() throws Exception {
throw new Exception("发生错误");
}
}

创建全局异常处理类

通过使用@ControllerAdvice定义统一的异常处理类,而不是在每个Controller中逐个定义。@ExceptionHandler用来定义函数针对的异常类型,最后将Exception对象和请求URL映射到error.html中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.mindex.exception;

import com.mindex.entities.ErrorInfo;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;

@ControllerAdvice
public class GlobalExceptionHandler {

public static final String DEFAULT_ERROR_VIEW = "error";

@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}

实现error.html页面展示:在templates目录下创建error.html,将请求的URL和Exception对象的message输出。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head lang="en">
<meta charset="UTF-8"/>
<title>统一异常处理</title>
</head>
<body>
<h1>Error Handler</h1>
<div th:text="${url}"></div>
<div th:text="${exception.message}"></div>
</body>
</html>

启动该应用,访问:http://localhost:8080/hello,可以看到如下错误提示页面。

通过实现上述内容之后,我们只需要在Controller中抛出Exception,当然我们可能会有多种不同的Exception。然后在@ControllerAdvice类中,根据抛出的具体Exception类型匹配@ExceptionHandler中配置的异常类型来匹配错误映射和处理。

返回json格式

创建统一的JSON返回对象,code:消息类型,message:消息内容,url:请求的url,data:请求返回的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.entities;

import lombok.Data;

@Data
public class ErrorInfo<T> {

public static final Integer OK = 0;
public static final Integer ERROR = 100;

private Integer code;
private String message;
private String url;
private T data;
}

创建一个自定义异常,用来实验捕获该异常,并返回json。

1
2
3
4
5
6
7
8
package com.mindex.exception;

public class MyException extends Exception {

public MyException(String message) {
super(message);
}
}

Controller中增加json映射,抛出MyException异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.controller;

import com.mindex.exception.MyException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloController {

@RequestMapping("/json")
public String json() throws MyException {
throw new MyException("发生错误2");
}

}

MyException异常创建对应的处理。

1
2
3
4
5
6
7
8
9
10
@ExceptionHandler(value = MyException.class)
@ResponseBody
public ErrorInfo<String> jsonErrorHandler(HttpServletRequest req, MyException e) throws Exception {
ErrorInfo<String> r = new ErrorInfo<>();
r.setMessage(e.getMessage());
r.setCode(ErrorInfo.ERROR);
r.setData("Some Data");
r.setUrl(req.getRequestURL().toString());
return r;
}

启动应用,访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

![](https://ws1.sinaimg.cn/large/006By2pOgy1fqtwhe1igxj30xy0cmdif.jpg)


---
## Authorize用户有效性鉴定
SellerAuthorizeAspect.java
```java
package com.imooc.aspect;

import com.imooc.constant.CookieConstant;
import com.imooc.constant.RedisConstant;
import com.imooc.exception.SellerAuthorizeException;
import com.imooc.utils.CookieUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
@Slf4j
public class SellerAuthorizeAspect {

@Autowired
private StringRedisTemplate redisTemplate;

@Pointcut("execution(public * com.imooc.controller.Seller*.*(..))" +
"&& !execution(public * com.imooc.controller.SellerUserController.*(..))")
public void verify() {
}

@Before("verify()")
public void doVerify() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

//查询cookie
Cookie cookie = CookieUtil.get(request, CookieConstant.TOKEN);
if (cookie == null) {
log.warn("【登录校验】Cookie中查不到token");
throw new SellerAuthorizeException();
}

//去redis里查询
String tokenValue = redisTemplate.opsForValue().get(String.format(RedisConstant.TOKEN_PREFIX, cookie.getValue()));
if (StringUtils.isEmpty(tokenValue)) {
log.warn("【登录校验】Redis中查不到token");
throw new SellerAuthorizeException();
}
}
}


Data Object层(Entity)

主要用来映射数据库表及字段关系。

dataobject定义

ProductCategory.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.imooc.dataobject;

import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

@Entity
@DynamicUpdate
@Data
public class ProductCategory {

@Id
@GeneratedValue
private Integer categoryId;

private String categoryName;
private Integer categoryType;
private Date createTime;
private Date updateTime;

public ProductCategory(String categoryName, Integer categoryType) {
this.categoryName = categoryName;
this.categoryType = categoryType;
}

public ProductCategory() {
}
}


Repository层

JpaRepository对数据库常用操作进行了封装,通过继承JpaRepository可以快速实现数据库操作。
JpaRepository第一个参数是data object,第二个是该data object的主键。

repository定义

ProductCategoryRepository.java

1
2
3
4
5
6
7
8
9
10
package com.imooc.repository;

import com.imooc.dataobject.ProductCategory;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Integer> {
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
}

repository单元测试

ProductCategoryRepositoryTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.imooc.repository;

import com.imooc.dataobject.ProductCategory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProductCategoryRepositoryTest {

@Autowired
private ProductCategoryRepository repository;

@Test
public void findOneTest() {
ProductCategory productCategory = repository.findOne(1);
System.out.println(productCategory.toString());
}

@Test
@Transactional
public void saveTest() {
ProductCategory productCategory = new ProductCategory("男生最爱", 4);
ProductCategory result = repository.save(productCategory);
Assert.assertNotNull(result);
// Assert.assertNotEquals(null, result);
}

@Test
public void findByCategoryTypeInTest() {
List<Integer> list = Arrays.asList(2,3,4);

List<ProductCategory> result = repository.findByCategoryTypeIn(list);
Assert.assertNotEquals(0, result.size());
}

@Test
public void updateTest() {
// ProductCategory productCategory = repository.findOne(4);
// productCategory.setCategoryName("男生最爱1");
ProductCategory productCategory = new ProductCategory("男生最爱", 4);
ProductCategory result = repository.save(productCategory);
Assert.assertEquals(productCategory, result);
}
}


Service层

service接口

CategoryService.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.imooc.service;

import com.imooc.dataobject.ProductCategory;
import java.util.List;

public interface CategoryService {

ProductCategory findOne(Integer categoryId);
List<ProductCategory> findAll();
List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList);
ProductCategory save(ProductCategory productCategory);
}

service实现

需要使用@Service注解

CategoryServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.imooc.service.impl;

import com.imooc.dataobject.ProductCategory;
import com.imooc.repository.ProductCategoryRepository;
import com.imooc.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CategoryServiceImpl implements CategoryService {

@Autowired
private ProductCategoryRepository repository;

@Override
public ProductCategory findOne(Integer categoryId) {
return repository.findOne(categoryId);
}

@Override
public List<ProductCategory> findAll() {
return repository.findAll();
}

@Override
public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) {
return repository.findByCategoryTypeIn(categoryTypeList);
}

@Override
public ProductCategory save(ProductCategory productCategory) {
return repository.save(productCategory);
}
}

service单元测试

CategoryServiceImplTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.imooc.service.impl;

import com.imooc.dataobject.ProductCategory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class CategoryServiceImplTest {

@Autowired
private CategoryServiceImpl categoryService;

@Test
public void findOne() throws Exception {
ProductCategory productCategory = categoryService.findOne(1);
Assert.assertEquals(new Integer(1), productCategory.getCategoryId());
}

@Test
public void findAll() throws Exception {
List<ProductCategory> productCategoryList = categoryService.findAll();
Assert.assertNotEquals(0, productCategoryList.size());
}

@Test
public void findByCategoryTypeIn() throws Exception {
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(Arrays.asList(1,2,3,4));
Assert.assertNotEquals(0, productCategoryList.size());
}

@Test
public void save() throws Exception {
ProductCategory productCategory = new ProductCategory("男生专享", 10);
ProductCategory result = categoryService.save(productCategory);
Assert.assertNotNull(result);
}

}


Controller层

@RestController 注解,直接返回json格式;
@RequestMapping(“buyer/product”) 注解声明服务的url前缀;

BuyerProductController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.imooc.controller;

import com.imooc.VO.ProductInfoVO;
import com.imooc.VO.ProductVO;
import com.imooc.VO.ResultVO;
import com.imooc.dataobject.ProductCategory;
import com.imooc.dataobject.ProductInfo;
import com.imooc.service.CategoryService;
import com.imooc.service.ProductService;
import com.imooc.utils.ResultVOUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/buyer/product")
public class BuyerProductController {

@Autowired
private ProductService productService;

@Autowired
private CategoryService categoryService;

@GetMapping("/list")
public ResultVO list() {
//1. 查询所有上架商品
List<ProductInfo> productInfoList = productService.findUpAll();

//2. 查询类目(一次性查询)
//传统方法
List<Integer> categoryTypeList = new ArrayList<>();
// for (ProductInfo productInfo : productInfoList) {
// categoryTypeList.add(productInfo.getCategoryType());
// }

//精简方法(java8, lambda)
categoryTypeList = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList());
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypeList);

//3. 数据拼装
List<ProductVO> productVOList = new ArrayList<>();
for (ProductCategory productCategory : productCategoryList) {
ProductVO productVO = new ProductVO();
productVO.setCategoryName(productCategory.getCategoryName());
productVO.setCategoryType(productCategory.getCategoryType());

List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for (ProductInfo productInfo : productInfoList) {
if (productInfo.getCategoryType().equals(productCategory.getCategoryType())) {
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo, productInfoVO);
productInfoVOList.add(productInfoVO);
}
}
productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}

return ResultVOUtil.success(productVOList);
}
}

SellerProductController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.imooc.controller;

import com.imooc.dataobject.ProductCategory;
import com.imooc.dataobject.ProductInfo;
import com.imooc.exception.SellException;
import com.imooc.form.ProductForm;
import com.imooc.service.CategoryService;
import com.imooc.service.ProductService;
import com.imooc.utils.KeyUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/seller/product")
public class SellerProductController {

@Autowired
private ProductService productService;

@Autowired
private CategoryService categoryService;

/**
* 列表
* @param page
* @param size
* @param map
* @return
*/
@GetMapping("/list")
public ModelAndView list(@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "size", defaultValue = "10") Integer size,
Map<String, Object> map) {
PageRequest request = new PageRequest(page - 1, size);
Page<ProductInfo> productInfoPage = productService.findAll(request);
map.put("productInfoPage", productInfoPage);
map.put("currentPage", page);
map.put("size", size);
return new ModelAndView("product/list", map);
}

/**
* 商品上架
* @param productId
* @param map
* @return
*/
@RequestMapping("/on_sale")
public ModelAndView onSale(@RequestParam("productId") String productId,
Map<String, Object> map) {
try {
productService.onSale(productId);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}
/**
* 商品下架
* @param productId
* @param map
* @return
*/
@RequestMapping("/off_sale")
public ModelAndView offSale(@RequestParam("productId") String productId,
Map<String, Object> map) {
try {
productService.offSale(productId);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}

@GetMapping("/index")
public ModelAndView index(@RequestParam(value = "productId", required = false) String productId,
Map<String, Object> map) {
if (!StringUtils.isEmpty(productId)) {
ProductInfo productInfo = productService.findOne(productId);
map.put("productInfo", productInfo);
}

//查询所有的类目
List<ProductCategory> categoryList = categoryService.findAll();
map.put("categoryList", categoryList);

return new ModelAndView("product/index", map);
}

/**
* 保存/更新
* @param form
* @param bindingResult
* @param map
* @return
*/
@PostMapping("/save")
// @Cacheable(cacheNames = "product", key = "123")
// @Cacheable(cacheNames = "product", key = "456")
// @CachePut(cacheNames = "product", key = "123")
@CacheEvict(cacheNames = "product", allEntries = true, beforeInvocation = true)
public ModelAndView save(@Valid ProductForm form,
BindingResult bindingResult,
Map<String, Object> map) {
if (bindingResult.hasErrors()) {
map.put("msg", bindingResult.getFieldError().getDefaultMessage());
map.put("url", "/sell/seller/product/index");
return new ModelAndView("common/error", map);
}

ProductInfo productInfo = new ProductInfo();
try {
//如果productId为空, 说明是新增
if (!StringUtils.isEmpty(form.getProductId())) {
productInfo = productService.findOne(form.getProductId());
} else {
form.setProductId(KeyUtil.genUniqueKey());
}
BeanUtils.copyProperties(form, productInfo);
productService.save(productInfo);
} catch (SellException e) {
map.put("msg", e.getMessage());
map.put("url", "/sell/seller/product/index");
return new ModelAndView("common/error", map);
}

map.put("url", "/sell/seller/product/list");
return new ModelAndView("common/success", map);
}
}


使用Mabatis注解方式实现增删改查

mapper层

ProductCategoryMapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.imooc.dataobject.mapper;

import com.imooc.dataobject.ProductCategory;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;

public interface ProductCategoryMapper {

@Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{category_type, jdbcType=INTEGER})")
int insertByMap(Map<String, Object> map);

@Insert("insert into product_category(category_name, category_type) values (#{categoryName, jdbcType=VARCHAR}, #{categoryType, jdbcType=INTEGER})")
int insertByObject(ProductCategory productCategory);

@Select("select * from product_category where category_type = #{categoryType}")
@Results({
@Result(column = "category_id", property = "categoryId"),
@Result(column = "category_name", property = "categoryName"),
@Result(column = "category_type", property = "categoryType")
})
ProductCategory findByCategoryType(Integer categoryType);

@Select("select * from product_category where category_name = #{categoryName}")
@Results({
@Result(column = "category_id", property = "categoryId"),
@Result(column = "category_name", property = "categoryName"),
@Result(column = "category_type", property = "categoryType")
})
List<ProductCategory> findByCategoryName(String categoryName);

@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByCategoryType(@Param("categoryName") String categoryName,
@Param("categoryType") Integer categoryType);

@Update("update product_category set category_name = #{categoryName} where category_type = #{categoryType}")
int updateByObject(ProductCategory productCategory);

@Delete("delete from product_category where category_type = #{categoryType}")
int deleteByCategoryType(Integer categoryType);

ProductCategory selectByCategoryType(Integer categoryType);
}

mapper单元测试

ProductCategoryMapperTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.imooc.dataobject.mapper;

import com.imooc.dataobject.ProductCategory;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class ProductCategoryMapperTest {

@Autowired
private ProductCategoryMapper mapper;

@Test
public void insertByMap() throws Exception {
Map<String, Object> map = new HashMap<>();
map.put("categoryName", "师兄最不爱");
map.put("category_type", 101);
int result = mapper.insertByMap(map);
Assert.assertEquals(1, result);
}

@Test
public void insertByObject() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryName("师兄最不爱");
productCategory.setCategoryType(102);
int result = mapper.insertByObject(productCategory);
Assert.assertEquals(1, result);
}

@Test
public void findByCategoryType() {
ProductCategory result = mapper.findByCategoryType(102);
Assert.assertNotNull(result);
}

@Test
public void findByCategoryName() {
List<ProductCategory> result = mapper.findByCategoryName("师兄最不爱");
Assert.assertNotEquals(0, result.size());
}

@Test
public void updateByCategoryType() {
int result = mapper.updateByCategoryType("师兄最不爱的分类", 102);
Assert.assertEquals(1, result);
}

@Test
public void updateByObject() {
ProductCategory productCategory = new ProductCategory();
productCategory.setCategoryName("师兄最不爱");
productCategory.setCategoryType(102);
int result = mapper.updateByObject(productCategory);
Assert.assertEquals(1, result);
}

@Test
public void deleteByCategoryType() {
int result = mapper.deleteByCategoryType(102);
Assert.assertEquals(1, result);
}

@Test
public void selectByCategoryType() {
ProductCategory productCategory = mapper.selectByCategoryType(101);
Assert.assertNotNull(productCategory);
}

}

Dao层

ProductCategoryDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.imooc.dataobject.dao;

import com.imooc.dataobject.mapper.ProductCategoryMapper;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

public class ProductCategoryDao {

@Autowired
ProductCategoryMapper mapper;

public int insertByMap(Map<String, Object> map) {
return mapper.insertByMap(map);
}
}

对象转换

Form表单对象(Json)转成DTO

OrderForm2OrderDTOConverter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.imooc.converter;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.imooc.dataobject.OrderDetail;
import com.imooc.dto.OrderDTO;
import com.imooc.enums.ResultEnum;
import com.imooc.exception.SellException;
import com.imooc.form.OrderForm;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class OrderForm2OrderDTOConverter {

public static OrderDTO convert(OrderForm orderForm) {
Gson gson = new Gson();
OrderDTO orderDTO = new OrderDTO();

orderDTO.setBuyerName(orderForm.getName());
orderDTO.setBuyerPhone(orderForm.getPhone());
orderDTO.setBuyerAddress(orderForm.getAddress());
orderDTO.setBuyerOpenid(orderForm.getOpenid());

List<OrderDetail> orderDetailList = new ArrayList<>();
try {
orderDetailList = gson.fromJson(orderForm.getItems(),
new TypeToken<List<OrderDetail>>() {
}.getType());
} catch (Exception e) {
log.error("【对象转换】错误, string={}", orderForm.getItems());
throw new SellException(ResultEnum.PARAM_ERROR);
}
orderDTO.setOrderDetailList(orderDetailList);

return orderDTO;
}
}

Data object转DTO

OrderMaster2OrderDTOConverter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.imooc.converter;

import com.imooc.dataobject.OrderMaster;
import com.imooc.dto.OrderDTO;
import org.springframework.beans.BeanUtils;
import java.util.List;
import java.util.stream.Collectors;

public class OrderMaster2OrderDTOConverter {

public static OrderDTO convert(OrderMaster orderMaster) {
OrderDTO orderDTO = new OrderDTO();
BeanUtils.copyProperties(orderMaster, orderDTO);
return orderDTO;
}

public static List<OrderDTO> convert(List<OrderMaster> orderMasterList) {
return orderMasterList.stream().map(e ->
convert(e)
).collect(Collectors.toList());
}
}

中文字符乱码

application.properties

1
2
3
4
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

IDEA设置

网页模板

Thymeleaf

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

...

<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>

index.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head lang="en">
<meta charset="UTF-8"/>
<title></title>
</head>
<body>
<h1 th:text="${host}">Hello World</h1>
</body>
</html>

注意:模板的位置可以直接放在src/main/resources/templates/目录下。

application.properties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8

# Enable template caching.
spring.thymeleaf.cache=true
# Check that the templates location exists.
spring.thymeleaf.check-template-location=true
# Content-Type value.
spring.thymeleaf.content-type=text/html
# Enable MVC Thymeleaf view resolution.
spring.thymeleaf.enabled=true
# Template encoding.
spring.thymeleaf.encoding=UTF-8
# Comma-separated list of view names that should be excluded from resolution.
spring.thymeleaf.excluded-view-names=
# Template mode to be applied to templates. See also StandardTemplateModeHandlers.
spring.thymeleaf.mode=HTML5
# Prefix that gets prepended to view names when building a URL.
spring.thymeleaf.prefix=classpath:/templates/
# Suffix that gets appended to view names when building a URL.
spring.thymeleaf.suffix=.html

ThymeleafTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.mindex.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ThymeleafTest {

@RequestMapping("/")
public String index(ModelMap map) {
map.addAttribute("host", "http://www.mindex.com");
return "index";
}
}

运行结果如下:

数据库操作

JdbcTemplate

引入POM依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>

修改配置文件application.properties

1
2
3
4
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=welcome
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Service接口:UserService.java

1
2
3
4
5
6
7
8
9
10
11
12
package com.mindex.service;

public interface UserService {

void create(String name, Integer age);

void deleteByName(String name);

Integer getAllUsers();

void deleteAllUsers();
}

Service实现:UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.mindex.service.impl;

import com.mindex.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void create(String name, Integer age) {
jdbcTemplate.update("INSERT INTO user (name, age) VALUES (?,?);", name, age);
}

@Override
public void deleteByName(String name) {
jdbcTemplate.update("delete from USER where NAME = ?", name);
}

@Override
public Integer getAllUsers() {
return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
}

@Override
public void deleteAllUsers() {
jdbcTemplate.update("delete from USER");
}
}

单元测试:UserServiceImplTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.mindex.service.impl;

import com.mindex.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class UserServiceImplTest {

@Autowired
private UserService userService;

@Test
public void create() throws Exception {
// 插入5个用户
userService.create("a", 1);
userService.create("b", 2);
userService.create("c", 3);
userService.create("d", 4);
userService.create("e", 5);

Assert.assertEquals(5, userService.getAllUsers().intValue());
}

@Test
public void deleteByName() throws Exception {
userService.deleteByName("b");
userService.deleteByName("c");

Assert.assertEquals(3, userService.getAllUsers().intValue());
}

@Test
public void getAllUsers() throws Exception {
}

@Test
public void deleteAllUsers() throws Exception {
}
}

Spring-data-jpa

引入pom依赖:pom.xml

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

application.properties创建数据库连接信息。

1
2
3
4
5
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123qweasd
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop

spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:

  • create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
  • create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
  • update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
  • validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

创建实体
创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.mindex.entities;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Data
@Entity
public class User {

@Id
@GeneratedValue
private Long id;

@Column(nullable = false)
private String name;

@Column(nullable = false)
private Integer age;

public User(String name, Integer age) {
this.name = name;
this.age = age;
}

public User() {
}
}

创建数据访问接口
下面针对User实体创建对应的Repository接口实现对该实体的数据访问,如下代码:UserRepository.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.mindex.repository;

import com.mindex.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;


public interface UserRepository extends JpaRepository<User, Long> {

User findByName(String name);

User findByNameAndAge(String name, Integer age);

@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);

}

在Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。

下面对上面的UserRepository做一些解释,该接口继承自JpaRepository,通过查看JpaRepository接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。

在我们实际开发中,JpaRepository接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,在这里先抛砖引玉简单介绍一下spring-data-jpa中让我们兴奋的功能,后续再单独开篇讲一下spring-data-jpa中的常见使用。

在上例中,我们可以看到下面两个函数:

  • User findByName(String name)
  • User findByNameAndAge(String name, Integer age)

它们分别实现了按name查询User实体和按name和age查询User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询

除了通过解析方法名来创建查询外,它也提供通过使用@Query 注解来创建查询,您只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。

Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以整合介绍为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如@Modifying操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开,这里先挖个坑,后续再补文章填坑,如您对这些感兴趣可以关注我博客或简书,同样欢迎大家留言交流想法。

单元测试
在完成了上面的数据访问接口之后,按照惯例就是编写对应的单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.mindex.repository;

import com.mindex.entities.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Test
public void test() throws Exception {
// 创建10条记录
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));

// 测试findAll, 查询所有记录
Assert.assertEquals(10, userRepository.findAll().size());

// 测试findByName, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());

// 测试findUser, 查询姓名为FFF的User
Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());

// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());

// 测试删除姓名为AAA的User
userRepository.delete(userRepository.findByName("AAA"));

// 测试findAll, 查询所有记录, 验证上面的删除是否成功
Assert.assertEquals(9, userRepository.findAll().size());
}

}

集成Redis

手动配置集成Redis

引入POM依赖:pom.xml

1
2
3
4
5
<!--此处并不是直接使用spring提供的redis-starter-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>

外部配置文件:application.yaml

1
2
3
4
5
6
7
8
9
jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true

Java配置类(替代传统xml):RedisConfig.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.mindex.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@Data
@Component
public class RedisConfig {

@Bean("jedis.config")
public JedisPoolConfig jedisPoolConfig(@Value("${jedis.pool.min-idle}") int minIdle,
@Value("${jedis.pool.max-idle}") int maxIdle,
@Value("${jedis.pool.max-wait}") int maxWaitMillis,
@Value("${jedis.pool.block-when-exhausted}") boolean blockWhenExhausted,
@Value("${jedis.pool.max-total}") int maxTotal) {

JedisPoolConfig config = new JedisPoolConfig();
config.setMinIdle(minIdle);
config.setMaxIdle(maxIdle);
config.setMaxWaitMillis(maxWaitMillis);
config.setMaxTotal(maxTotal);
// 连接耗尽时是否阻塞, false报异常,ture阻塞直到超时, 默认true
config.setBlockWhenExhausted(blockWhenExhausted);
// 是否启用pool的jmx管理功能, 默认true
config.setJmxEnabled(true);
return config;
}

@Bean
public JedisPool jedisPool(@Qualifier("jedis.config") JedisPoolConfig config,
@Value("${jedis.host}") String host,
@Value("${jedis.port}") int port) {
return new JedisPool(config, host, port);
}
}

Service接口定义:RedisService.java

1
2
3
4
5
6
7
8
9
package com.mindex.service;

public interface RedisService {

String get(String key);

boolean set(String key, String val);

}

Service接口实现类:RedisServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.mindex.service.impl;

import com.mindex.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@Service
public class RedisServiceImpl implements RedisService {

// 此处直接注入即可
@Autowired
private JedisPool jedisPool;

@Override
public String get(String key) {
Jedis jedis = this.jedisPool.getResource();
String ret;
try {
ret = jedis.get(key);
} finally {
if (jedis != null)
jedis.close();
}
return ret;
}

@Override
public boolean set(String key, String val) {
Jedis jedis = this.jedisPool.getResource();
try {
return "OK".equals(jedis.set(key, val));
} finally {
if (jedis != null)
jedis.close();
}
}

}

测试:RedisServiceImplTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.mindex.service.impl;

import com.mindex.service.RedisService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisServiceImplTest {

@Autowired
private RedisService redisService;

@Test
public void testGet() {
// test set
boolean status = this.redisService.set("foo", "bar");
Assert.assertTrue(status);

// test get
String str = this.redisService.get("foo");
Assert.assertEquals("bar", str);
}
}

在Redis中检查结果

使用spring-boot-starter-data-redis

外部配置文件:application.yaml

1
2
3
4
5
6
7
8
9
jedis:
host: 127.0.0.1
port: 6379
pool:
max-idle: 300
min-idle: 10
max-total: 600
max-wait: 1000
block-when-exhausted: true

配置类:RedisConfig1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.mindex.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig1 {

@Autowired
private RedisConnectionFactory redisConnectionFactory;

/**
* 实例化 RedisTemplate 对象
*
*/
@Bean
public RedisTemplate<String, Object> functionDomainRedisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
this.initRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}

/**
* 序列化设置
*/
private void initRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setConnectionFactory(factory);
}

@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}

@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}

@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}

@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}

@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}

}

简单测试:RedisAnotherConfigTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.mindex;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.test.context.junit4.SpringRunner;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisAnotherConfigTest {

@Autowired
private ValueOperations<String, Object> valueOperations;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Test
public void contextLoads() {
}

@Test
public void testStringOps() {
this.valueOperations.set("k1", "spring-redis");

Boolean hasKey = this.redisTemplate.hasKey("k1");
assertEquals(true, hasKey);

Object str = this.valueOperations.get("k1");
assertNotNull(str);
assertEquals("spring-redis", str.toString());
}
}

邮件

使用JavaMailSender发送邮件

引入POM依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>

增加配置:application.properties

注意:这里的password是邮箱授权码,不是邮箱密码。

1
2
3
4
5
6
spring.mail.host=smtp.qq.com
spring.mail.username=85648606@qq.com
spring.mail.password=clqpsraiifwqbidg
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.properties.mail.smtp.starttls.required=true

单元测试:MailTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.mindex;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MailTest {

@Autowired
private JavaMailSender mailSender;

@Test
public void sendSimpleMail() throws Exception {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("85648606@qq.com");
message.setTo("wfgdlut@msn.com");
message.setSubject("主题:简单邮件");
message.setText("测试邮件内容");

mailSender.send(message);
}
}

发送html格式邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void sendAttachmentsMail() throws Exception {

MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【HTML】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);
// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");

}

发送包含内嵌图片的邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 发送包含内嵌图片的邮件
*
* @throws Exception
*/
@Test
public void sendAttachedImageMail() throws Exception {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// multipart模式
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true);
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【图片】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p>");
// cid为固定写法,imageId指定一个标识
sb.append("<img src=\"cid:imageId\"/></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);

// 设置imageId
FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
mimeMessageHelper.addInline("imageId", img);

// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");
}

发送包含附件的邮件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 发送包含附件的邮件
* @throws Exception
*/
@Test
public void sendAttendedFileMail() throws Exception {
MimeMessage mimeMessage = mailSender.createMimeMessage();
// multipart模式
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, true, "utf-8");
mimeMessageHelper.setFrom("85648606@qq.com");
mimeMessageHelper.setTo("wfgdlut@msn.com");
mimeMessageHelper.setSubject("Spring Boot Mail 邮件测试【附件】");

StringBuilder sb = new StringBuilder();
sb.append("<html><head></head>");
sb.append("<body><h1>spring 邮件测试</h1><p>hello!this is spring mail test。</p></body>");
sb.append("</html>");

// 启用html
mimeMessageHelper.setText(sb.toString(), true);
// 设置附件
FileSystemResource img = new FileSystemResource(new File("/Users/kwang/IdeaProjects/SpringBootDemoProject/src/main/resources/static/test.png"));
mimeMessageHelper.addAttachment("test.png", img);

// 发送邮件
mailSender.send(mimeMessage);

System.out.println("邮件已发送");
}

本文标题:Spring Boot 学习笔记 - 钢钢更新

文章作者:王方钢 / Kenny Wang

发布时间:2018年04月29日 - 14:04

最后更新:2018年06月27日 - 12:06

原始链接:https://wangfanggang.com/Java/Spring-Boot/springboot/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

王方钢 / Kenny Wang wechat
王方钢 | 智叟网络
0%