SpringCloudday07
RabbitMQ和Spring boot整合
建立環境:
新建springboot工程
spring:
rabbitmq:
host: 192.168.64.140
username: admin
password: admin
真正的分散式系統,生產者和消費者應該部署在不同的伺服器,現在測試將兩個模組放到一起
簡單模式
package cn.tedu.rabbitmqboot.m1;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue helloworldQueue() {
// 非持久佇列,spring 的 Queue,預設是持久佇列
return new Queue("helloworld", false);
}
@Autowired
private Producer p;
/*
在 spring 執行完掃描,建立完所有的物件,完成所有的注入後,
會自動執行 @PostConstruct 方法
*/
@PostConstruct
public void test() {
p.send();
}
}
package cn.tedu.rabbitmqboot.m1;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
* 消費者自動啟動,註冊成為消費者
* 自動開始監聽佇列中的訊息
* 不需要手動呼叫
*
*
* @RabbitListener
* 1 在類上註解,需要 @RabbitListener 配合指定處理的方法
2 直接加在方法上
*/
@Component
public class Consumer {
@RabbitListener(queues = "helloworld")
public void recieve(String msg) {
System.out.println("收到: "+msg);
}
}
package cn.tedu.rabbitmqboot.m1;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Producer {
@Autowired
private AmqpTemplate amqpTemplate;
//需要手動呼叫,傳送訊息
public void send(){
//字串自動轉成byte[] 陣列,在傳送helloworld佇列
amqpTemplate.convertAndSend("helloworld", "Hello World!!");
System.out.println("訊息已傳送");
}
}
工作模式
package cn.tedu.rabbitmqboot.m2;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public Queue taskQueue() {
// spring 的 Queue,預設是持久佇列
return new Queue("task_queue", true);
}
@Autowired
private Producer p;
/*
在 spring 執行完掃描,建立完所有的物件,完成所有的注入後,
會自動執行 @PostConstruct 方法
*/
@PostConstruct
public void test() {
/*
p.send()中執行一個死迴圈,會阻塞spring主執行緒的執行,
要啟動一個新的執行緒執行 send()
*/
// new Thread(new Runnable() {
// @Override
// public void run() {
// p.send();
// }
// }).start();
// lambda
new Thread(() -> p.send()).start();
}
}
package cn.tedu.rabbitmqboot.m2;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫
@RabbitListener
1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
2. 直接載入方法上
*/
@Component
public class Consumer {
@RabbitListener(queues = "task_queue")
public void recieve1(String msg) {
System.out.println("消費者1收到: "+msg);
}
@RabbitListener(queues = "task_queue")
public void recieve2(String msg) {
System.out.println("消費者2收到: "+msg);
}
}
package cn.tedu.rabbitmqboot.m2;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Scanner;
@Component
public class Producer {
@Autowired
private AmqpTemplate amqpTemplate;
// 需要手動呼叫,傳送訊息
public void send() {
while (true) {
System.out.print("輸入: ");
String msg = new Scanner(System.in).nextLine();
// 預設是持久訊息
// 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
amqpTemplate.convertAndSend("task_queue", msg);
}
}
}
釋出訂閱
package cn.tedu.rabbitmqboot.m3;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public FanoutExchange logsExchange() {
// 第二個引數持久屬性,預設是 true
return new FanoutExchange("logs", false, false);
}
@Autowired
private Producer p;
@PostConstruct
public void test() {
new Thread(() -> p.send()).start();
}
}
package cn.tedu.rabbitmqboot.m3;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫
@RabbitListener
1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
2. 直接載入方法上
*/
@Component
public class Consumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "logs",declare = "false") //不重複定義這個交換機
))
public void recieve1(String msg) {
System.out.println("消費者1收到: "+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "logs",declare = "false") //不重複定義這個交換機
))
public void recieve2(String msg) {
System.out.println("消費者2收到: "+msg);
}
}
package cn.tedu.rabbitmqboot.m3;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Scanner;
@Component
public class Producer {
@Autowired
private AmqpTemplate amqpTemplate;
// 需要手動呼叫,傳送訊息
public void send() {
while (true) {
System.out.print("輸入: ");
String msg = new Scanner(System.in).nextLine();
// 預設是持久訊息
// 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
amqpTemplate.convertAndSend("logs", "", msg);
}
}
}
路由模式:
package cn.tedu.rabbitmqboot.m4;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public DirectExchange logsExchange() {
// 第二個引數持久屬性,預設是 true
return new DirectExchange("direct_logs", false, false);
}
@Autowired
private Producer p;
@PostConstruct
public void test() {
new Thread(() -> p.send()).start();
}
}
package cn.tedu.rabbitmqboot.m4;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Scanner;
@Component
public class Producer {
@Autowired
private AmqpTemplate amqpTemplate;
// 需要手動呼叫,傳送訊息
public void send() {
while (true) {
System.out.print("輸入: ");
String msg = new Scanner(System.in).nextLine();
System.out.print("輸入路由鍵: ");
String key = new Scanner(System.in).nextLine();
// 預設是持久訊息
// 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
amqpTemplate.convertAndSend("direct_logs", key, msg);
}
}
}
package cn.tedu.rabbitmqboot.m4;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫
@RabbitListener
1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
2. 直接載入方法上
*/
@Component
public class Consumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "direct_logs",declare = "false"), //不重複定義這個交換機
key={"error"}
))
public void recieve1(String msg) {
System.out.println("消費者1收到: "+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "direct_logs",declare = "false"), //不重複定義這個交換機
key = {"info","error","warning"}
))
public void recieve2(String msg) {
System.out.println("消費者2收到: "+msg);
}
}
主題模式
package cn.tedu.rabbitmqboot.m5;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.PostConstruct;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@Bean
public TopicExchange logsExchange() {
// 第二個引數持久屬性,預設是 true
return new TopicExchange("topic_logs");
}
@Autowired
private Producer p;
@PostConstruct
public void test() {
new Thread(() -> p.send()).start();
}
}
package cn.tedu.rabbitmqboot.m5;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Scanner;
@Component
public class Producer {
@Autowired
private AmqpTemplate amqpTemplate;
// 需要手動呼叫,傳送訊息
public void send() {
while (true) {
System.out.print("輸入: ");
String msg = new Scanner(System.in).nextLine();
System.out.print("輸入路由鍵: ");
String key = new Scanner(System.in).nextLine();
// 預設是持久訊息
// 如果要傳送非持久訊息,新增第三個引數 MessagePostProcessor
amqpTemplate.convertAndSend("topic_logs", key, msg);
}
}
}
package cn.tedu.rabbitmqboot.m5;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/*
消費者自動啟動,註冊成為消費者,
自動開始監聽佇列中的訊息。
不需要手動呼叫
@RabbitListener
1. 在類上註解,需要 @RabbitHandler 配合指定處理訊息的方法
2. 直接載入方法上
*/
@Component
public class Consumer {
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "topic_logs",declare = "false"), //不重複定義這個交換機
key={"*.orange.*"}
))
public void recieve1(String msg) {
System.out.println("消費者1收到: "+msg);
}
@RabbitListener(bindings = @QueueBinding(
value = @Queue, //由伺服器自動命名,false,true,true
exchange = @Exchange(name = "topic_logs",declare = "false"), //不重複定義這個交換機
key = {"*.*.rabbit","lazy.#"}
))
public void recieve2(String msg) {
System.out.println("消費者2收到: "+msg);
}
}
分散式事務
在微服務系統中,每個微服務應用都可能會有自己的資料庫,它們首先需要控制自己的本地事務。
一項業務操作可能會呼叫執行多個微服務。如何保證多個服務執行的多個資料庫的操作整體成功或整體失敗?這就是分散式事務要解決的問題。
資料庫初始化工具db-init ,seata控制分散式事務
分散式事務案例
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
seata-server.sql
drop database if exists `seata`;
CREATE DATABASE `seata` CHARSET utf8;
use `seata`;
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
order.sql
drop database if exists `seata_order`;
CREATE DATABASE `seata_order` charset utf8;
use `seata_order`;
CREATE TABLE `order` (
`id` bigint(11) NOT NULL,
`user_id` bigint(11) DEFAULT NULL COMMENT '使用者id',
`product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
`count` int(11) DEFAULT NULL COMMENT '數量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
ALTER TABLE `order` ADD COLUMN `status` int(1) DEFAULT NULL COMMENT '訂單狀態:0:建立中;1:已完結' AFTER `money` ;
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
CREATE TABLE IF NOT EXISTS segment
(
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '自增主鍵',
VERSION BIGINT DEFAULT 0 NOT NULL COMMENT '版本號',
business_type VARCHAR(63) DEFAULT '' NOT NULL COMMENT '業務型別,唯一',
max_id BIGINT DEFAULT 0 NOT NULL COMMENT '當前最大id',
step INT DEFAULT 0 NULL COMMENT '步長',
increment INT DEFAULT 1 NOT NULL COMMENT '每次id增量',
remainder INT DEFAULT 0 NOT NULL COMMENT '餘數',
created_at BIGINT UNSIGNED NOT NULL COMMENT '建立時間',
updated_at BIGINT UNSIGNED NOT NULL COMMENT '更新時間',
CONSTRAINT uniq_business_type UNIQUE (business_type)
) CHARSET = utf8mb4
ENGINE INNODB COMMENT '號段表';
INSERT INTO segment
(VERSION, business_type, max_id, step, increment, remainder, created_at, updated_at)
VALUES (1, 'order_business', 1000, 1000, 1, 0, NOW(), NOW());
storage.sql
drop database if exists `seata_storage`;
CREATE DATABASE `seata_storage` charset utf8;
use `seata_storage`;
CREATE TABLE `storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_id` bigint(11) DEFAULT NULL COMMENT '產品id',
`total` int(11) DEFAULT NULL COMMENT '總庫存',
`used` int(11) DEFAULT NULL COMMENT '已用庫存',
`residue` int(11) DEFAULT NULL COMMENT '剩餘庫存',
`frozen` int(11) DEFAULT '0' COMMENT 'TCC事務鎖定的庫存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seata_storage`.`storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES ('1', '1', '100', '0', '100');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
account.sql
drop database if exists `seata_account`;
CREATE DATABASE `seata_account` charset utf8;
use `seata_account`;
CREATE TABLE `account` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint(11) UNIQUE DEFAULT NULL COMMENT '使用者id',
`total` decimal(10,0) DEFAULT NULL COMMENT '總額度',
`used` decimal(10,0) DEFAULT NULL COMMENT '已用餘額',
`residue` decimal(10,0) DEFAULT '0' COMMENT '剩餘可用額度',
`frozen` decimal(10,0) DEFAULT '0' COMMENT 'TCC事務鎖定的金額',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `seata_account`.`account` (`id`, `user_id`, `total`, `used`, `residue`) VALUES ('1', '1', '1000', '0', '1000');
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
建立sql資料夾
主程式中新增程式碼,執行sql指令碼
Spring 中提供了一個 jdbc 指令碼執行器,使用這個工具可以非常方便的執行一個 sql 指令碼檔案,下面是這個方法:
ScriptUtils.executeSqlScript()
只需要傳入它需要的引數即可。
下面程式碼執行 sql 目錄中的四個指令碼程式,每次執行都會刪除四個資料庫再重新建立,並初始化資料。
package cn.tedu.dbinit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;
@SpringBootApplication
public class DbInitApplication {
@Autowired
private DataSource dataSource;
public static void main(String[] args) {
SpringApplication.run(DbInitApplication.class, args);
}
@PostConstruct
public void init() throws SQLException {
exec(dataSource, "sql/account.sql");
exec(dataSource, "sql/storage.sql");
exec(dataSource, "sql/order.sql");
exec(dataSource, "sql/seata-server.sql");
}
private void exec(DataSource accountDatasource, String script) throws SQLException {
ClassPathResource rc = new ClassPathResource(script, DbInitApplication.class.getClassLoader());
EncodedResource er = new EncodedResource(rc, "utf-8");
ScriptUtils.executeSqlScript(accountDatasource.getConnection(), er);
}
}
package cn.tedu.dbinit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.sql.SQLException;
@SpringBootApplication
public class DbInitApplication {
public static void main(String[] args) {
SpringApplication.run(DbInitApplication.class, args);
}
@Autowired
private DataSource dataSource;
@PostConstruct
public void runSql() throws SQLException {
run("sql/account.sql");
run("sql/order.sql");
run("sql/seata-server.sql");
run("sql/storage.sql");
}
private void run(String sql) throws SQLException {
ClassPathResource rc=new ClassPathResource(sql, DbInitApplication.class.getClassLoader());
EncodedResource er = new EncodedResource(rc, "UTF-8");
ScriptUtils.executeSqlScript(dataSource.getConnection(), er);
}
}
執行程式資料庫出現響應檔案
註冊中心
spring:
application:
name: eureka-server
server:
port: 8761
eureka:
server:
enable-self-preservation: false
instance:
hostname: eureka1
client:
register-with-eureka: false
fetch-registry: false
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
測試:localhost:8761
訂單,庫存,賬戶從父專案中繼承
新建maven專案
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>order-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>order-parent</name>
<properties>
<mybatis-plus.version>3.3.2</mybatis-plus.version>
<druid-spring-boot-starter.version>1.1.23</druid-spring-boot-starter.version>
<seata.version>1.3.0</seata.version>
<spring-cloud-alibaba-seata.version>2.0.0.RELEASE</spring-cloud-alibaba-seata.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter.version}</version>
</dependency>
<!--<dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-alibaba-seata</artifactId>-->
<!-- <version>${spring-cloud-alibaba-seata.version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <artifactId>seata-all</artifactId>-->
<!-- <groupId>io.seata</groupId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!--</dependency>-->
<!--<dependency>-->
<!-- <groupId>io.seata</groupId>-->
<!-- <artifactId>seata-all</artifactId>-->
<!-- <version>${seata.version}</version>-->
<!--</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
account賬戶專案
在這個微服務專案中實現扣減賬戶金額的功能。
<?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">
<parent>
<artifactId>order-parent</artifactId>
<groupId>cn.tedu</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>account</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>account</name>
<description>Demo project for Spring Boot</description>
</project>
spring:
application:
name: account
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
mybatis-plus:
type-aliases-package: cn.tedu.account.entity
mapper-locations:
- classpath:mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.account.mapper: DEBUG
package cn.tedu.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
package cn.tedu.account.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
private Long id;
private Long userId;
private BigDecimal total; //總金額
private BigDecimal used; //以用金額
private BigDecimal residue;//剩餘金額
private BigDecimal frozen;//凍結金額
}
package cn.tedu.account.mapper;
import cn.tedu.account.entity.Account;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
public interface AccountMapper extends BaseMapper<Account> {
void decrease(Long userId, BigDecimal money);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.tedu.account.mapper.AccountMapper" >
<resultMap id="BaseResultMap" type="cn.tedu.account.entity.Account" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="user_id" property="userId" jdbcType="BIGINT" />
<result column="total" property="total" jdbcType="DECIMAL" />
<result column="used" property="used" jdbcType="DECIMAL" />
<result column="residue" property="residue" jdbcType="DECIMAL"/>
<result column="frozen" property="frozen" jdbcType="DECIMAL"/>
</resultMap>
<update id="decrease">
UPDATE account SET residue = residue - #{money},used = used + #{money} where user_id = #{userId};
</update>
</mapper>
package cn.tedu.account.controller;
import cn.tedu.account.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class AccountController {
@Autowired
private AccountService accountService;
@GetMapping("/decrease")
public String decrease(Long userId, BigDecimal money){
accountService.decrease(userId, money);
return "扣減賬戶金額成功!!!";
}
}
訪問測試
http://localhost:8081/decrease?userId=1&money=100
Eureka客戶端向伺服器註冊正確IP
1.選擇網路卡
2.註冊ip,而不註冊主機名
1.選擇網路卡
配置bootstrap.yml檔案
spring:
cloud:
inetutils:
ignored-interfaces: # 忽略的網路卡
- VM.*
preferred-networks: # 要是用的網路卡的網段
- 192\.168\.7\..*
測試頁面:
account的yml檔案
spring:
application:
name: account
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3307/seata_account?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: root
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true # 使用ip進行註冊
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port} # 介面列表中顯示的格式也顯示ip
mybatis-plus:
type-aliases-package: cn.tedu.account.entity
mapper-locations:
- classpath:mapper/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.tedu.account.mapper: DEBUG