1.集成测试
1.集成测试正常下单
1.步骤

2.浏览器访问 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100





3.注意事项和细节

2.集成测试模拟异常
1.步骤
1.com/sun/springcloud/controller/StorageController.java 休眠12s,模拟openfeign超时

2.仍然按照正常步骤启动并测试

3.预测结果
- 保存订单,扣减账户余额,扣减库存成功!但是修改订单状态失败,也就是status是0!


2.执行前数据库状态
order表

account表

storage表

3.浏览器输入 http://localhost:10008/order/save?userId=666&productId=1&nums=2&money=200,12s后查看数据库状态
order表

account表(这里在写sql时并没有计算数量,所以确实应该减去200)

storage表(库存减2)

3.集成测试分布式事务控制
1.在com/sun/springcloud/service/Impl/OrderServiceImpl.java 的save方法加上@GlobalTransactional进行分布式事务控制

2.测试步骤

3.全部启动后查看nacos注册情况

4.测试前数据库状态



5.浏览器请求 http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100




5.注意事项

2.seata分布式事务总结
1.工作机制

2.seata的使用流程
1.安装
2.修改 D:\seata\conf\file.conf文件
1.修改事务组(需要进行分布式事务控制的微服务在同一组)

2.修改日志存储模式为db

3.修改数据库(MySQL5.7)连接信息

3.修改registry.conf 配置注册中心nacos(seata需要注册到nacos)


4.创建seata数据库和表
1.创建seata数据库
create database seata;
use seata;
2.复制db_store.sql的内容,创建需要的表


5.启动nacos和seata进行测试,seata会注册到nacos
6.为每个需要分布式事务控制的数据库执行 db_undo_log.sql,创建undo_log表,用于事务回滚

7.pom.xml引入依赖
- 注意这里引入的nacos是nacos-discovery starter,没有整合Seata之前引入的跟这个不一样!!!
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>e_commerce_center-common-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
8.application.yml
server:
port: 10010
spring:
application:
name: seata-storage-micor-service
cloud:
alibaba:
seata:
tx-service-group: sun_order_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource:
driver-class-name: com.mysql.jdbc.Driver
url:
username:
password:
logging:
level:
io:
seata: info
mybatis:
mapperLocations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
9.D:\seata\conf下的两个配置文件复制到src/main/resources下并修改
1.将file.conf复制到src/main/resources下,然后修改这行,seata的服务ip+端口
以及db
根据实际情况修改(前面是修改过的)

2.registry.conf复制到src/main/resources下,然后配置注册中心nacos(前面也配过,直接复制即可)

3.最终的文件目录

10.进行业务逻辑编写
1.创建实体类
- 如果数据库中的数据是sun_name则在实体类中可以编写为sunName,不过这样需要在application.yml中开启自动驼峰命名(上面配置了)
- 这个就相当于在每个Mapper.xml中加了一个resultMap映射,如果实体类属性和表的字段可以被自动驼峰命名所映射那么就不需要再写resultMap
2.dao层
- Mapper接口注入容器
- Mapper接口的@Param注解一旦指定,就不需要在xml实现的时候指定参数类型了,直接使用#{}来取出数据即可
- Mapper.xml在application.yml中已经配置了自动扫描
3.service层
- service接口编写
- service实现类注入容器
4.controller层
- 注入容器
- 在微服务中如果使用
GetMapping
则参数需要添加@RequestParam
- 如果使用
PostMapping
则参数需要添加@RequestBody
11.两个常规配置类
1.MyBatisConfig.java 配置类,依赖注入所有Mapper接口(不用加@Mapper注解了)
package com.sun.springcloud.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@MapperScan("com.sun.springcloud.dao")
@Configuration
public class MyBatisConfig {
}
2.DataSourceProxyConfig.java 配置数据源代理为 seata
- 这里需要注意:配置文件中的mybatis.mapperLocations是以驼峰命名的
package com.sun.springcloud.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)
throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean =
new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSourceProxy);
sqlSessionFactoryBean.setMapperLocations
(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTransactionFactory
(new SpringManagedTransactionFactory());
return sqlSessionFactoryBean.getObject();
}
}
12.创建主启动类(示例)
package com.sun.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
@EnableFeignClients
public class SeataStorageMicroServiceApplication10010 {
public static void main(String[] args) {
SpringApplication.run(SeataStorageMicroServiceApplication10010.class, args);
}
}
13.OpenFeign远程调用细节
1.在前面的依赖中已经引入了OpenFeign,并且在主启动类中也开启了Feign客户端
2.service接口声明需要远程调用的controller
- 这里的FeignClient可以进行服务发现,得到要调用的服务的ip+端口+上下文路径
- 这里的RequestMapping可以找到远程调用的服务的资源路径,与服务发现的路径进行拼接即可找到指定资源
- 具体声明的方式就是
- 添加@FeignClient注解,指定要远程调用服务的application-name(注意不能带_)
- 将需要调用的controller直接复制过来,然后去掉方法体即可
package com.sun.springcloud.service;
import com.sun.springcloud.util.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "seata-storage-micor-service")
public interface StorageService {
@RequestMapping("/storage/reduce")
public Result reduce(Long productId, Integer nums);
}
3.远程调用的方式
- 通过依赖注入针对service接口的代理对象进行远程调用
4.谁可以远程调用