首页
归档
留言
友链
广告合作
壁纸
更多
美女主播
Search
1
博瑞GE车机升级/降级
5,608 阅读
2
Mac打印机设置黑白打印
4,936 阅读
3
修改elementUI中el-table树形结构图标
4,894 阅读
4
Mac客户端添加腾讯企业邮箱方法
4,672 阅读
5
intelliJ Idea 2022.2.X破解
4,354 阅读
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
登录
/
注册
Search
标签搜索
Spring Boot
Java
Vue
Spring Cloud
Mac
MyBatis
WordPress
MacOS
asp.net
Element UI
Nacos
.Net
Spring Cloud Alibaba
MySQL
Mybatis-Plus
Typecho
jQuery
Java Script
IntelliJ IDEA
微信小程序
Laughing
累计撰写
627
篇文章
累计收到
1,421
条评论
首页
栏目
后端开发
HarmonyOS Next
Web前端
微信开发
开发辅助
App开发
数据库
随笔日记
页面
归档
留言
友链
广告合作
壁纸
美女主播
搜索到
627
篇与
的结果
2021-07-27
Ubuntu 20.04换国内源 清华源 阿里源 中科大源 163源
Ubuntu 20.04 是 Ubuntu 的第 8 个 LTS 版本,其重大更新和改进将在 2030 年前终止,计划于2020年 4 月 23 日发布。 国内有很多Ubuntu的镜像源,包括阿里的、网易的,还有很多教育网的源,比如:清华源、中科大源。 我们这里以清华源为例讲解如何修改Ubuntu 20.04里面默认的源。 编辑/etc/apt/sources.list文件, 在文件最前面添加以下条目(操作前请做好相应备份):清华源# 默认注释了源码镜像以提高 apt update 速度,如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-security main restricted universe multiverse # 预发布软件源,不建议启用 # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse然后执行sudo apt-get update sudo apt-get upgrade阿里源deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse deb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse deb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse中科大源deb https://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal main restricted universe multiverse deb https://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-updates main restricted universe multiverse deb https://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-backports main restricted universe multiverse deb https://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-security main restricted universe multiverse deb https://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse deb-src https://mirrors.ustc.edu.cn/ubuntu/ focal-proposed main restricted universe multiverse网易163源deb http://mirrors.163.com/ubuntu/ focal main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ focal-security main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ focal-updates main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ focal-proposed main restricted universe multiverse deb http://mirrors.163.com/ubuntu/ focal-backports main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ focal main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ focal-security main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ focal-updates main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ focal-proposed main restricted universe multiverse deb-src http://mirrors.163.com/ubuntu/ focal-backports main restricted universe multiverse
2021年07月27日
1,136 阅读
0 评论
0 点赞
2021-07-26
wsl ubuntu 20.0.4安装MySql 5.7
安装依次执行以下命令:sudo apt update sudo apt upgrade sudo apt install libaio1 libtinfo5 libmecab2 libjson-perl python2 python2.7 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-server_5.7.31-1ubuntu18.04_amd64.deb-bundle.tar tar xvf mysql-server_5.7.31-1ubuntu18.04_amd64.deb-bundle.tar rm -f mysql-testsuite_5.7.31-1ubuntu18.04_amd64.deb rm -f mysql-community-test_5.7.31-1ubuntu18.04_amd64.deb sudo dpkg -i mysql-common_5.7.31-1ubuntu18.04_amd64.deb sudo dpkg -i lib*.deb sudo dpkg -i mysql-*.deb然后输入root密码启动MySqlservice mysql start修改远程登录sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf重启服务service mysql restart
2021年07月26日
1,181 阅读
0 评论
0 点赞
2021-07-26
Nacos集群配置
Netflix组件移除说明Netflix公司是目前微服务落地中最成功的公司。它开源了诸如Eureka、Hystrix、Zuul、Feign、Ribbon等等广大开发者所知微服务套件,统称为Netflix OSS。在当时Netflix OSS成为微服务组件上事实的标准。但是微服务兴起不久,也就是在 2018 年前后Netflix公司宣布其核心组件Hystrix、Ribbon、Zuul、Eureka等进入维护状态,不再进行新特性开发,只修 BUG。这直接影响了Spring Cloud项目的发展路线,Spring 官方不得不采取了应对措施,在 2019 年的在 SpringOne 2019 大会中,Spring Cloud宣布 Spring Cloud Netflix 项目进入维护模式,并在 2020 年移除相关的Netflix OSS组件。什么是 Nacos?阿里巴巴在2018年7月份发布Nacos, Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos 支持几乎所有主流类型的服务的发现、配置和管理。Nacos集群及配置前置条件MySql 5.X版本JDK 1.8nginx创建MySql数据库创建一个名称为nacos_config的数据库,然后执行以下数据库预置脚本。/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info */ /******************************************/ CREATE TABLE `config_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) DEFAULT NULL, `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', `c_desc` varchar(256) DEFAULT NULL, `c_use` varchar(64) DEFAULT NULL, `effect` varchar(64) DEFAULT NULL, `type` varchar(64) DEFAULT NULL, `c_schema` text, PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_aggr */ /******************************************/ CREATE TABLE `config_info_aggr` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(255) NOT NULL COMMENT 'group_id', `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', `content` longtext NOT NULL COMMENT '内容', `gmt_modified` datetime NOT NULL COMMENT '修改时间', `app_name` varchar(128) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_beta */ /******************************************/ CREATE TABLE `config_info_beta` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_info_tag */ /******************************************/ CREATE TABLE `config_info_tag` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL COMMENT 'content', `md5` varchar(32) DEFAULT NULL COMMENT 'md5', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `src_user` text COMMENT 'source user', `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', PRIMARY KEY (`id`), UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = config_tags_relation */ /******************************************/ CREATE TABLE `config_tags_relation` ( `id` bigint(20) NOT NULL COMMENT 'id', `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', `data_id` varchar(255) NOT NULL COMMENT 'data_id', `group_id` varchar(128) NOT NULL COMMENT 'group_id', `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', `nid` bigint(20) NOT NULL AUTO_INCREMENT, PRIMARY KEY (`nid`), UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = group_capacity */ /******************************************/ CREATE TABLE `group_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = his_config_info */ /******************************************/ CREATE TABLE `his_config_info` ( `id` bigint(64) unsigned NOT NULL, `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `data_id` varchar(255) NOT NULL, `group_id` varchar(128) NOT NULL, `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', `content` longtext NOT NULL, `md5` varchar(32) DEFAULT NULL, `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `src_user` text, `src_ip` varchar(50) DEFAULT NULL, `op_type` char(10) DEFAULT NULL, `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', PRIMARY KEY (`nid`), KEY `idx_gmt_create` (`gmt_create`), KEY `idx_gmt_modified` (`gmt_modified`), KEY `idx_did` (`data_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; /******************************************/ /* 数据库全名 = nacos_config */ /* 表名称 = tenant_capacity */ /******************************************/ CREATE TABLE `tenant_capacity` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; CREATE TABLE `tenant_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', `kp` varchar(128) NOT NULL COMMENT 'kp', `tenant_id` varchar(128) default '' COMMENT 'tenant_id', `tenant_name` varchar(128) default '' COMMENT 'tenant_name', `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; CREATE TABLE `users` ( `username` varchar(50) NOT NULL PRIMARY KEY, `password` varchar(500) NOT NULL, `enabled` boolean NOT NULL ); CREATE TABLE `roles` ( `username` varchar(50) NOT NULL, `role` varchar(50) NOT NULL, UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE ); CREATE TABLE `permissions` ( `role` varchar(50) NOT NULL, `resource` varchar(255) NOT NULL, `action` varchar(8) NOT NULL, UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ); INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE); INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');此脚本sql位于Nacos的conf文件夹下:nacos-mysql.sql修改application.properties文件修改conf/application.properties文件,修改MySQL信息及端口。由于我们演示三个集群,位于一个服务器,所以需要三个端口号,我这里分别设置为3333、4444、5555。server.port=3333 #*************** Config Module Related Configurations ***************# ### If use MySQL as datasource: spring.datasource.platform=mysql ### Count of DB: db.num=1 ### Connect URL of DB: db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user=root db.password=root修改cluster.conf我们这里要三个进行集群,所以输入以下信息localhost:3333 localhost:4444 localhost:5555修改另外两个Nacos我这里复制我刚才修改后的Nacos,然后打开application.properties,将端口号分别修改为4444、5555。测试分别进去三个Nacos的bin文件夹,执行 ./startup.sh -m cluster打开http://localhost:3333/nacos,查看节点列表配置nginx启动三台Nacos后,我们需要配置Nginx进行端口转发,我这里Nginx使用1111端口。打开nginx配置文件vi nginx.conf增加以下内容upstream cluster{ server localhost:3333 weight=1; server localhost:4444 weight=1; server localhost:5555 weight=1; } server { listen 1111; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { proxy_pass http://cluster; } #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # proxy the PHP scripts to Apache listening on 127.0.0.1:80 # #location ~ \.php$ { # proxy_pass http://127.0.0.1; #} # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 # #location ~ \.php$ { # root html; # fastcgi_pass 127.0.0.1:9000; # fastcgi_index index.php; # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # include fastcgi_params; #} # deny access to .htaccess files, if Apache's document root # concurs with nginx's one # #location ~ /\.ht { # deny all; #} }重启nginxnginx -s reload进入http://localhost:1111/nacos,查看节点至此,Nacos集群配置完成。
2021年07月26日
1,491 阅读
0 评论
2 点赞
2021-07-26
Seata基础教程下-Spring Cloud Alibaba使用Seata
在seata基础教程上-seata 1.4.2的安装及基于Nacos的配置中,我们基于Nacos完成了Seata的安装及配置,这篇文章我们介绍下在Spring Cloud Alibaba中的使用。环境准备学习本文之前,你应该准备好以下环境。Nacos 2.0.2版本并运行Seata Server 1.4.2版本,配置好Nacos并运行前置知识本文通过Spring Cloud Alibaba,使用Nacos作为服务中心,通过OpenFeign实现服务之间的调用,所以你必须具备以下基础知识:在Spring Cloud中使用Nacos作为服务注册、发现中心的配置及使用。在Spring Cloud中通过OpenFeign实现服务之间的调用。MyBatis的使用。场景描述本文参考尚硅谷阳哥的视频教程,使用最新的Nacos(2.0.2)及Seata(1.4.2),演示用户下单的业务场景,基本流程如下其实业务很简单,用户下单,扣减商品库存表数据然后减去账户余额,如果不是微服务架构,我们一般使用一个系统,基本上一个@Transactional注解就解决了。基于微服务就不同了,我们提供三个微服务,分别为订单服务、库存服务、账户服务,三个服务对应三个数据库,分别为seata_order、seata_storage、seata_account,现在不同业务分布在不同的数据库中,那么我们在进行数据回滚就不太好处理了。Seata的分布式交易解决方案有了Seata就简单了,我们只需要使用一个@GlobalTransactional注解在业务方法上就可以了。Spring Cloud使用Seata创建数据库创建三个数据库,分别为seata_order、seata_storage、seata_account。订单微服务表结构create table t_order ( id bigint auto_increment comment '主键', user_id bigint null comment '用户Id', product_id bigint null, count int null, money decimal(11) null, status int(1) null, constraint t_order_id_uindex unique (id) ); alter table t_order add primary key (id); create table undo_log ( id bigint auto_increment primary key, branch_id bigint not null, xid varchar(100) not null, context varchar(128) not null, rollback_info longblob not null, log_status int not null, log_created datetime not null, log_modified datetime not null, constraint ux_undo_log unique (xid, branch_id) ) charset=utf8;账户微服务表结构create table t_account ( id bigint auto_increment, user_id bigint null, total decimal null, used decimal null, residue decimal null, constraint t_account_id_uindex unique (id) ); alter table t_account add primary key (id); create table undo_log ( id bigint auto_increment primary key, branch_id bigint not null, xid varchar(100) not null, context varchar(128) not null, rollback_info longblob not null, log_status int not null, log_created datetime not null, log_modified datetime not null, constraint ux_undo_log unique (xid, branch_id) ) charset=utf8; INSERT INTO seata_account.t_account (id, user_id, total, used, residue) VALUES (1, 1, 1000, 0, 1000);库存微服务表结构create table t_storage ( id bigint auto_increment, product_id bigint null, total int null, used int null, residue int null comment '剩余库存', constraint t_storage_id_uindex unique (id) ); alter table t_storage add primary key (id); create table undo_log ( id bigint auto_increment primary key, branch_id bigint not null, xid varchar(100) not null, context varchar(128) not null, rollback_info longblob not null, log_status int not null, log_created datetime not null, log_modified datetime not null, constraint ux_undo_log unique (xid, branch_id) ) charset=utf8; INSERT INTO seata_storage.t_storage (id, product_id, total, used, residue) VALUES (2, 1, 100, 0, 100);创建工程工程结构,如下图,请忽略其他模块。{message type="warning" content="一定要注意依赖里面的版本号,不然会出现各种错误。"/}创建父工程父工程用于维护依赖管理,没有具体代码。增加依赖<?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>cc.lisen</groupId> <artifactId>Cloud2020</artifactId> <version>1.0-SNAPSHOT</version> <packaging>pom</packaging> <modules> <module>cloud-provider-payment8001</module> <module>cloud-consumer-order80</module> <module>cloud-api-common</module> <module>cloud-provider-payment8004</module> <module>cloud-consumerzk-order80</module> <module>cloud-consumer-feign-order80</module> <module>cloud-provider-hystrix-payment8001</module> <module>cloud-consumer-feign-hystrix-order80</module> <module>cloud-consumer-hystrix-dashboard9001</module> <module>cloud-gateway-gateway9527</module> <module>cloud-config-center-3344</module> <module>cloud-config-client-3355</module> <module>cloud-config-client-3366</module> <module>cloud-stream-rabbitmq-provider8801</module> <module>cloud-stream-rabbitmq-consumer8802</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloud-stream-rabbitmq-consumer8802</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloud-stream-rabbitmq-consumer8803</module> <module>cloudalibaba-provider-payment9001</module> <module>cloudalibaba-provider-payment9002</module> <module>cloudalibaba-consumer-order80</module> <module>cloudalibaba-config-nacos-client3377</module> <module>cloudalibaba-sentinel-service8401</module> <module>seata-order-service2001</module> <module>seata-storage-service2002</module> <module>seata-account-service2003</module> </modules> <!-- 统一管理jar包版本--> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.3.12.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR12</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <!-- <version>2.1.0.RELEASE</version>--> <version>2.2.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> </dependencies> </dependencyManagement> <build> <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) --> <plugins> <plugin> <artifactId>maven-clean-plugin</artifactId> <version>3.1.0</version> </plugin> <plugin> <artifactId>maven-site-plugin</artifactId> <version>3.7.1</version> </plugin> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> <version>3.0.0</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-site-plugin</artifactId> <configuration> <locales>en,fr</locales> </configuration> </plugin> </plugins> </build> <reporting> <plugins> <plugin> <artifactId>maven-project-info-reports-plugin</artifactId> </plugin> </plugins> </reporting> </project> 创建通用模块通用模块这里维护了一个CommonResult用于返回Json对象。增加依赖<?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"> <parent> <artifactId>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-api-common</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <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.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> </dependencies> </project>增加实体package cc.lisen.springcloud.entites; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * 博客:https://lisen.cc * Description: * * @Author: 李森的博客 * DateTime: 2021-07-24 11:25 */ @Data @AllArgsConstructor @NoArgsConstructor public class CommonResult<T> { private Integer code; private String message; private T data; public CommonResult(Integer code, String message) { this(code, message, null); } }创建订单微服务模块订单模块维护订单微服务,通过Feign调用库存及账户微服务。增加依赖<?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"> <parent> <artifactId>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service2001</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <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.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </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-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>修改配置文件配置文件一定要注意,tx-service-group一定要与我们Seata服务端的配置保持一致。server: port: 2001 spring: application: name: seata-order-service cloud: nacos: discovery: server-addr: localhost:1111 loadbalancer: retry: enabled: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_order?useSSL=false username: root password: root feign: hystrix: enabled: false mybatis: type-aliases-package: cc.lisen.springcloud.alibaba.seata.domain mapperLocations: classpath:/mapper/*.xml seata: tx-service-group: my_tx_group #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 service: vgroup-mapping: my_tx_group: default #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 config: type: nacos nacos: server-addr: localhost:1111 group: "SEATA_GROUP" namespace: "" username: "nacos" password: "nacos"增加实体@Data @AllArgsConstructor @NoArgsConstructor public class Order { /** * 主键 */ private Long id; /** * 用户Id */ private Long userId; private Long productId; private Integer count; private BigDecimal money; private Integer status; }增加Mapperpublic interface OrderDao { int create(Order record); int update(@Param("userId") Long userId, @Param("status") Integer status);数据库操作<?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="cc.lisen.springcloud.alibaba.seata.dao.OrderDao"> <resultMap id="BaseResultMap" type="cc.lisen.springcloud.alibaba.seata.domain.Order"> <!--@Table t_order--> <result column="id" jdbcType="BIGINT" property="id"/> <result column="user_id" jdbcType="BIGINT" property="userId"/> <result column="product_id" jdbcType="BIGINT" property="productId"/> <result column="count" jdbcType="INTEGER" property="count"/> <result column="money" jdbcType="DECIMAL" property="money"/> <result column="status" jdbcType="INTEGER" property="status"/> </resultMap> <sql id="Base_Column_List"> <!--@mbg.generated--> id, user_id, product_id, `count`, money, `status` </sql> <insert id="create" parameterType="cc.lisen.springcloud.alibaba.seata.domain.Order"> <!--@mbg.generated--> insert into t_order (user_id, product_id, `count`, money, `status`) values (#{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT}, #{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER}) </insert> <update id="update"> update t_order set status = 1 where user_id = #{userId} and status = #{status} </update> </mapper>增加服务接口增加账户微服务Feign调用接口@FeignClient(value = "seata-account-service") public interface IAccountService { @PostMapping("/account/decrease") public CommonResult accountDecrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money); }增加库存微服务Feign调用接口@FeignClient(value = "seata-storage-service") public interface IStorageService { @PostMapping("/storage/decrease") public CommonResult storageDecrease(@RequestParam("productId") Long productId,@RequestParam("count") Integer count); } 增加订单接口public interface IOrderService { int create(Order record); }增加订单服务实现@Service @Slf4j public class OrderServiceImpl implements IOrderService { @Resource private OrderDao orderDao; @Resource private IStorageService storageService; @Resource private IAccountService accountService; @Override @GlobalTransactional(name = "fsp-create-order") // @Transactional public int create(Order order) { log.info("--------->开始新建订单"); orderDao.create(order); log.info("--------->订单微服务开始调用库存,做扣减Count"); storageService.storageDecrease(order.getProductId(), order.getCount()); log.info("--------->订单微服务开始调用库存,做扣减End"); log.info("--------->订单微服务开始调用账户,做扣减Money"); accountService.accountDecrease(order.getUserId(), order.getMoney()); log.info("--------->订单微服务开始调用账户,做扣减End"); log.info("--------->修改订单状态开始"); orderDao.update(order.getUserId(), 0); log.info("--------->修改订单状态End"); return 1; } }增加接口controller@RestController @Slf4j @RequestMapping("/order") public class OrderController { @Resource private IOrderService orderService; @PostMapping("/create") public CommonResult create(Order order) { orderService.create(order); return new CommonResult(200, "订单创建成功"); } }增加DataSource,让Seata代理数据库@Configuration public class DataSourceProxyConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } }修改启动类取消数据源自动配置,让Seata能够代理数据源。@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = {"cc.lisen.springcloud.alibaba.seata.dao"}) public class SeataOrderServiceMain2001 { public static void main(String[] args) { SpringApplication.run(SeataOrderServiceMain2001.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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-storage-service2002</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <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.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>修改配置文件server: port: 2002 spring: application: name: seata-storage-service cloud: nacos: discovery: server-addr: localhost:1111 loadbalancer: retry: enabled: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_storage?useSSL=false username: root password: root feign: hystrix: enabled: false mybatis: type-aliases-package: cc.lisen.springcloud.alibaba.seata.domain mapperLocations: classpath:/mapper/*.xml seata: tx-service-group: my_tx_group #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 service: vgroup-mapping: my_tx_group: default #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 config: type: nacos nacos: server-addr: localhost:1111 group: "SEATA_GROUP" namespace: "" username: "nacos" password: "nacos"增加实体@Data @AllArgsConstructor @NoArgsConstructor public class Storage { private Long id; private Long productId; private Integer total; private Integer used; private Integer residue; }增加Mapperpublic interface StorageDao { int decrease(@Param("productId") Long productId, @Param("count") Integer count); }数据库操作<?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="cc.lisen.springcloud.alibaba.seata.dao.StorageDao"> <resultMap id="BaseResultMap" type="cc.lisen.springcloud.alibaba.seata.domain.Storage"> <!--@Table t_storage--> <result column="id" jdbcType="BIGINT" property="id"/> <result column="product_id" jdbcType="BIGINT" property="productId"/> <result column="total" jdbcType="INTEGER" property="total"/> <result column="used" jdbcType="INTEGER" property="used"/> <result column="residue" jdbcType="INTEGER" property="residue"/> </resultMap> <sql id="Base_Column_List"> id, product_id, total, used, residue </sql> <update id="decrease"> update t_storage set used = used + #{count}, residue = residue - #{count} where product_id = #{productId} </update> </mapper>增加服务接口public interface IStorageService { int decrease(Long productId, Integer count); } 增加服务实现@Service @Slf4j public class StorageServiceImpl implements IStorageService { @Resource private StorageDao storageDao; @Override // @Transactional public int decrease(Long productId, Integer count) { log.info("--------->storage-------decrease获取xid:" + RootContext.getXID()); return storageDao.decrease(productId, count); } }增加接口controller@RestController @RequestMapping("/storage") @Slf4j public class StorageController { @Resource private IStorageService storageService; @PostMapping("/decrease") public CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) { storageService.decrease(productId, count); return new CommonResult(200, "扣减库存成功"); } }增加DataSource,让Seata代理数据源@Configuration public class DataSourceProxyConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } }修改启动类取消数据源自动配置,让Seata能够代理数据源。@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = {"cc.lisen.springcloud.alibaba.seata.dao"}) public class SeataStorageServiceMain2002 { public static void main(String[] args) { SpringApplication.run(SeataStorageServiceMain2002.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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>Cloud2020</artifactId> <groupId>cc.lisen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-account-service2003</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <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.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.5</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </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-seata</artifactId> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>cc.lisen</groupId> <artifactId>cloud-api-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>修改配置文件server: port: 2003 spring: application: name: seata-account-service cloud: nacos: discovery: server-addr: localhost:1111 loadbalancer: retry: enabled: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/seata_account?useSSL=false username: root password: root feign: hystrix: enabled: false mybatis: type-aliases-package: cc.lisen.springcloud.alibaba.seata.domain mapperLocations: classpath:/mapper/*.xml seata: tx-service-group: my_tx_group #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 service: vgroup-mapping: my_tx_group: default #与config.txt 中的service.vgroupMapping.my_tx_group=default一致 config: type: nacos nacos: server-addr: localhost:1111 group: "SEATA_GROUP" namespace: "" username: "nacos" password: "nacos"增加实体@Data @AllArgsConstructor @NoArgsConstructor public class Account { private Long id; private Long userId; private Long total; private Long used; private Long residue; }增加Mapperpublic interface AccountDao { int decrease(@Param("userId") Long userId, @Param("money") 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="cc.lisen.springcloud.alibaba.seata.dao.AccountDao"> <resultMap id="BaseResultMap" type="cc.lisen.springcloud.alibaba.seata.domain.Account"> <!--@Table t_account--> <result column="id" jdbcType="BIGINT" property="id"/> <result column="user_id" jdbcType="BIGINT" property="userId"/> <result column="total" jdbcType="DECIMAL" property="total"/> <result column="used" jdbcType="DECIMAL" property="used"/> <result column="residue" jdbcType="DECIMAL" property="residue"/> </resultMap> <sql id="Base_Column_List"> id, user_id, total, used, residue </sql> <update id="decrease"> update t_account set used = used + #{money}, residue=residue - #{money} where user_id = #{userId} </update> </mapper>增加服务接口public interface IAccountService { int decrease( Long userId, BigDecimal money); }增加服务实现这里注意以下,我们模拟了报错信息@Service @Slf4j public class AccountServiceImpl implements IAccountService { @Resource private AccountDao accountDao; @Override // @Transactional public int decrease(Long userId, BigDecimal money) { log.info("--------->storage-------decrease获取xid:" + RootContext.getXID()); //模拟报错 int age = 10 / 0; return accountDao.decrease(userId, money); } }增加接口controller@RestController @Slf4j @RequestMapping("/account") public class AccountController { @Resource private IAccountService accountService; @PostMapping("/decrease") public CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money) { accountService.decrease(userId, money); return new CommonResult(200, "账户扣减成功"); } }增加DataSource,让Seata代理数据源@Configuration public class DataSourceProxyConfig { @Value("${mybatis.mapperLocations}") private String mapperLocations; @Bean @ConfigurationProperties(prefix = "spring.datasource") public DataSource druidDataSource(){ return new DruidDataSource(); } }修改启动类取消数据源自动配置,让Seata能够代理数据源。@EnableDiscoveryClient @EnableFeignClients @MapperScan(basePackages = {"cc.lisen.springcloud.alibaba.seata.dao"}) @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) public class SeataAccountServiceMain2003 { public static void main(String[] args) { SpringApplication.run(SeataAccountServiceMain2003.class, args); } }测试通过postman测试可以看到系统抛出了异常,然后我们查看以下数据库,可以看到数据没有发生任何变化。
2021年07月26日
1,066 阅读
0 评论
1 点赞
2021-07-26
Seata基础教程上-seata 1.4.2的安装及基于Nacos的配置
先吐槽一下,seata的文档真的是乱的一批。我们这里只介绍安装及基本使用过程。1.seata介绍Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。具体原理可以参考官网http://seata.io/zh-cn/docs/overview/what-is-seata.html。{message type="success" content="一定要注意版本问题,包括安装的Nacos版本、Seata版本、Spring Cloud版本、Spring Boot版本等等。"/}2.前置条件2.1安装JDK并配置环境变量安装JDK1.8及以上版本,并正确配置环境变量。2.2maven我这里用的是3.6.3,并配置阿里云仓库。2.3Git由于需要将Seata Server的配置文件导入Nacos,所以需要安装Git2.4安装NacosNacos 2.0.2版本,关于Nacos的安装,我们稍后会出一个专门的文章进行介绍。温馨提示 所以学习本篇文章之前,您至少应该改知道Nacos的安装以及在Spring Cloud中如何利用Nacos进行服务的注册与发现。Nacos默认端口为8848,由于我这里配置了集群,所以前端端口使用的是1111,大家在下面配置文件注意以下。3.seata安装我这里使用的seata是1.4.2(截止到目前最新的版本)。3.1.修改配置文件3.1.1.修改file.conf进入Seata的conf目录,找到file.conf,如下图修改以下内容## transaction log store, only used in seata-server store { ## store mode: file、db、redis mode = "db" #我们用数据库存储,改成db ## rsa decryption public key publicKey = "" ## file store property file { ## store location dir dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions maxBranchSessionSize = 16384 # globe session size , if exceeded throws exceptions maxGlobalSessionSize = 512 # file buffer size , if exceeded allocate new buffer fileWriteBufferCacheSize = 16384 # when recover batch read size sessionReloadReadSize = 100 # async, sync flushDiskMode = async } ## database store property db { ## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc. datasource = "druid" ## mysql/oracle/postgresql/h2/oceanbase etc. dbType = "mysql" # 改成MySql driverClassName = "com.mysql.jdbc.Driver" ## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param url = "jdbc:mysql://ip:3306/数据库名称?rewriteBatchedStatements=true" user = "数据库用户" password = "数据库密码" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } ## redis store property redis { ## redis mode: single、sentinel mode = "single" ## single mode property single { host = "127.0.0.1" port = "6379" } ## sentinel mode property sentinel { masterName = "" ## such as "10.28.235.65:26379,10.28.235.65:26380,10.28.235.65:26381" sentinelHosts = "" } password = "" database = "0" minConn = 1 maxConn = 10 maxTotal = 100 queryLimit = 100 } } 3.1.2.修改registry.conf进入Seata的conf目录,找到registry.conf,如下图我们这里用Nacos,所以主要是配置Nacos的信息,配置内容包括两部分,registry服务,将type从file改成nacos,将seata服务配置进nacos;config将type从file改成nacos,这样不需要每个项目都放file.conf修改内容如下:registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" #改成Nacos nacos { application = "seata-server" serverAddr = "127.0.0.1:1111" #Nacos地址,我这里集群用的1111端口,默认为8848端口 group = "SEATA_GROUP" #Nacos分组 namespace = "" username = "nacos" #Nacos用户名 password = "nacos" #Nacos密码 } eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" db = 0 password = "" cluster = "default" timeout = 0 } zk { cluster = "default" serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } consul { cluster = "default" serverAddr = "127.0.0.1:8500" aclToken = "" } etcd3 { cluster = "default" serverAddr = "http://localhost:2379" } sofa { serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" group = "SEATA_GROUP" addressWaitTime = "3000" } file { name = "file.conf" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" #改成Nacos nacos { serverAddr = "127.0.0.1:1111" #Nacos地址,我这里集群用的1111端口,默认为8848端口 namespace = "" group = "SEATA_GROUP" #Nacos分组 username = "nacos" #Nacos用户名 password = "nacos" #Nacos密码 # dataId = "" } consul { serverAddr = "127.0.0.1:8500" aclToken = "" } apollo { appId = "seata-server" ## apolloConfigService will cover apolloMeta apolloMeta = "http://192.168.1.204:8801" apolloConfigService = "http://192.168.1.204:8080" namespace = "application" apolloAccesskeySecret = "" cluster = "seata" } zk { serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" nodePath = "/seata/seata.properties" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } } 3.1.3.导入Seata相应的配置项到Nacos的配置中心(非注册中心)由于我这里注册中心跟配置中心用的一个,所以我其实导入的就是一个Nacos。3.1.3.1.创建导入脚本在conf文件夹下,创建nacos-config.sh,并将以下内容粘贴进去#!/usr/bin/env bash # Copyright 1999-2019 Seata.io Group. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at、 # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. while getopts ":h:p:g:t:u:w:" opt do case $opt in h) host=$OPTARG ;; p) port=$OPTARG ;; g) group=$OPTARG ;; t) tenant=$OPTARG ;; u) username=$OPTARG ;; w) password=$OPTARG ;; ?) echo " USAGE OPTION: $0 [-h host] [-p port] [-g group] [-t tenant] [-u username] [-w password] " exit 1 ;; esac done urlencode() { for ((i=0; i < ${#1}; i++)) do char="${1:$i:1}" case $char in [a-zA-Z0-9.~_-]) printf $char ;; *) printf '%%%02X' "'$char" ;; esac done } if [[ -z ${host} ]]; then host=localhost fi if [[ -z ${port} ]]; then port=8848 fi if [[ -z ${group} ]]; then group="SEATA_GROUP" fi if [[ -z ${tenant} ]]; then tenant="" fi if [[ -z ${username} ]]; then username="" fi if [[ -z ${password} ]]; then password="" fi nacosAddr=$host:$port contentType="content-type:application/json;charset=UTF-8" echo "set nacosAddr=$nacosAddr" echo "set group=$group" failCount=0 tempLog=$(mktemp -u) function addConfig() { curl -X POST -H "${contentType}" "http://$nacosAddr/nacos/v1/cs/configs?dataId=$(urlencode $1)&group=$group&content=$(urlencode $2)&tenant=$tenant&username=$username&password=$password" >"${tempLog}" 2>/dev/null if [[ -z $(cat "${tempLog}") ]]; then echo " Please check the cluster status. " exit 1 fi if [[ $(cat "${tempLog}") =~ "true" ]]; then echo "Set $1=$2 successfully " else echo "Set $1=$2 failure " (( failCount++ )) fi } count=0 for line in $(cat $(dirname "$PWD")/config.txt | sed s/[[:space:]]//g); do (( count++ )) key=${line%%=*} value=${line#*=} addConfig "${key}" "${value}" done echo "=========================================================================" echo " Complete initialization parameters, total-count:$count , failure-count:$failCount " echo "=========================================================================" if [[ ${failCount} -eq 0 ]]; then echo " Init nacos config finished, please start seata-server. " else echo " init nacos config fail. " fi3.1.3.2.创建配置文件在conf的上级目录,创建config.txt并粘贴以下内容transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=true transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 service.vgroupMapping.my_tx_group=default #自定义,一定注意与spring cloud配置文件保持一致 service.default.grouplist=127.0.0.1:8091 #seata服务 service.enableDegrade=false service.disableGlobalTransaction=false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.tableMetaCheckerInterval=60000 client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.rm.tccActionInterceptorOrder=-2147482648 client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 client.tm.interceptorOrder=-2147482648 store.mode=db store.lock.mode=db store.session.mode=db store.publicKey= store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://数据库IP:3306/seata?useUnicode=true&rewriteBatchedStatements=true store.db.user=数据库用户名 store.db.password=数据库密码 store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 store.redis.mode=single store.redis.single.host=127.0.0.1 store.redis.single.port=6379 store.redis.sentinel.masterName= store.redis.sentinel.sentinelHosts= store.redis.maxConn=10 store.redis.minConn=1 store.redis.maxTotal=100 store.redis.database=0 store.redis.password= store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false server.distributedLockExpireTime=10000 client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.undo.compress.enable=true client.undo.compress.type=zip client.undo.compress.threshold=64k log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898上图一定要注意配置数据库信息及service.vgroupMapping.my_tx_group=default,这个后续在spring cloud项目的配置文件中会用到。3.1.3.3.导入配置文件打开git bash,进入到conf目录中,执行以下命令sh [nacos-config.sh文件路径] -h [nacos-ip地址] -p 8848 -g SEATA_GROUP[导入的组] -u [用户名] -w [密码] 执行完成后,查看我们Nacos配置中心中,配置文件是否已存在。3.1.4.创建相关的数据库(在回滚或提交前会将日志保存在数据库中,成功后会删除)3.1.3.1.创建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(128), `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;3.1.4.2.创建undo_log表注意:在业务数据库中创建undo_log,与上面的脚本不在一个数据库中。比如我有三个微服务,三个数据库,那么就需要在三个业务数据库中创建undo_log表-- 在业务数据库中创建undo_log(与上面的脚本不在一个数据库中) CREATE TABLE IF NOT EXISTS `undo_log` ( `branch_id` BIGINT NOT NULL COMMENT 'branch transaction id', `xid` VARCHAR(128) 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';4.运行seata运行Nacos这里不多介绍。运行seata进入seata的bin目录,执行./seata-server.bat,运行成功后,查看Nacos的服务列表能看到我们配置的seata即代表成功。
2021年07月26日
1,248 阅读
0 评论
0 点赞
2021-07-22
Spring Data Jpa 乐观锁
在数据库并发操作时,为了保证数据的正确性,我们会做一些并发处理,主要就是加锁。在加锁的选择上,常见有两种方式:悲观锁和乐观锁。悲观锁:简单的理解就是把需要的数据全部加锁,在事务提交之前,这些数据全部不可读取和修改。乐观锁:使用对单条数据进行版本校验和比较,来保证本次的更新是最新的,否则就失败,效率要高很多。实际工作中,乐观锁不止在数据库层面,其实我们在做分布式系统的时候,为了实现分布式系统的数据一致性,分布式事物的一种做法就是乐观锁。数据库操作举例说明悲观锁的做法select * from user where id = 1 for update; update user set name = '张三' where id = 1;通过使用for update给这条语句加锁,如果事务没有提交,其他任何读取和修改,都得排队等待。在代码中,我们加事务的java方法就会自然地形成了一个锁。乐观锁的做法select id,version from user where id = 1; update user set name = '张三',version=version+1 where id = 1 and version = 1;假设本次查询version=1,在更新操作时,带上这次查出来的Version,这样只有和我们上次版本一样的时候才会更新,就不会出现互相覆盖的问题,保证了数据的原子性。Jpa中乐观锁解决方案在没有@Version之前,我们都是自己手动维护这个version的,这样很有可能忘掉。或者是我们自己底层做框架,用AOP的思路做拦截底层维护这个Version的值。而Spring Data JPA的@Version就是通过AOP机制,帮我们动态维护这个Version,从而更优雅地实现乐观锁。实体上的version字段加上@Version注解即可。修改用户实体@Entity @Table(name = "sys_user") @Data @Slf4j @EntityListeners({UserAuditListener.class}) public class SysUser extends AbstractAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; @Version private Long version; }测试更新@GetMapping("/save") public SysUser save(){ Optional<SysUser> sysUserOptional = sysUserRepository.findById(100L); sysUserOptional.get().setNickName("哈哈"); return sysUserRepository.save(sysUserOptional.get()); }我们查看控制台输出信息,可以看到实际Sql中自动追加了version字段。同时我们查看数据库的version字段,值自动+1了
2021年07月22日
1,119 阅读
0 评论
0 点赞
2021-07-22
Spring Data Jpa Listener事件的扩展之自定义EntityListener
随着DDD的设计模式逐渐被大家认可和热捧。JPA通过Listener这种机制可以很好地实现事件分离、状体分离。假如,订单的状态变化。可能对我们来说比较重要,我们需要定一个类去监听订单状态变更,通知相应的逻辑代码各自去干各自的活。新增一个UserAuditListener类,在相应的操作上添加Callbacks注解@Slf4j public class UserAuditListener { @PostPersist private void postPersist(SysUser sysUser) { recordLog(sysUser, OperateType.CREATE); } @PostRemove private void postRemove(SysUser sysUser) { recordLog(sysUser, OperateType.REMOVE); } @PostUpdate private void postUpdate(SysUser sysUser) { recordLog(sysUser, OperateType.UPDATE); } @PostLoad public void postLoad(SysUser sysUser) { recordLog(sysUser, OperateType.LOAD); } /** * 记录审计日志 * * @param sysUser 用户实体 * @param operateType 操作类型 */ private void recordLog(SysUser sysUser, OperateType operateType) { log.info("{}执行了{}操作", sysUser, operateType.getType()); } } enum OperateType { CREATE("创建"), UPDATE("更新"), REMOVE("删除"), LOAD("查询"); private final String type; OperateType(String type) { this.type = type; } public String getType() { return this.type; } }修改实体,增加@EntityListeners注解@Entity @Table(name = "sys_user") @Data @Slf4j @EntityListeners({UserAuditListener.class}) public class SysUser extends AbstractAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; }测试这样,我们在增删改查方法调用时,就会执行我们对应的callback注解的方法比如我们插入用户@GetMapping("/save") public Long save(){ SysUser sysUser = new SysUser(); sysUser.setNickName("测试123321"); sysUser.setUserName("测试123321"); sysUser.setSex("0"); sysUserRepository.save(sysUser); return sysUser.getUserId(); }保存后,会自动调用postPersist方法
2021年07月22日
1,356 阅读
0 评论
1 点赞
2021-07-22
Spring Data Jpa扩展之Auditing
Auditing翻译过来是审计和审核。Spring的优秀之处在于帮我们想到了很多我们平时烦琐事情的解决方案,我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时间创建的,谁什么时间修改的,并且能让我们方便地记录操作日志。Spring Data JPA为我们提供了审计功能的架构实现,提供了4个注解专门解决这件事:@CreatedBy:创建人。@CreatedDate:创建时间。@LastModifiedBy:最后修改人。@LastModifiedDate:最后修改时间。增加公共虚拟类一般情况下,创建人、创建时间、最后修改人、最后修改时间四个字段都是统一的。所以,我们可以将其提取到公共的类中,然后所有的实体类继承这个虚拟类。@EntityListeners(AuditingEntityListener.class) @MappedSuperclass public abstract class AbstractAuditable { @CreatedBy private String createBy; @CreatedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; @LastModifiedBy private String updateBy; @LastModifiedDate @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; }必须添加@MappedSuperclass注解"修改实体,继承AbstractAuditable@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser extends AbstractAuditable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; }实现AbstractAuditable接口实现AbstractAuditable接口,提供创建者、最后修改者信息,这里我们随便模拟了以下常量。public class MyAuditorAware implements AuditorAware<String> { /** * Returns the current auditor of the application. * * @return the current auditor. */ @Override public Optional<String> getCurrentAuditor() { return Optional.of("测试用户"); } }开启Auditing功能通过@EnableJpaAuditing注解开启JPA的Auditing功能,并且告诉应用AuditorAware的实现类是谁。@SpringBootApplication @EnableJpaRepositories(queryLookupStrategy = QueryLookupStrategy.Key.CREATE_IF_NOT_FOUND) @EnableJpaAuditing public class Example1Application { public static void main(String[] args) { SpringApplication.run(Example1Application.class, args); } @Bean public AuditorAware<String> auditorAware(){ return new MyAuditorAware(); } }测试 @GetMapping("/save") public Long save(){ SysUser sysUser = new SysUser(); sysUser.setNickName("测试123321"); sysUser.setUserName("测试123321"); sysUser.setSex("0"); sysUserRepository.save(sysUser); return sysUser.getUserId(); }
2021年07月22日
1,271 阅读
0 评论
0 点赞
2021-07-22
Spring Data Jpa QueryByExampleExecutor及JpaSpecificationExecutor的用法
QueryByExampleExecutor可以通过实体进行简单的查询。比如我们查询用于昵称是超级开头的用户。@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { SysUser sysUser = new SysUser(); sysUser.setNickName("超级"); ExampleMatcher exampleMatcher = ExampleMatcher.matching() .withMatcher("nickName", ExampleMatcher.GenericPropertyMatchers.startsWith()) .withIgnorePaths("focus"); Example<SysUser> sysUserExample = Example.of(sysUser, exampleMatcher); return sysUserRepository.findAll(sysUserExample); } }其实跟MyBatis-Plus类似,功能比较简单,只能拼接and查询,一般使用不是很多。JpaSpecificationExecutor使用Specification的要点就是CriteriaBuilder,通过这个对象来创建条件,之后返回一个Predicate对象。这个对象中就有了相应的查询需求,我们同样可以定义多个Specification,之后通过Specifications对象将其连接起来。修改接口public interface SysUserRepository extends JpaRepository<SysUser, Long>, JpaSpecificationExecutor<SysUser> { }测试@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { return sysUserRepository.findAll((root, criteriaQuery, criteriaBuilder) -> { Predicate p1 = criteriaBuilder.like(root.get("nickName"), "%超级%"); Predicate p2 = criteriaBuilder.equal(root.get("userName"), "admin"); Predicate p3 = criteriaBuilder.equal(root.get("email"),"ry@163.com"); return criteriaBuilder.and(p1, p2); }); } }
2021年07月22日
1,243 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa多表关联查询
在权限表中,我们一般会设计用户表、部门表、角色表,一般情况下,一个用户只能属于一个部门,但是一个用户能拥有多个角色,一个角色也可能对应多个部门。Spring Data Jpa中提供了多个注解,用于处理表之间的关联管理。常见注解@OneToOne@OneToOne代表一对一的关联关系,需要配合@JoinColumn一起使用。注意:可以双向关联,也可以只配置一方,需要视实际需求而定。@OneToOne注释五个属性:targetEntity、cascade、fetch、optional和mappedByfetch属性默认值是FetchType.EAGER。optional = true设置属性可以为null;targetEntity属性:Class类型的属性。定义关系类的类型,默认是该成员属性对应的类类型,所以通常不需要提供定义;cascade属性:CascadeType[]类型。该属性定义类和类之间的级联关系。定义的级联关系将被容器视为对当前类对象及其关联类对象采取相同的操作,而且这种关系是递归调用的。cascade的值只能从CascadeType.PERSIST(级联新建)、CascadeType.REMOVE(级联删除)、CascadeType.REFRESH(级联刷新)、CascadeType.MERGE(级联更新)中选择一个或多个。还有一个选择是使用CascadeType.ALL,表示选择全部四项。@OneToMany、@ManyToOne@OneToMany、@ManyToOne代表一对多和多对一的关系,需要配合@JoinColumn一起使用。比如我们下面例子的用户与部门表,一个用户只属于一个部门,一个部门可以包含多个用户。@ManyToMany@ManyToMany代表多对多的关系。例如我们用于与角色的关联表,一般我们会设计一个用户、角色关联表的对应关系,配合@JoinTable一起使用。示例创建表部门表create table sys_dept ( dept_id bigint auto_increment comment '部门id' primary key, parent_id bigint default 0 null comment '父部门id', ancestors varchar(50) default '' null comment '祖级列表', dept_name varchar(30) default '' null comment '部门名称', order_num int default 0 null comment '显示顺序', leader varchar(20) null comment '负责人', phone varchar(11) null comment '联系电话', email varchar(50) null comment '邮箱', status char default '0' null comment '部门状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间' ) comment '部门表';用户表create table sys_user ( user_id bigint auto_increment comment '用户ID' primary key, dept_id bigint null comment '部门ID', user_name varchar(30) not null comment '用户账号', nick_name varchar(30) not null comment '用户昵称', user_type varchar(2) default '00' null comment '用户类型(00系统用户)', email varchar(50) default '' null comment '用户邮箱', phonenumber varchar(11) default '' null comment '手机号码', sex char default '0' null comment '用户性别(0男 1女 2未知)', avatar varchar(100) default '' null comment '头像地址', password varchar(100) default '' null comment '密码', status varchar(5) default 'start' null comment '帐号状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', login_ip varchar(50) default '' null comment '最后登录IP', login_date datetime null comment '最后登录时间', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间', remark longtext null comment '备注', recycle_company_id bigint null comment '所属回收单位', medical_institution_id bigint null comment '所属医疗机构', ethnicity bigint null comment '民族' ) comment '用户信息表';角色表create table sys_role ( role_id bigint auto_increment comment '角色ID' primary key, role_name varchar(30) not null comment '角色名称', role_key varchar(100) not null comment '角色权限字符串', role_sort int not null comment '显示顺序', data_scope char default '1' null comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', menu_check_strictly tinyint(1) default 1 null comment '菜单树选择项是否关联显示', dept_check_strictly tinyint(1) default 1 null comment '部门树选择项是否关联显示', status char not null comment '角色状态(0正常 1停用)', del_flag char default '0' null comment '删除标志(0代表存在 2代表删除)', create_by varchar(64) default '' null comment '创建者', create_time datetime null comment '创建时间', update_by varchar(64) default '' null comment '更新者', update_time datetime null comment '更新时间', remark varchar(500) null comment '备注' ) comment '角色信息表';用户角色关联表create table sys_user_role ( user_id bigint not null comment '用户ID', role_id bigint not null comment '角色ID', primary key (user_id, role_id) ) comment '用户和角色关联表';创建实体用户实体@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; @JoinColumn(name = "dept_id", referencedColumnName = "dept_id") @ManyToOne(cascade = CascadeType.ALL) private SysDept sysDept; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String createBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; private String updateBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "sys_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")}) private Set<SysRole> sysRoles; } 部门实体@Entity @Table(name = "sys_dept") @Getter @Setter @ToString @RequiredArgsConstructor public class SysDept { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "dept_id") private long deptId; private long parentId; private String ancestors; private String deptName; private long orderNum; private String leader; private String phone; private String email; private String status; private String delFlag; private String createBy; private java.sql.Timestamp createTime; private String updateBy; private java.sql.Timestamp updateTime; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; SysDept sysDept = (SysDept) o; return Objects.equals(deptId, sysDept.deptId); } @Override public int hashCode() { return 866095534; } } 角色实体@Entity @Data public class SysRole { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long roleId; private String roleName; private String roleKey; private long roleSort; private String dataScope; private long menuCheckStrictly; private long deptCheckStrictly; private String status; private String delFlag; private String createBy; private java.sql.Timestamp createTime; private String updateBy; private java.sql.Timestamp updateTime; private String remark; } 用户角色关联实体@Entity @IdClass(SysUserRole.class) @Data public class SysUserRole implements Serializable { @Id @Column(name = "user_id") private long userId; @Id @Column(name = "role_id") private long roleId; }Jpa查询接口这个接口不是必须的,可以调用任意的Crud接口。public interface SysUserRepository extends JpaRepository<SysUser, Long> { /** * 根据用户名称查找列表 * * @param userName 用户名称 * @return 列表 */ List<SysUser> findByUserName(String userName); // @Query("select u from SysUser u") Stream<SysUser> readAllBy(); Stream<SysUser> streamAllBy(); @Async Future<List<SysUser>> findAllBy(); @Query("select u from SysUser u where u.userName = :userName and u.email = :email") SysUser findByUserNameAndEmail(@Param("userName") String userName, @Param("email") String email); @Query("select u from SysUser u where u.nickName like %:nickName%") List<SysUser> findAllByNickName(@Param("nickName") String nickName); @Query(value = "select * from sys_user u where email like concat('%',:email,'%')", nativeQuery = true) List<SysUser> findAllByEmailLike(@Param("email") String email); @Query(value = "select u from SysUser u") Page<SysUser> findAllBy(Pageable pageable); SysUser findFirstByUserId(Long userId); @Modifying(clearAutomatically = true) @Query(value = "update SysUser set remark = '234' where userId = :userId") int setRemarkByUserId(@Param("userId") Long userId); }测试@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public SysUser findAll() { return sysUserRepository.findFirstByUserId(111L); } }
2021年07月21日
1,380 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa使用枚举
有时候在数据库设计的时候,我们可能会使用一些固定的值,比如启用、停用,只有这两个值,我们设计数据库可能设计成stop、start。这个时候,我们在Jpa中设计实体时,一般设置成枚举就更合理了。创建枚举public enum Status { stop("停用"), start("启用"); private String value; Status(String value) { this.value = value; } }实体@Entity @Table(name = "sys_user") @Data @Slf4j public class SysUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long userId; private Long deptId; private String userName; private String nickName; private String userType; private String email; @Column(name = "phonenumber") private String phoneNumber; private String sex; private String avatar; @JsonIgnore private String password; @Enumerated(EnumType.STRING) private Status status; private String delFlag; private String loginIp; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp loginDate; private String createBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp createTime; private String updateBy; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private java.sql.Timestamp updateTime; private String remark; private Long recycleCompanyId; private Long medicalInstitutionId; private Long ethnicity; } 查询我们可以查看一些查询的结果
2021年07月21日
1,020 阅读
0 评论
0 点赞
2021-07-21
Spring Data Jpa流查询
Spring Data Jpa可以通过使用Java 8 Stream作为返回类型,来逐步处理查询方法的结果,而不是简单的将查询结果包装在Stream数据存储中。接口定义public interface SysUserRepository extends JpaRepository<SysUser, Long> { /** * 根据用户名称查找列表 * * @param userName 用户名称 * @return 列表 */ List<SysUser> findByUserName(String userName); // @Query("select u from SysUser u") Stream<SysUser> readAllBy(); Stream<SysUser> streamAllBy(); }使用@RestController @RequestMapping("/user") public class UserController { @Resource private SysUserRepository sysUserRepository; @GetMapping("/findAll") @Transactional public List<SysUser> findAll() { try (Stream<SysUser> sysUserStream = sysUserRepository.streamAllBy()) { return sysUserStream.collect(Collectors.toList()); } catch (Exception ex) { ex.printStackTrace(); return new ArrayList<>(); } } }注意事项必须通过try catch关闭流方法必须有@Transactional注解
2021年07月21日
1,663 阅读
0 评论
0 点赞
1
...
21
22
23
...
53