《Spring Cloud与Docker微服务架构实战》周立 --笔记
学习完《Spring Cloud与Docker微服务架构实战》第3章后,用MySQL+Mybatis持久层框架实现了原书中代码。还是以电影售票系统为例,电影服务通过调用用户服务判断是否符合买票标准。
在eclipse中新建2个Spring Boot工程,分别为服务提供者,服务消费者springcloud-consumer-movie,具体如下:
服务提供者springcloud-provider-user–用户服务
工程目录
大家命名可随意,我这里是为了实现一个服务提供者而命名。
引入所需依赖–pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springcloud-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 引入spring boot的依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- 提供SpringMVC支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 持久层框架,mybatis整合到Spring Boot中 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Actuator监控功能:把收集到的消息都可暴露给JMX -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
准备建表的.sql文件
存储在classpath下,我放在classpath下自建的sql文件夹。
create table if not exists users(
id bigint not null PRIMARY KEY auto_increment,
username varchar(40) not null,
name varchar(20) not null,
age int(3) not null,
balance decimal(10,2) not null
);
建不同的包,编写相应的类
顶层包com.xym.cloud
(1) 编写启动类ProviderUserApplication:
package com.xym.cloud;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.xym.cloud.mapper")
public class ProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderUserApplication.class, args);
}
}
(2) 编写用于初始化数据表的DatabaseInitializer类
建立好了users表之后,往表中存入数据,便于模拟测试。
package com.xym.cloud;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@Component
public class DatabaseInitializer {
@Autowired
JdbcTemplate jdbcTemplate;
@PostConstruct
public void init() {
jdbcTemplate.update("insert into users (username,name,age,balance) values ('account1','Alice',20,100.00);");
jdbcTemplate.update("insert into users (username,name,age,balance) values ('account2','Bob',28,180.00);");
jdbcTemplate.update("insert into users (username,name,age,balance) values ('account3','Cat',34,280.00);");
}
}
该类之所以可以进行数据库表的初始化设置,因为在SpringIoC容器初始化该Bean时,会执行@PostConstruct标注的方法,从而实现初始化。
子包com.xym.cloud.entity
建立JavaBean–User ,可以数据库记录相互映射。
package com.xym.cloud.entity;
import java.math.BigDecimal;
public class User {
private Long id;
private String username;
private String name;
private Integer age;
private BigDecimal balance;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public BigDecimal getBalance() {
return balance;
}
public void setBalance(BigDecimal balance) {
this.balance = balance;
}
}
子包com.xym.cloud.mapper
MyBatis使用Mapper来实现映射,而且Mapper必须是接口。Mapper中定义访问users表的接口方法。 这些接口可以自己实现,但Mybatis提供了一个MapperFactoryBean来自动创建所有Mapper的实现类,只需要注意,在启动类添加注解@MapperScan(“com.xym.cloud.mapper”)指定mapper接口。
package com.xym.cloud.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import com.xym.cloud.entity.User;
@Repository
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User getById(@Param("id") Long id);
//offset--表记录的起始位置之前,maxResults--获取记录条数
@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")
List<User> getAll(@Param("offset") int offset, @Param("maxResults") int maxResults);
@Insert("INSERT INTO users (id,username,name,age,balance) VALUES (#{user.id}, #{user.username}, #{user.name}, #{user.age}, #{user.balance})")
void insert(@Param("user") User user);
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("INSERT INTO users (id,username,name,age,balance) VALUES (#{user.id}, #{user.username}, #{user.name}, #{user.age}, #{user.balance})")
void insertById(@Param("user") User user);
@Update("UPDATE users SET name = #{user.name}, balance = #{user.balance} WHERE id = #{user.id}")
void update(@Param("user") User user);
@Delete("DELETE FROM users WHERE id = #{id}")
void deleteById(@Param("id") Long id);
}
子包com.xym.cloud.service
建立UserService,利用UserMapper可以实现对数据库的增删改查。
package com.xym.cloud.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.xym.cloud.entity.User;
import com.xym.cloud.mapper.UserMapper;
@Service
@Transactional
public class UserService {
// 注入UserMapper:
@Autowired
UserMapper userMapper;
public User getUserById(long id) {
// 调用Mapper方法:
User user = userMapper.getById(id);
if (user == null) {
throw new RuntimeException("User not found by id.");
}
return user;
}
//返回多行记录
public List<User> getUsers(int pageIndex){
//调用Mapper方法
List<User> list = userMapper.getAll(0, 4);
if (list.size() == 0) {
throw new RuntimeException("User is empty.");
}
return list;
}
//执行删除
public void deleteUserById(long id) {
//调用Mapper方法
userMapper.deleteById(id);
}
//执行插入
public void insertUserById(User user) {
userMapper.insertById(user);
}
//执行更新
public void updateUserById(User user) {
userMapper.update(user);
}
}
子包com.xym.cloud.web
在浏览器侧请求数据库记录,根据URI传入的参数,进行相应的数据库查询,并返回数据库结果,为了直接可以在浏览器侧显示查询结果,UserController类的方法应该是RESR API,在该类标注@RestController即可:
package com.xym.cloud.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.xym.cloud.entity.User;
import com.xym.cloud.mapper.UserMapper;
@RestController //所有方法都是REST API
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
//@PathVariable 指示参数必须被绑定到URI
public User findById(@PathVariable Long id) {
User user = this.userMapper.getById(id);
return user;
}
}
application.yml配置
编写完所有逻辑代码后,进行配置,一定要注意,配置dataSouce的url时,需要指定一个数据库名,这个数据库要已经存在,否则会报错,我这里是mybatisDatabase,具体配置如下:
server:
port: 8000
spring:
application:
name: ${APP_NAME:unnamed}
# mysql数据库相关配置
datasource:
#############MySQL数据库:url中需要设定一个mybatisDatabase
url: jdbc:mysql://localhost:3306/mybatisDatabase?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 你的password
dirver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
schema:
- classpath:sql/department.sql
initialization-mode: always
# mybatis依赖
mybatis:
#mapper-locations: classpath:/*Mapper.xml
type-aliases-package: com.xym.cloud.entity
测试
运行启动类,在浏览器中输入:
http://localhost:8000/1
服务消费者springcloud-consumer-movie–电影服务
工程目录
和上面差不多,具体包在下面列出。
引入所需依赖–pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>springcloud-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 引入spring boot的依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<!-- 提供SpringMVC支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Actuator监控功能:把收集到的消息都可暴露给JMX -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
<!-- 引入spring cloud的依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Edgware.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
建不同的包,编写相应的类
顶层包com.xym.cloud
(1) 编写启动类ProviderUserApplication:
package com.xym.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
//先启动服务提供者再启动服务消费者
//这里采用的硬编码服务调用,一旦服务提供者地址改变,将无法调用
@SpringBootApplication
public class ConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
子包com.xym.cloud.entity
建立JavaBean–User ,同上面服务的User类。
子包com.xym.cloud.web
在浏览器侧请求访问电影服务,电影服务通过调用用户服务,查询相应结果,实现服务之间的调用。
package com.xym.cloud.web;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.xym.cloud.entity.User;
//使用RestTemplate请求用户微服务的API
@RestController
public class MovieController {
//同步客户端来执行HTTP请求
@Autowired
private RestTemplate restTemplate;
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
//getForObject(String url, Class<T> responseType)
//对指定的URL执行GET来检索指定响应
return this.restTemplate.getForObject("http://localhost:8000/"+id, User.class);
}
}
application.yml配置
配置如下:
server:
port: 8010
测试
先运行Provider启动类,再运行Consumer启动类,在浏览器中输入:
http://localhost:8010/user/1
可以发现,通过RestTemplate实现了服务之间的API调用。
总结
以上方式为硬编码方式,存在的问题是:
(1)适用场景局限:一旦服务提供者的网络地址发生改变,就会影响到服务消费者,导致服务消费者需要重新配置和发布,不可取。
(2)无法动态伸缩:生产环境下,每个微服务会部署多个实例,从而实现容灾和负载均衡。在微服务架构中,还需动态增减节点,硬编码无法适应这种需求。