创建一个maven项目
初始化父工程
- 创建父工程, 工程名称, AarifactId, 包。
- 父类工程中的pom中添加引入依赖。
<?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>cn.itcast</groupId>
<artifactId>spring_cloud_demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>product_service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</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>
<!-- spring boot 基本依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot 日志依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- spring boot 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- hutool快捷开发工具依赖,会有很多方便的util类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.4.1</version>
</dependency>
<!-- lombok注解依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- spring cloud依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 仓库配置 -->
<!-- <repositories>-->
<!-- <repository>-->
<!-- <id>spring-snapshots</id>-->
<!-- <name>Spring Snapshots</name>-->
<!-- <url>http://repo.spring.io/libs-snapshot-local</url>-->
<!-- <snapshots></snapshots>-->
<!-- </repository>-->
<!-- </repositories>-->
</project>
product子模块创建
该模块名为product-service,提供product相关的一些接口,用于产品相关的业务逻辑,例如产品的增删改查等及产品周边的业务。子工程可以直接用父工程的大部分依赖。
2. 添加子工程依赖
因为子工程需要连接mysql数据库做查询,所以需要mysql的连接支持依赖和jpa数据库访问依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
注:父工程不需要src文件夹,因为父工程基本上都是依赖配置,不会有相应代码
product子模块创建
1. 创建产品服务的module
该模块名为product-service,提供product相关的一些接口,用于产品相关的业务逻辑,例如产品的增删改查等及产品周边的业务。子工程可以直接用父工程的大部分依赖。
2. 添加子工程依赖
因为子工程需要连接mysql数据库做查询,所以需要mysql的连接支持依赖和jpa数据库访问依赖
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
3. 创建子模块规范结构
主要包名为cn.itcast.product,实体类放在entity包下,持久层类放在dao包下,业务层放在service包下,API提供调用类放在controller包下。
4. 创建数据库及相关表
创建一个叫做shop的数据库,并且创建一张tb_product的产品表
create table tb_product (
id int(11) NOT NULL AUTO_INCREMENT,
product_name varchar(40) DEFAULT NULL COMMENT 'name',
status int(2) DEFAULT NULL COMMENT 'status',
price decimal(10,2) DEFAULT NULL COMMENT 'price per product',
prodcut_desc varchar(255) DEFAULT NULL COMMENT 'description for product',
caption varchar(255) DEFAULT NULL COMMENT 'title',
inventory int(11) DEFAULT NULL COMMENT 'current count for product',
primary key (id)
) engine=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
5. 创建实体类
entity包下面创建Product实体类,
- 需要在类上面加@Entity注解标明他是一个实体类。
- 需要在类上面加@Table注解连接他的实体表。
- 使用lombok包的@Data注解类,使其有get/set/toString等方法的自动生成。
- 主键标注为@ID,并且@GeneratedValue(strategy = GenerationType.IDENTITY) 主键由数据库自动生成(主要是自动增长型)
import java.math.BigDecimal;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
/**
* 商品实体类
*/
@Data
@Entity
@Table(name="tb_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private Integer status;
private BigDecimal price;
private String productDesc;
private String caption;
private Integer inventory;
}
6. 创建Dao数据持久接口
- 该dao层为interface
- 继承了JpaRepository 用于数据库访问,范型键为产品类Product,值为产品类主键类型Long
- 还要继承JpaSpecificationExecutor,范型为产品类Product
两者均提供了一些基本的查询方法,如findAll(), findAll(Sort), save(Iterable), delete(Id)
import cn.itcast.product.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
* 商品持久层DAO
* 继承
*/
public interface ProductDao extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
}
7. 创建serivce层接口和实现类
- 接口service
import cn.itcast.product.entity.Product;
public interface ProductService {
Product findById(Long Id);
void save(Product product);
void update(Product product);
void delete(Long Id);
}
- 实现类
- 使用@Serivce注解类表明是一个service,并且交给容器管理。
- 实现Serivce接口并实现其所有方法
- 注入Dao接口,因为Serivce类中需要使用dao操作数据库
import cn.itcast.product.dao.ProductDao;
import cn.itcast.product.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ProductServiceimpl implements ProductService {
@Autowired
private ProductDao productDao;
@Override public Product findById(final Long Id) {
return productDao.findById(Id).get();
}
@Override public void save(final Product product) {
productDao.save(product);
}
@Override public void update(final Product product) {
productDao.save(product);
}
@Override public void delete(final Long Id) {
productDao.deleteById(Id);
}
}
8. 创建Controler层和类
- 需要使用@RestController注解类,表示该类为一个controller类,并交给容器管理
- @RequestMapping注解标注,表示基本访问路径是什么
- 注入ProductService接口,因为需要使用service做业务逻辑。
import cn.itcast.product.entity.Product;
import cn.itcast.product.serivce.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productService;
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
return productService.findById(id);
}
@RequestMapping(value = "", method = RequestMethod.POST)
public String save(@RequestBody Product product) {
productService.save(product);
return "save successfully";
}
}
9. 创建springboot启动类
在product包最外层创建启动类。
- 注解@SpringBootApplication表示该类是spring boot的Application启动类
- 注解@EntityScan代表我要扫描的实体类注解
- 配置启动main方法。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
@SpringBootApplication
@EntityScan("cn.itcast.product.entity")
public class ProductApplication {
public static void main(String[] args)
{
SpringApplication.run(ProductApplication.class, args);
}
}
10. 创建配置文件
在resources包下面我们需要增加一个application.yml配置文件,用于项目启动。
- server.port=8081
- application.name=service-product
- datasource 数据源配置见下图
- jpa配置数据库类型,是否show sql是否open-in-view
server:
port: 8081
spring:
application:
name: service-product
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shop?useUnicode=true&characterEncoding=utf8
username: root
password: root
jpa:
database: MySQL
show-sql: true
open-in-view: true
11. 启动测试
启动spring boot的启动类,解决启动时候出现的问题,如果启动成功,测试controller的方法。
Order子模块创建
按照product子模块的创建方式,创建一个Order子模块
- 创建产品服务的module
- 添加子工程依赖
- 创建子模块规范结构
- 创建数据实体表
- 创建实体类
- 创建Dao数据持久接口
- 创建serivce层接口和实现类
- 创建Controler层和类
- 创建springboot启动类
- 创建配置文件
注意: 因为order服务也需要使用到product实体对象,所以我们需要拷贝一份实体对象,但是不需要设置数据库连接等注解, 例如@Entity和@Table。
模块间通讯
因为根据业务需要,我们要订单服务模块访问到产品服务模块,这就涉及到了两个模块之间的通讯方式。如何让订单服务模块调用到商品服务模块? 我们是通过在订单服务模块中直接调用商品服务模块的rest api
以下都是模块间通讯的方式:
- Java中自带的HttpURLConenction
- Apache HttpComponents
- OKHttp
- 其他url调用工具、
- Spring提供的RestTemplate
介绍: 他是Spring框架提供的一套可用于在应用中调用rest服务的类,他简化了与http服务的通信方式,统一了RESTful的标准,封装了http链接,我们只需要传入url及返回值类型就可以了。
RestTemplate默认依赖JDK提供的http连接能力(HttpURLConenction),如果又需要的话 也能通过setRequestFactory方法替换为Apache HttpComponents,Netty或者OKHttp等其他的HTTP library。
该模版类的主要切入点为以下几个方法(并对应着HTTP的六个主要方法):
- RestTemplate的使用
在order服务的入口类中创建RestTemplate对象
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EntityScan("cn.itcast.order.entity")
public class OrderApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args)
{
SpringApplication.run(OrderApplication.class, args);
}
}
-
在OrderContoller使用的时候调用对应的方法完成操作
import cn.itcast.order.entity.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
public RestTemplate restTemplate;
@RequestMapping(value = "/buy/{id}", method = RequestMethod.GET)
public Product findById(@PathVariable Long id) {
Product product = null;
product = restTemplate.getForObject("http://127.0.0.1:8081/product/" + id,Product.class);
return product;
}
}
测试跨模块调用
启动两个模块, 访问 http://localhost:8082/order/buy/3 获得下面输入表示模块间通讯正常
{"id":3,"productName":"book","status":1,"price":12.30,"productDesc":"the good book in my local database","caption":"methodway to go","inventory":300}
手动搭建所包含的问题
- 调用地址硬编码到了微服务的java代码中。
- 正式应用的时候微服务会有一大堆,调用方端需要记录维护每一个其他微服务的地址。增加了开发难度
- 微服务调用做负载均衡会有问题。
- 调用关系链路复杂,导致追踪困难
解决办法:
1. 加入API网关
2. 统一整个系统的配置统一管理
3. 链路追踪
附录
lombok注解说明
注解名称 | 功能 |
---|---|
@Setter | 自动添加类中所有属性相关的 set 方法 |
@Getter | 自动添加类中所有属性相关的 get 方法 |
@Builder | 使得该类可以通过 builder (建造者模式)构建对象 |
@RequiredArgsConstructor | 生成一个该类的构造方法,禁止无参构造 |
@ToString | 重写该类的toString() 方法 |
@EqualsAndHashCode | 重写该类的equals() 和hashCode() 方法 |
@Data | 等价于上面的@Setter 、@Getter 、@RequiredArgsConstructor 、@ToString 、@EqualsAndHashCode |
JpaRepository说明
JpaRepository 中有很多基础查询方法,他还继承了PagingAndSortingRepository类实现了分页操作。
而PagingAndSortingRepository还继承于CrudRepository, 这里面也提供了大量的数据库基本查询方法。
如count(), deleteById(ID var1), delete(T var1), deleteAll(Iterable<? extends T> var1), deleteAll()
findAll(); findById(ID var1); existsById(ID var1), findAllById(Iterable var1),
save(S var1)
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S var1);
<S extends T> Iterable<S> saveAll(Iterable<S> var1);
Optional<T> findById(ID var1);
boolean existsById(ID var1);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> var1);
long count();
void deleteById(ID var1);
void delete(T var1);
void deleteAll(Iterable<? extends T> var1);
void deleteAll();
}
JPA提供的四种GenerationType
- TABLE:使用一个特定的数据库表格来保存主键。
- SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
- IDENTITY:主键由数据库自动生成(主要是自动增长型)
- AUTO:主键由程序控制。