前言
本次示例的场景是下订单减库存。
用到了springboot+springcloud+mybatis+mysql。
数据库有订单表和库存表
springboot整合mybatis和mysql的详细讲解可以看此博客
https://blog.csdn.net/daziyuanazhen/article/details/100822121
创建eureka注册中心项目
添加依赖
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>lcneureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lcneureka</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
修改application.properties配置文件
spring.application.name=eureka
server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://localhost:8080/eureka
修改springboot入口类
@SpringBootApplication
@EnableEurekaServer
public class LcneurekaApplication {
public static void main(String[] args) {
SpringApplication.run(LcneurekaApplication.class, args);
}
}
创建订单项目
添加依赖
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>lcnservicea</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>lcnservicea</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- mybatis依赖 begin -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mybatis依赖 end -->
<!-- mysql数据库配置 begin -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mysql数据库配置 end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
修改application.properties配置文件
server.port=8081
spring.application.name=service-order
eureka.client.service-url.defaultZone=http://localhost:8080/eureka
##数据库配置
##数据库地址
spring.datasource.url=jdbc:mysql://localhost:3306/blog?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
##数据库用户名
spring.datasource.username=root
##数据库密码
spring.datasource.password=123456
##数据库驱动
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
在resources文件夹下创建配置文件
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Integer" type="java.lang.Integer" />
<typeAlias alias="Long" type="java.lang.Long" />
<typeAlias alias="HashMap" type="java.util.HashMap" />
<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
<typeAlias alias="ArrayList" type="java.util.ArrayList" />
<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
</configuration>
OrderMapper.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.lcnservicea.OrderMapper">
<insert id="addOrder">
INSERT INTO o_order VALUE('asd','2019-09-13 21:14:00')
</insert>
</mapper>
创建业务文件
新建库存服务的feign调用文件 ServiceBFeign.java
package com.example.lcnservicea;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(value="service-stock")
public interface ServiceBFeign {
@RequestMapping(value = "/reduceStock",method = RequestMethod.GET)
void reduceStock();
}
新建OrderMapper.java
package com.example.lcnservicea;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OrderMapper {
void addOrder();
}
新建OrderService.java
package com.example.lcnservicea;
public interface OrderService {
void addOrder();
}
新建OrderServiceImpl.java
package com.example.lcnservicea;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = false,rollbackFor = Exception.class)
public class OrderServiceImpl implements OrderService {
@Autowired
private ServiceBFeign serviceBFeign;
@Autowired
private OrderMapper orderMapper;
@Override
public void addOrder() {
orderMapper.addOrder();
System.out.println("添加订单成功");
serviceBFeign.reduceStock();
System.out.println("调用库存服务减库存成功");
System.out.println("出现异常,事务回滚");
int i = 1/0;
}
}
新建OrderController.java
package com.example.lcnservicea;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/addOrder")
public String addOrder(){
orderService.addOrder();
return "ok";
}
}
修改springboot入口类
package com.example.lcnservicea;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class LcnserviceaApplication {
public static void main(String[] args) {
SpringApplication.run(LcnserviceaApplication.class, args);
}
}
创建库存项目
跟订单项目相似,不同的是没有引入feign,业务文件也不一样
StockMapper.xml
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.lcnserviceb.StockMapper">
<update id="reduceStock">
UPDATE s_stock SET stockNum = stockNum - 1
</update>
</mapper>
StockServiceImpl.java
package com.example.lcnserviceb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = false,rollbackFor = Exception.class)
public class StockServiceImpl implements StockService {
@Autowired
private StockMapper stockMapper;
@Override
public void reduceStock() {
stockMapper.reduceStock();
System.out.println("减库存成功。。。。");
}
}
StockController.java
package com.example.lcnserviceb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class StockController {
@Autowired
private StockService stockService;
@RequestMapping("/reduceStock")
public void reduceStock(){
stockService.reduceStock();
}
}
springboot入口类
package com.example.lcnserviceb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class LcnservicebApplication {
public static void main(String[] args) {
SpringApplication.run(LcnservicebApplication.class, args);
}
}
到此,项目的准备就完成了,接下来
先启动注册中心服务,然后启动订单服务和库存服务。
打开浏览器访问http://localhost:8081/order/addOrder
可以发现订单项目报了/ by zero的异常,库存项目无异常。
打开数据库发现订单数据库并没有新增一条记录
库存数据库库存减了1,变成99
说明订单服务调用了库存服务后,库存服务正常运行,减了库存1,之后订单服务由于出现了/ by zero异常导致事务回滚,没有添加订单成功,这就出现了分布式事务问题,导致了少卖问题(即没有添加订单,而库存少了)。