4. gRPC4.1 gRPC介绍1. gRPC是由google开源的一个高性能的RPC框架。Stubby Google内部的RPC,演化而来的，2015正式开源。云原生时代是一个RPC标准。 2. gRPC核心的设计思路 1．网络通信 --->gRPC自己封装网络通信的部分提供多种语言的网络通信的封装(C Java[Netty] Go) 2．协议---> HTTP2传输数据的时候二进制数据内容。支持双向流(双工）连接的多路复用。 3．序列化--->基本文本JSON基于二进制Java原生序列化方式 Thrift二进制的序列化压缩二级制序列化。 protobuf (Protocol Buffers) google开源一种序列化方式︰时间效率和空间效率是JSON的3---5倍。 IDL语言 4．代理的创建--->让调用者像调用本地方法那样去调用远端的服务方法。 stub 3. gRPC 与ThriftRPC区别 共性:支持异构语言的RPC。 区别: 1．网络通信 Thrift TCP 专属协议 GRPC HTTP2 2．性能角度ThriftRPC 性能高于gRPC 3. gRPC 大厂背书(Google)，云原生时代与其他组件合作的顺利。所以gRPC应用更广泛。 4. gRPC的好处 1．高效的进行进程间通信。 2．支持多种语言原生支持C Go Java实现。C语言版本上扩展C++C# NodeJs Python Ruby PHP.. 3．支持多平台运行 Linux Android IOS Mac0s windows。 4. gPRC序列化方式采用protobuf，效率高。 5．使用Http2协议 6．大厂的背书 4.2 Http2.0协议1. 回顾 Http1.x协议 Http1.0协议 请求响应的模式 短链接协议（无状态协议） 传输数据文本结构 单工 无法实现服务的推送 变相实现推送（客户端轮询的方式） Http1.1协议 请求响应的模式 有限的长连接 升级的方式WebSocket 双工 实现服务器向客户端的推送。 管道网络传输(wei'bei's) 总结Http1.x协议 共性 1. 传输数据文本格式 可读性好 但是效率差。 2. 本质上Http1.x协议无法实现双工通信。 3. 资源的请求。需要发送多次请求，建立多个连接才可以完成。 2. HTTP2.0协议 1. http2.0协议是一个二进制协议，效率高于Http1.x协议，可读性差。 2. 可以实现双工通信 3. 一个请求，一个链接 可以请求多个数据。【多路复用】（如下图） 3. Http2.0协议的三个概念 1. 数据liu stream 2. 消息 message 3. 帧 frame 4. 其他的相关概念 1. 数据流的优先级，可以通过为不同的stream设置权重，来限制不同流的传输顺序。 2. 流控 --client发送的数据太快了，server处理不过来，通知client暂停数据的发送。4.3 Protocol buffers[protobuf]1. protobuf 是一种与变成语言无关【IDL】，与集体的平台无关【OS】。 它定义的中间语言，可以方便的在clinet 与server中进行RPC的数据传输。 2. protobuf 两个版本 proto2 proto3，但是目前主流应用都是proto3. 3. protobuf 主要安装protobuf的编译器，编译器的目的，可以把protobf的IDL语言，转换成具体的某一种语言。4.3.1 protobuf的编译器安装IDE 集成4.3.2 protobuf的语法文件格式xxx.proto UserServce.proto ORderService.proto版本设定syntax = "proto3"； (IDL语言)注释1. 单行注释 // 2. 多行注释 /* */与Java语言相关的语法optin 关键字开头的 可以选择使用语法 # 后续protobuf生成的java代码 一个源文件还是多个源文件 xx.java optin java_multiple_files = false; (true为多个或者false一个) 一般情况为false # 指定protobuf生成的类 放置在哪个包中 optin jva_package= "com.suns"; # 指定protobuf生成的外部类的名字（管理内部类【内部类才是正真开发使用】） optin java_outer_classname = "UserName"; (外部类)逻辑包（了解）# 对于protobuf对于文件内容管理 package xxx；导入UserService.proto OrderService.proto import "xxx/UserService.proto";基本类型枚举enm SEASON{ SPRING = 0; SUMMER=1; } 枚举的值 必须是0开始消息 messagesmessages LoginRequset { string username = 1; string password = 2； int32 age = 3； } 编号 从1开始 到2^29-1 注意：19000 -19999 不能用这个区间内的编号，因为他是protobuf自己保留的。 (关键字) - singular ：这个字段的值 只能是0个或1个（默认关键字） null "123456" - repwated message Result{ string conten = 1; repeated string stutas =2; //这个字段 返回值 等价于 java List Protobuf getStatusList() -->List } protobuf[grpc] 可以定义多个消息 message LonginRequest{ .... } message LoginRespone { .... } 消息可以被嵌套 message SerchResponse{ message Result{ string url =1; string titlr = 2; } string xxx =1; int32 yyy =2; Result ppp = 3; } SerchResponse.Result message AAA { string xxx =1; SearchResponse.Result yyy =2; } oneof[其中一个] message SimpleMssage{ onof test_oneof{ string name = 1; int32 age =2; } test_oneof xxx }服务service HelloService{ rpc hello(HelloRequest) returns(HelloResponse){} } # 里面可以定义多个服务方法 # 定义多个服务接口 # gRPC 服务 4个服务方式4.3.3 第一个gRPC的开发项目结构1. xxx-api 模块 定义 protobuf IDL语言 并且通过命令创建具体的代码，后续client server引入使用 1. message 2. service 2. xxx-server 模块 1. 实现api模块中定义的服务接口 2. 发布gPRC服务（创建服务端程序） 3. xxx-clien 模块 1. 创建服务端stub（代理） 2. 基于代理 (stub) PRC调用。api1. .proto文件 书写 protobuf的IDL 2. protoc命令 把proto文件的IDL 转换成编程语言 protoc ---java_out=/xxx/xxx /xxx/xxx/xx.proto 3. 【实战】 mmaven插件 进行protobuf IDL文件的编译，并把它放置IDEA具体位置 pom.xml <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-netty-shaded</artifactId> <version>1.58.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-protobuf</artifactId> <version>1.58.0</version> </dependency> <dependency> <groupId>io.grpc</groupId> <artifactId>grpc-stub</artifactId> <version>1.58.0</version> </dependency> <dependency> <groupId>org.apache.tomcat</groupId> <artifactId>annotations-api</artifactId> <version>6.0.53</version> <scope>provided</scope> </dependency> <build> <extensions> <extension> <groupId>kr.motd.maven</groupId> <artifactId>os-maven-plugin</artifactId> <version>1.7.1</version> </extension> </extensions> <plugins> <plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.6.1</version> <configuration> <protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.58.0:exe:${os.detected.classifier}</pluginArtifact> <outputDirectory>${basedir}/src/main/java</outputDirectory> <clearOutputDirectory>false</clearOutputDirectory> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>xxx-server 服务端模块的开发1. 实现业务接口 添加具体的功能 （Mybatis+Mysql） package cn.zzrg.service; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.stub.StreamObserver; /** * @Author ZzRG * @Date 2023/9/24 16:48 * @Version 1.0 */ public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase { /** * 1. 接受client提交的参数 * 2. 业务处理 service + dao 调用对应的业务功能。 * 3. 提供返回值 * @param request * @param responseObserver */ @Override public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) { // 1. 接收客户端的请求参数 String name = request.getName(); // 2. 业务处理 System.out.println("name parameter" + name); // 3. 封装响应 // 3.1 创建响应对象的构造者 HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder(); // 3.2 填充数据 builder.setResult("hello method invoke ok"); // 3.3 封装响应 HelloProto.HelloResponse helloResponse = builder.build(); responseObserver.onNext(helloResponse); responseObserver.onCompleted(); } } 2. 创建服务端 (Netty) package cn.zzrg.service; import io.grpc.Server; import io.grpc.ServerBuilder; import java.io.IOException; /** * @Author ZzRG * @Date 2023/9/24 16:57 * @Version 1.0 */ public class GrpcServer1 { public static void main(String[] args) throws IOException, InterruptedException { // 1.绑定端口 ServerBuilder<?> serverBuilder = ServerBuilder.forPort(9000); // 2.发布服务 serverBuilder.addService(new HelloServiceImpl()); // 3.创建服务对象 Server server = serverBuilder.build(); server.start(); server.awaitTermination(); } } xxx-client 模块1. client通过代理对象完成远程调用 package cn.zzrg; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient { public static void main(String[] args) { // 1 创建通信的管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); // 2 获得代理对象 stub try { HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel); // 3 完成RPC调用 // 3.1 准备参数 HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder(); builder.setName("ZzRG"); HelloProto.HelloRequest helloRequest = builder.build(); // 3.2 进行功能rpc调用 获取相应的内容 HelloProto.HelloResponse helloResponse = helloService.hello(helloRequest); String result = helloResponse.getResult(); System.out.println("result" + result); } catch (Exception e) { throw new RuntimeException(e); }finally { managedChannel.shutdown(); } } }注意事项服务端 处理返回值时 responseObserver.onNext(helloResponse1); // 通过这个方法 把响应的消息 回传client responseObserver.onCompleted(); //通过client 整个服务结束。 底层返回标记 // clinet就会监听标记[grpc做的] responseObserver.onNext(helloRequest1); responseObserver.onCompleted();4.3.4 gRPC的四种通讯方式四种通讯方式1. 简单rpc 一元rpc（Unary RPC） 2. 服务端流式RPC (Server Streaming RPC) 3. 客户端流式RPC (Client Streaming RPC) 4. 双向流RPC (Bi-directional Stream RPC)简单RPC（一元RPC）1. 第一个RPC程序，实际上就是一元RPC特点当client发起调用后，提交数据，并且等待 服务端响应。 开发过程中，主要采用的就是一元RPC的这种通讯方式语法service HelloService{ rpc hello(HelloRequest) returns (HelloResponse){} rpc hello1(HelloRequest1) returns(HelloResponse1){} }服务端流式RPC一个请求对象，服务端可以回传多个结果对象特点使用场景client ----------> Server 股票的编号 <--------- 某一个时刻的 股票行情proto语法service HelloService{ rpc hello(HelloRequest) returns (stream HelloResponse){} //【服务端流式RPC】 rpc hello1(HelloRequest1) returns(HelloResponse1){} //【一元RPC】 }关键性代码服务端代码： @Override public void c2ss(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) { // 接受client客户端的请求参数 String name = request.getName(); // 做业务处理 System.out.println("name = " + name); // 根据业务处理的结果,提供响应 for (int i = 0; i < 9; i++) { HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder(); builder.setResult("处理结果" + i); HelloProto.HelloResponse helloResponse = builder.build(); responseObserver.onNext(helloResponse); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } responseObserver.onCompleted(); } 客户端代码： package cn.zzrg; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.Iterator; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient2 { public static void main(String[] args) { // 获得管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); try { HelloServiceGrpc.HelloServiceBlockingStub helloService = HelloServiceGrpc.newBlockingStub(managedChannel); HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder(); builder.setName("ZzRG"); HelloProto.HelloRequest helloRequest = builder.build(); Iterator<HelloProto.HelloResponse> helloResponseIterator = helloService.c2ss(helloRequest); while (helloResponseIterator.hasNext()){ HelloProto.HelloResponse helloResponse = helloResponseIterator.next(); System.out.println("helloResponse.getResult()= " + helloResponse.getResult()); } } catch (Exception e) { throw new RuntimeException(e); }finally { managedChannel.shutdown(); } } } 监听 异步方式 处理服务流式RPC的开发 1. api 2. 服务端 3. 客户端 package cn.zzrg; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; import java.util.Iterator; import java.util.concurrent.TimeUnit; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient3 { public static void main(String[] args) { // 获取管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); try { HelloServiceGrpc.HelloServiceStub helloService = HelloServiceGrpc.newStub(managedChannel); HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder(); builder.setName("ZzRG"); HelloProto.HelloRequest helloRequest = builder.build(); helloService.c2ss(helloRequest, new StreamObserver<HelloProto.HelloResponse>() { @Override public void onNext(HelloProto.HelloResponse value) { // 服务端 响应了 一个消息后 需要处理的话 把代码写在这个方法中 System.out.println("服务端每一次响应消息" +value.getResult()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { // 需要把服务 响应的所有数据 拿到后 在进行业务的处理 System.out.println("服务端响应结束 后续可以根据需要 在这里统一处理服务端响应所有的内容"); } }); // 解决相应太快的问题 managedChannel.awaitTermination(5, TimeUnit.SECONDS); } catch (Exception e) { throw new RuntimeException(e); }finally { // 异步编程因为没有阻塞 顺序执行代码太快了 直接shutdown // 服务端响应 发现client 关了 managedChannel.shutdown(); } } } 客户端流式RPC客户端发送多个请求对象，服务端只返回一个结果应用场景IOT(物联网【传感器】) 向服务端 发送数据proto语法rpc cs2s(stream HelloRequest) returns(HelloResponse){}关键性代码1.api rpc cs2s(stream HelloRequest) returns(HelloResponse){} 2.服务端开发 // 客户端 流式RPC 返回值 @Override public StreamObserver<HelloProto.HelloRequest> cs2s(StreamObserver<HelloProto.HelloResponse> responseObserver) { return new StreamObserver<HelloProto.HelloRequest>() { @Override public void onNext(HelloProto.HelloRequest value) { System.out.println("接受到了client发送的一条消息" + value); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { System.out.println("client的所有消息,都发送到了服务器..."); // 提供响应的目的: 当接受了全部的client 提交的信息,并处理后 提供响应 HelloProto.HelloResponse.Builder builder = HelloProto.HelloResponse.newBuilder(); builder.setResult("this is result"); HelloProto.HelloResponse helloResponse = builder.build(); responseObserver.onNext(helloResponse); responseObserver.onCompleted(); } }; } 3.客户端开发 package cn.zzrg; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; import java.util.concurrent.TimeUnit; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient4 { public static void main(String[] args) { // 获取管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); try { HelloServiceGrpc.HelloServiceStub helloService = HelloServiceGrpc.newStub(managedChannel); // 监听返回响应 StreamObserver<HelloProto.HelloRequest> helloRequestStreamObserver = helloService.cs2s(new StreamObserver<HelloProto.HelloResponse>() { @Override public void onNext(HelloProto.HelloResponse value) { // 监控响应 System.out.println("服务端 响应 数据内容为" + value.getResult()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { System.out.println("服务端响应结束..."); } }); // 客户端 发送数据 到服务端 多条数据 ,不定时... for (int i = 0; i < 10; i++) { HelloProto.HelloRequest.Builder builder = HelloProto.HelloRequest.newBuilder(); builder.setName("ZzRG" + i); HelloProto.HelloRequest helloRequest = builder.build(); helloRequestStreamObserver.onNext(helloRequest); } helloRequestStreamObserver.onCompleted(); // 解决相应太快的问题 managedChannel.awaitTermination(12, TimeUnit.SECONDS); } catch (Exception e) { throw new RuntimeException(e); }finally { // 异步编程因为没有阻塞 顺序执行代码太快了 直接shutdown // 服务端响应 发现client 关了 managedChannel.shutdown(); } } }双向流式RPC客户端可以发送个请求消息，服务端响应多个响应消息 应用场景聊天室proto语法rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){}关键性代码1. api rpc cs2ss(stream HelloRequest) returns(stream HelloResponse){} 2. 服务端开发 //双向流式RPC的客户端开发 @Override public StreamObserver<HelloProto.HelloRequest> cs2ss(StreamObserver<HelloProto.HelloResponse> responseObserver) { return new StreamObserver<HelloProto.HelloRequest>() { @Override public void onNext(HelloProto.HelloRequest value) { System.out.println("接受到了client发送的一条消息" + value.getName()); responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("response" + value.getName() + "value").build()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { System.out.println("接受到了所有请求的消息...."); responseObserver.onCompleted(); } }; } 3. 客户端开发 package cn.zzrg; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.stub.StreamObserver; import java.util.concurrent.TimeUnit; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient5 { public static void main(String[] args) { // 获取管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); try { HelloServiceGrpc.HelloServiceStub helloService = HelloServiceGrpc.newStub(managedChannel); // 监听返回响应 StreamObserver<HelloProto.HelloRequest> helloRequestStreamObserver = helloService.cs2s(new StreamObserver<HelloProto.HelloResponse>() { @Override public void onNext(HelloProto.HelloResponse value) { // 监控响应 System.out.println("服务端 响应 数据内容为" + value.getResult()); } @Override public void onError(Throwable throwable) { } @Override public void onCompleted() { System.out.println("服务端响应结束..."); } }); // 客户端 发送数据 到服务端 多条数据 ,不定时... for (int i = 0; i < 10; i++) { HelloProto.HelloRequest helloRequest = HelloProto.HelloRequest.newBuilder().setName("ZzRG" + i).build(); helloRequestStreamObserver.onNext(helloRequest); } helloRequestStreamObserver.onCompleted(); // 解决相应太快的问题 managedChannel.awaitTermination(12, TimeUnit.SECONDS); } catch (Exception e) { throw new RuntimeException(e); }finally { // 异步编程因为没有阻塞 顺序执行代码太快了 直接shutdown // 服务端响应 发现client 关了 managedChannel.shutdown(); } } }4.3.5 gRPC的代理方式1. BlockingStub 阻塞 通信方式 2. Stub 异步 通过监听处理的 3. FutureStub //不常用 同步 异步 NettyFuture 1. FutureStub 只能应用一元RPC第三种方式package cn.zzrg; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import com.zzrg.TestProto; import com.zzrg.TestServiceGrpc; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @Author ZzRG * @Date 2023/9/24 17:02 * @Version 1.0 */ public class GrpcClient6 { public static void main(String[] args) { // 1 创建通信的管道 ManagedChannel managedChannel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build(); // 2 获得代理对象 stub try { TestServiceGrpc.TestServiceFutureStub testServiceFutureStub = TestServiceGrpc.newFutureStub(managedChannel); ListenableFuture<TestProto.TestResponse> responseListenableFuture = testServiceFutureStub.testZzRG(TestProto.TestRequest.newBuilder().setResult("this is result").build()); // 同步操作 // TestProto.TestResponse testResponse = responseListenableFuture.get(); // System.out.println(testResponse.getName()); // responseListenableFuture.addListener(()->{ // System.out.println("异步的RPC响应 回来了"); // }, Executors.newCachedThreadPool()); Futures.addCallback(responseListenableFuture, new FutureCallback<TestProto.TestResponse>() { @Override public void onSuccess(TestProto.TestResponse result) { System.out.println("result.getResult" + result.getResult()); } @Override public void onFailure(Throwable t) { } }, Executors.newCachedThreadPool()); System.out.println("后续操作..."); managedChannel.awaitTermination(12, TimeUnit.SECONDS); } catch (Exception e) { throw new RuntimeException(e); }finally { managedChannel.shutdown(); } } }5.gRPC与StringBoot整合5.1gRPC与Stringboot整合的思想1. grpc-server 2. grpc-clientStringBoot与GRPC整个过程中 对于服务端做了什么封装搭建开发环境1. 搭建StringBoot的开发环境 2. 引入GRPC相关内容 <dependency> <groupId>cn.zzrg</groupId> <artifactId>grpc-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>net.devh</groupId> <artifactId>grpc-server-spring-boot-starter</artifactId> <version>2.14.0.RELEASE</version> </dependency>开发服务端package com.zzrg.service; import com.zzrg.HelloProto; import com.zzrg.HelloServiceGrpc; import io.grpc.stub.StreamObserver; import net.devh.boot.grpc.server.service.GrpcService; /** * @Author ZzRG * @Date 2023/9/25 12:56 * @Version 1.0 */ // 重复多次 @GrpcService public class HelloServiceImpl extends HelloServiceGrpc.HelloServiceImplBase { @Override public void hello(HelloProto.HelloRequest request, StreamObserver<HelloProto.HelloResponse> responseObserver) { String name = request.getName(); System.out.println("name is" + name); responseObserver.onNext(HelloProto.HelloResponse.newBuilder().setResult("this is result").build()); responseObserver.onCompleted(); } } //yml 核心配置 gRPC端口 spring: application: name: boot-server # 不启动tomcat main: web-application-type: none grpc: server: port: 9000 客户端环境搭建 <dependency> <groupId>net.devh</groupId> <artifactId>grpc-client-spring-boot-starter</artifactId> <version>2.14.0.RELEASE</version> </dependency>编码1. yml spring: application: name: boot-client grpc: client: grpc-server: address: 'static://127.0.0.1:9000' negotiation-type: plaintext 2. 注入stub @GrpcClient("grpc-server") private HelloServiceGrpc.HelloServiceBlockingStub stub; 3. 引入 @RequestMapping("/test") public String test(String name) { System.out.println("name = " + name); HelloProto.HelloResponse helloResponse = stub.hello(HelloProto.HelloRequest.newBuilder().setName(name).build()); return helloResponse.getResult(); }gRPC的高级应用1. 拦截器 一元拦截器 2. Stram Tracer【监听流】 流拦截器 解决后三种通讯方式 3. Retry Policy 客户端重试 4. NameResolver 5. 负载均衡 （pick-first ，轮询） 6. grpc与微服务的整合 序列化 （protobuf）Dobbo grpc Dobbo grpc GateWay grpc JWT grpc Nacos2.0 grpc OpenFeign
分布式锁CREATE TABLE `stock` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_code` varchar(20) NOT NULL, `warehouse` varchar(20) NOT NULL, `count` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_pc` (`product_code`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;1.JVM本地锁2.一个sql3.悲观锁：select ...from updatemysql悲观锁中使用行级锁：1.锁的查询或者更新条件必须是索引字段 2.查询或者更新条件必须是具体值 不能使用 like这样的模糊查询Mapper代码实现：@select("select * from stock where product_code = #{productCode} for update;") List<Stock> queryStock(String productCode);service实现：解决了锁定的范围 同时一个商品的多条库存记录 记录了库存变化前后的状态缺点问题： 1.性能问题 性能较低2.会产生死锁： 对多条数据加锁时，加锁的顺序要一致 3.库存操作要统一：用select ...for update 而用普通select锁不住4.mysql的乐观锁：时间戳version版本号CAS机制CREATE TABLE `stock` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_code` varchar(20) NOT NULL, `warehouse` varchar(20) NOT NULL, `count` int(11) NOT NULL, `version` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `idx_pc` (`product_code`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;如果version与mysql中的不相等就更新失败 再次进行尝试！servic实现：会出现栈溢出 还有超时问题！解决栈溢出问题MDL 更新删除操作 会进行加锁 就会出现超时问题去掉手动事务@Transactional（事务也会加锁） 后面当进行到 if（this....update...）时候 本来有悲观锁 当执行失败的就会放掉锁 后面就不会阻塞 最终：乐观锁存在的问题：1.高并发的情况下，性能极低。 （乐观锁适合读多写少的情况）2.CAS会产生ABA问题。3.读写分离的情况下导致乐观锁不可靠myqsl锁总结：性能：一个sql>悲观锁>JVM锁>乐观锁如果追求极致的性能，业务场景简单并不需要记录前后变化的情况下 优先选择：一个sql；如果写并发量低（读多），争抢不是很激烈的情况下 优先选择：乐观锁；如果写并发量较高，一般会经常冲突，此时选择乐观锁的话，会导致业务代码间的不断重试。 应该优先选择：悲观锁；不推荐使用JVM本地锁；
微服务网关Gateway使用为什么需要网关？Gateway网关是我们服务的守门神，所有微服务的统一入口。网关的核心功能特性1. 请求路由和负载均衡 一切请求都必须先经过gateway，但网关不处理业务，而是根据某种规则，把请求转发到某个微服务，这个过程叫做路由。当然路由的目标服务有多个时，还需要做负载均衡。2. 权限控制 网关作为微服务入口，需要校验用户是是否有请求资格，如果没有则进行拦截。3. 限流 请求流量过高时，在网关中按照下流的微服务能够接受的速度来放行请求，避免服务压力过大。架构图gateway快速入门基本步骤如下：1、创建SpringBoot工程gateway，引入网关依赖 <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>2、编写启动类package com.cn.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }3、编写基础配置和路由规则server: port: 10010 # 网关端口 spring: application: name: gateway # 服务名称 cloud: nacos: server-addr: localhost:8848 # nacos地址 gateway: routes: # 网关路由配置 - id: user-service # 路由id，自定义，只要唯一即可 # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址 uri: lb://userservice # 路由的目标地址 lb就是负载均衡，后面跟服务名称 predicates: # 路由断言，也就是判断请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配，只要以/user/开头就符合要求 我们将符合Path 规则的一切请求，都代理到 uri参数指定的地址。本例中，我们将 /user/** 开头的请求，代理到lb://userservice，lb是负载均衡，根据服务名拉取服务列表，实现负载均衡。4、启动网关服务进行测试重启网关，访问 http://localhost:10010/user/1 时，符合/user/**规则，请求转发到uri：http://userservice/user/15、网关路由的流程图总结网关搭建步骤创建项目，引入nacos服务发现和gateway依赖配置application.yml，包括服务基本信息、nacos地址、路由路由配置包括路由id：路由的唯一标识路由目标（uri）：路由的目标地址，http代表固定地址，lb代表根据服务名负载均衡路由断言（predicates）：判断路由的规则，路由过滤器（filters）：对请求或响应做处理路由断言和路由过滤器断言工厂名称说明示例After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]Cookie请求必须包含某些cookie- Cookie=chocolate, ch.pHeader请求必须包含某些header- Header=X-Request-Id, \d+Host请求必须是访问某个host（域名）- Host=.somehost.org,.anotherhost.orgMethod请求方式必须是指定方式- Method=GET,POSTPath请求路径必须符合指定规则- Path=/red/{segment},/blue/**Query请求参数必须包含指定参数- Query=name, Jack或者- Query=nameRemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24Weight权重处理 过滤器工厂GatewayFilter是网关中提供的一种过滤器，可以对进入网关的请求和微服务返回的响应做处理：Spring提供了31种不同的路由过滤器工厂。例如：名称说明AddRequestHeader给当前请求添加一个请求头RemoveRequestHeader移除请求中的一个请求头AddResponseHeader给响应结果中添加一个响应头RemoveResponseHeader从响应结果中移除有一个响应头RequestRateLimiter限制请求的流量请求头过滤器下面我们以AddRequestHeader 为例来讲解。{alert type="info"}需求：给所有进入userservice的请求添加一个请求头：Truth=itcast is freaking awesome!{/alert}只需要修改gateway服务的application.yml文件，添加路由过滤即可：spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器 - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头当前过滤器写在userservice路由下，因此仅仅对访问userservice的请求有效。默认过滤器如果要对所有的路由都生效，则可以将过滤器工厂写到default下。格式如下：spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** default-filters: # 默认过滤项 - AddRequestHeader=Truth, Itcast is freaking awesome!过滤器的作用是什么？对路由的请求或响应做加工处理，比如添加请求头配置在路由下的过滤器只对当前路由的请求生效defaultFilters的作用是什么？对所有路由都生效的过滤器全局过滤器网关提供了31种，但每一种过滤器的作用都是固定的。如果我们希望拦截请求，做自己的业务逻辑则没办法实现。定义方式是实现GlobalFilter接口。自定义全局过滤器{callout color="#f0ad4e"}需求：定义全局过滤器，拦截请求，判断请求的参数是否满足下面条件：参数中是否有authorizationauthorization参数值是否为admin如果同时满足则放行，否则拦截如果同时满足则放行，否则拦截{/callout}实现：在gateway中定义一个过滤器：package com.cn.gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取请求参数 MultiValueMap<String, String> params = exchange.getRequest().getQueryParams(); // 2.获取authorization参数 String auth = params.getFirst("authorization"); // 3.校验 if ("admin".equals(auth)) { // 放行 return chain.filter(exchange); } // 4.拦截 // 4.1.禁止访问，设置状态码 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); // 4.2.结束处理 return exchange.getResponse().setComplete(); } } 过滤器执行顺序请求进入网关会碰到三类过滤器：当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后，会将当前路由过滤器和DefaultFilter、GlobalFilter，合并到一个过滤器链（集合）中，排序后依次执行每个过滤器：排序的规则是什么呢？每一个过滤器都必须指定一个int类型的order值，order值越小，优先级越高，执行顺序越靠前。GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己指定路由过滤器和defaultFilter的order由Spring指定，默认是按照声明顺序从1递增。当过滤器的order值一样时，会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行。
在Spring Security 中有两种资源放行的策略若是你但愿用户不用登陆就能访问，那么通常来讲，有两种配置策略：java第一种就是在 configure(WebSecurity web) 方法中配置放行，像下面这样：web@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode"); }第二种方式是在 configure(HttpSecurity http) 方法中进行配置：springhttp.authorizeRequests() .antMatchers("/hello").permitAll() .anyRequest().authenticated()两种方式最大的区别在于，第一种方式是不走 Spring Security 过滤器链，而第二种方式走 Spring Security 过滤器链，在过滤器链中，给请求放行。有的资源可使用第一种方式额外放行，不须要验证，例如前端页面的静态资源，就能够按照第一种方式配置放行。有的资源放行，则必须使用第二种方式，例如登陆接口。你们知道，登陆接口也是必需要暴露出来的，不须要登陆就能访问到的，可是咱们却不能将登陆接口用第一种方式暴露出来，登陆请求必需要走 Spring Security 过滤器链，由于在这个过程当中，还有其余事情要作。
一、string常用方法valueOf(基础数据类型 b) 将基础类型数据的文本转换为字符串 substring(int beginIndex) 切割原字符串，返回一个新的字符串， beginIndex：表示从下标为几的地方开始切割 replace(char oldChar, char newChar) 返回一个新的字符串，它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 trim() 返回字符串的副本，忽略前导空白和尾部空白。 isEmpty() 当且仅当 length() 为 0 时返回 true。 toLowerCase() 将此 String 中的所有字符都转换为小写。 toUpperCase() 将此 String 中的所有字符都转换为大写。 二、在字符串中查询指定子字符串的位置charAt(int index) 获取指定索引处的 char 值。没有则为null indexOf(string) 返回指定字符在此字符串中第一次出现处的索引。没有则为0 lastIndexOf(int ch) 返回指定字符在此字符串中最后一次出现处的索引。 lastIndexOf(int ch, int fromIndex) 返回指定字符在此字符串中最后一次出现处的索引，从指定的索引处开始进行反向搜索。 contains(CharSequence s) 判断字符串是否包含指定的 char 值，有则返回 true。三、比较两个字符串的内容equals(Object anObject) 将此字符串与指定的对象比较。 equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较，不考虑大小写。四、判断是否以指定的字符开头或者结尾endsWith(String suffix) 判断此字符串是否以指定的后缀结束。 startsWith(String prefix) 判断此字符串是否以指定的前缀开始。五、将字符串转换为字符数组，和字节数组format(Locale l, String format, Object... args) 格式字符串和参数返回一个格式化字符串。 getBytes(String charset) 将string转换为byte 数组。 getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 将string转换为字符数组 toCharArray() 将此字符串转换为一个新的字符数组。六、正则表达式matches(String regex) 判断此字符串是否匹配给定的正则表达式。 replaceAll(String regex, String replacement) 使用给定的 replacement 替换此字符串所有匹配给定的正则表达式的子字符串。 replaceFirst(String regex, String replacement) 使用给定的 replacement 替换此字符串匹配给定的正则表达式的第一个子字符串。 split(String regex) 根据给定正则表达式的匹配拆分此字符串。七、StringBuffer/StringBuilder类常用方法StringBuffer 上的主要操作是 append 和 insert 方法， append 方法始终将这些字符添加到缓冲区的末端；而 insert 方法则在指定的索引下添加字符。 StringBuffer(String str) 构造一个字符串缓冲区，并将其内容初始化为指定的字符串内容。 append(基础类型数据 b) 将 基础类型的字符串表示形式追加到序列后面。 charAt(int index) 返回此序列中指定索引处的 char 值。 delete(int start, int end) 移除此序列的子字符串中的字符。 deleteCharAt(int index) 移除此序列指定位置的 char。 insert(int offset, 基础类型数据 b) 将 基础类型参数的字符串表示形式插入指定位置中。 toString() 返回此序列中数据的字符串表示形式。
synchronized的变化①. java5以前,只有Synchronized,这个是操作系统级别的重量级操作,重量级锁,假如锁的竞争比较激烈的话,性能下降②. 在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因。Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁③. 为什么每一个对象都可以成为一个锁?Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高④. Mutex LockMonitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。⑤. Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁,需要有个逐步升级的过程,别一开始就捅到重量级锁⑥. synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略无锁偏向锁单个线程多次访问定义：①. 主要作用:当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁(偏向锁)同一个老顾客来访,直接老规矩行方便偏向锁为了解决只有在一个线程执行同步时提高性能②. 64位标记图再看(通过CAS方式修改markword中的线程ID)③. 偏向锁的理论在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的④. 技术实现一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入Monitor去竞争对象了。⑤. 对于如上的③、④进行细化锁对比偏向锁的操作不用直接捅到操作系统，不涉及用户到内核转换，不必要直接升级为最高级，我们以一个account对象的“对象头”为例，假如有一个线程执行到synchronized代码块的时候，JVMM使用CAS操作把线程指针ID记录到Mark Word当中，并修改标偏向标示，标示当前线程就获得该锁。锁对象变成偏向锁（通过CAS修改对象头里的锁标志位〉，字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后，线程并不会主动释放偏向锁。这时线程获得了锁，可以执行同步代码块。当该线程第二次到达同步代码块时会判断此时持有锁的线程是否还是自己（持有锁的线程ID也在对象头里)，JVM通过account对象的Mark Word判断:当前线程ID还在，说明还持有着这个对象的锁，就可以继续进入临界区工作。由于之前没有释放锁，这里也就不需要重新加锁。如果自始至终使用锁的线程风有一个，很明显偏向锁几乎没有额外开销，性能极高。结论:JVM不用和操作系统协商设置Mutex(争取内核)，它只需要记录下线程ID就标示自己获得了当前锁，不用操作系统接入。上述就是偏向锁:在没有其他线程竞争的时候，一直偏向偏心当前线程，当前线程可以一直执行。实际上偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟, 4秒所以需要添加参数-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0关闭偏向锁:关闭之后程序默认会直接进入轻量级锁状态。-XX:-UseBiasedLockingpublic class MyObject{ public static void main(String[] args){ Object o = new Object(); new Thread(() -> { synchronized (o){ System.out.println(ClassLayout.parseInstance(o).toPrintable()); } },"t1").start(); } } //查看状态此时情况下偏向锁4秒后开启偏向锁的撤销偏向锁的撤销(偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行)第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁 ,重新偏向(我的理解是,其实如果线程A执行完毕,如果不再去竞争,那么就会重新线程B为偏向锁;如果线程A继续竞争,那么就会CAS自旋 也就升级到了轻量级锁)轻量级锁多线程竞争，但是任意时刻最多只有一个线程竞争，即不存在锁竞争太过激烈的情况，也就没有线程阻塞。有线程来参与锁的竞争,但是获取锁的冲突时间极短（本质就是自旋锁）轻量级锁的获取如果关闭偏向锁,就可以直接进入轻量级锁 -XX:-UseBiasedLocking自旋达到一定次数和程度没有获取则会升级锁java6之前(了解):默认启用,默认情况下自旋的次数是10次,-XX:PreBlockSpin=10来修改或者自旋线程数超过cpu核数一半Java6之后:自适应(自适应意味着自旋的次数不是固定不变的),而是根据:同一个锁上一次自旋的时间和拥有锁线程的状态来决定。重锁①. 有大量的线程参与锁的竞争,冲突性很高②. 锁标志位锁升级后，hashcode值无锁地址在31位偏向锁没有，需要获取的时候需要锁升级。当一个对象已经计算过identity hash code 它就无法进入到偏向锁状态时跳过偏向锁，直接生成轻量级锁。偏向锁过程中遇到一致性哈希计算请求，立马撤销偏向模式，膨胀为重量级锁锁的优缺点synchronized锁升级过程总结:一句话,就是先自旋,不行再阻塞。 实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。. 偏向锁、轻量级锁、重量级锁总结偏向锁:适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁锁消除/ 锁粗化锁消除:从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用** * 锁消除 * 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用, * 极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用 */ public class LockClearUPDemo{ static Object objectLock = new Object();//正常的 public void m1(){ //锁消除,JIT会无视它,synchronized(对象锁)不存在了。不正常的 Object o = new Object(); synchronized (o){ System.out.println("-----hello LockClearUPDemo"+"\t"+o.hashCode()+"\t"+objectLock.hashCode()); } } public static void main(String[] args){ LockClearUPDemo demo = new LockClearUPDemo(); for (int i = 1; i <=10; i++) { new Thread(() -> { demo.m1(); },String.valueOf(i)).start(); } } } /** * 锁粗化 * 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块, * 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能 */ public class LockBigDemo { static Object objectLock = new Object(); public static void main(String[] args) { new Thread(() -> { synchronized (objectLock) { System.out.println("11111"); } synchronized (objectLock) { System.out.println("22222"); } synchronized (objectLock) { System.out.println("33333"); } },"a").start(); new Thread(() -> { synchronized (objectLock) { System.out.println("44444"); } synchronized (objectLock) { System.out.println("55555"); } synchronized (objectLock) { System.out.println("66666"); } },"b").start(); } }
volatile的特性可见性:可见性：保证不同线程对某个变量完成操作后结果及时可见，即该共享变量一旦改变所有线程立即可见诉求:1.线程中修改了自己工作内存中的副本之后，立即将其刷新到主内存;2.工作内存中每次读取共享变量时，都去主内存中重新读取，然后拷贝到工作内存。使用volatile修饰共享变量，就可以达到上面的效果，被volatile修改的变量有以下特点:1.线程中读取的时候，每次读取都会去主内存中读取共享变量最新的值，然后将其复制到工作内存2.线程中修改了工作内存中变量的副本，修改之后会立即刷新到主内存没有原子性：volatile变量不适合参与到依赖当前值的运算，如i =i+ 1; i++;之类的那么依靠可见性的特点volatile可以用在哪些地方呢?通常volatile用做保存某个状态的boolean值or int值。由于volatile变量只能保证可见性，在不符合以下两条规则的运算场景中，我们仍然要通过加锁(使用synchronized、java.util.concurrent中的锁或原子类）来保证原子性·运算结果并不依赖变量的当前值，或者能够确保只有单一的线程修改变量的值.·变量不需要与其他的状态变量共同参与不变约束。禁止指令重排重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段，有时候会改变程序语句的先后顺序不存在数据依赖关系，可以重排序;存在数据依赖关系，禁止重排序四种内存屏障策略内存屏障︰是一种屏障指令，它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束。也叫内存栅栏或栅栏指令作用：阻止屏障两边的指令重排序写数据时加入屏障，强制将线程私有工作内存的数据刷回主物理内存读数据时加入屏障，线程私有工作内存的数据失效，重新到主物理内存中获取最新的数据happens-before之volatile变量规则①.当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前②.当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatile写之后③.当第一个操作为volatile写时,第二个操作为volatile读时,不能重排①. 写在每个volatile写操作的前⾯插⼊⼀个StoreStore屏障在每个volatile写操作的后⾯插⼊⼀个StoreLoad屏障写指令：②. 读在每个volatile读操作的后⾯插⼊⼀个LoadLoad屏障在每个volatile读操作的后⾯插⼊⼀个LoadStore屏障读指令：数据依赖性:若两个操作访问同一变量，且这两个操作中有一个为写操作，此时两操作间就存在数据依赖性。volatile的用法状态标志，判断业务是否结束使用:作为一个布尔状态标志，用于指示发生了一个重要的一次性事件，例如完成初始化或任务结 理由:状态标志并不依赖于程序内任何其他状态，且通常只有一种状态转换例子:判断业务是否结京public class UseVolatileDemo{ private volatile static boolean flag = true; public static void main(String[] args){ new Thread(() -> { while(flag) { //do something...... } },"t1").start(); //暂停几秒钟线程 try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { flag = false; },"t2").start(); } } 开销较低的读,写锁策略public class UseVolatileDemo{ /** * 使用:当读远多于写，结合使用内部锁和 volatile 变量来减少同步的开销 * 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性 */ public class Counter{ private volatile int value; public int getValue(){ return value; //利用volatile保证读取操作的可见性 } public synchronized int increment(){ return value++; //利用synchronized保证复合操作的原子性 } } } 单列模式 DCL双端锁的发布public class SafeDoubleCheckSingleton{ //通过volatile声明，实现线程安全的延迟初始化。 private volatile static SafeDoubleCheckSingleton singleton; //私有化构造方法 private SafeDoubleCheckSingleton(){ } //双重锁设计 public static SafeDoubleCheckSingleton getInstance(){ if (singleton == null){ //1.多线程并发创建对象时，会通过加锁保证只有一个线程能创建对象 synchronized (SafeDoubleCheckSingleton.class){ if (singleton == null){ //隐患：多线程环境下，由于重排序，该对象可能还未完成初始化就被其他线程读取 //原理:利用volatile，禁止 "初始化对象"(2) 和 "设置singleton指向内存空间"(3) 的重排序 singleton = new SafeDoubleCheckSingleton(); } } } //2.对象创建完毕，执行getInstance()将不需要获取锁，直接返回创建对象 return singleton; } } 原因：(1). DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排原因在于某一个线程在执行到第一次检测,读取到的instance不为null时,instance的引用对象 可能没有完成初始化instance=new SingletonDem(); 可以分为以下步骤(伪代码)memory=allocate();//1.分配对象内存空间instance(memory);//2.初始化对象instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null(2). 步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.memory=allocate();//1.分配对象内存空间instance=memory;//3.设置instance的指向刚分配的内存地址,此时instance!=null 但对象还没有初始化完.instance(memory);//2.初始化对象(3). 但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性所以当一条线程访问instance不为null时,由于instance实例未必完成初始化,也就造成了线程安全问题(4). 我们使用volatile禁止instance变量被执行指令重排优化即可private volatile static SafeDoubleCheckSingleton singleton;采用静态内部类的方式实现public class SingletonDemo { private SingletonDemo() { } private static class SingletonDemoHandler { private static SingletonDemo instance = new SingletonDemo(); } public static SingletonDemo getInstance() { return SingletonDemoHandler.instance; } public static void main(String[] args) { for (int i = 0; i <10 ; i++) { new Thread(()->{ SingletonDemo instance = getInstance(); // 可以知道这里获取到的地址都是同一个 System.out.println(instance); },String.valueOf(i)).start(); } } }
乐观锁和悲观锁1、 悲观锁(synchronized关键字和Lock的实现类都是悲观锁)什么是悲观锁？认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改适合写操作多的场景,先加锁可以保证写操作时数据正确(写操作包括增删改)、显式的锁定之后再操作同步资源synchronized关键字和Lock的实现类都是悲观锁2、 乐观锁概念:乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作乐观锁在Java中通过使用无锁编程来实现，最常采用的时CAS算法,Java原子类中的递增操作就通过CAS自旋实现的适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅度提升乐观锁一般有两种实现方式(采用版本号机制、CAS算法实现) //悲观锁的调用方式 public synchronized void m1(){ //加锁后的业务逻辑 } //保证多个线程使用的是同一个lock对象的前提下 ReetrantLock lock=new ReentrantLock(); public void m2(){ lock.lock(); try{ //操作同步资源 }finally{ lock.unlock(); } } //乐观锁的调用方式 //保证多个线程使用的是同一个AtomicInteger private AtomicInteger atomicIntege=new AtomicInteger(); atomicIntege.incrementAndGet();对象锁/类锁【强制】高并发时，同步调用应该去考量锁的性能损耗。能用无锁数据结构，就不要用锁;能锁区块，就不要锁整个方法体﹔能用对象锁，就不要用类锁。说明︰尽可能使加锁的代码块工作量尽可能的小，避免在锁代码块中调用RPC方法。作用于实例方法，当前实例加锁，进入同步代码前要获得当前实例的锁;作用于代码块，对括号里配置的对象加锁。作用于静态方法，当前类加锁，进去同步代码前要获得当前类对象的锁;//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } }*一个对象里面如果有多个synchronized方法，某一个时刻内，只要一个线程去调归其中的一个synchronized方法了，*其它的线程都只能等待，换句话说，某一个时刻内，只能有唯一的一个线程去访问这些synchronized方法*锁的是当前对象this，被锁定后，其它的线程都不能进入到当前对象的其它的synchronized方法//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----hello -----sendEmail//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail*加个普通方法后发现和同步锁无关*换成两个对象后，不是同一把锁了，情况立刻变化。//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public static synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendEmail -----sendSMS//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public static synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendEmail -----sendSMS**都换成静态同步方法后，情况又变化三种synchronized 锁的内容有一些差别:*对于普通同步方法，锁的是当前实例对象，通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁—>实例对象本身，*对于静态同步方法，锁的是当前类的Class对象，如Phone.class唯一的一个模板。 Phone phone = new Phone(); Phone phone2 = new Phone(); //对于静态同步方法 看的是当前的类 比竟：static关键字修饰的方法，又叫类方法.属于类的，不属于对象， 在实例化对象之前就可以通过类名.方法名调用静态方法。 public static synchronized void sendEmail(){}; //static synchronized 是属于类锁 public synchronized void sendsMs(){}; //synchronize 是属于对象锁 //静态方法static（类方法）和 非静态方法（实例方法）的区别： //方法我们主要分为三种： //1.构造方法 //2.非静态方法（普通方法/实例方法） //3.静态方法（类方法） //一、静态方法和非静态方法的区别（调用对象、引用变量不同） //静态方法:是使用static关键字修饰的方法，又叫类方法.属于类的，不属于对象， 在实例化对象之前就可以通过类名.方法名调用静态方法。 （静态属性，静态方法都是属于类的，可以直接通过类名调用）。 //A.在静态方法中，可以调用静态方法。 //B.在静态方法中，不能调用非静态方法。 //C.在静态方法中，可以引用类变量（即，static修饰的变量）。 //D.在静态方法中，不能引用成员变量（即，没有static修饰的变量）。 //E.在静态方法中，不能使用super和this关键字 //非静态方法:是不含有static关键字修饰的普通方法，又称为实例方法，成员方法。属于对象的，不属于类的。（成员属性，成员方法是属于对象的，必须通过new关键字创建对象后，再通过对象调用）。 //A.在普通方法中，可以调用普通方法。 //B.在普通方法中，可以调用静态方法 //C.在普通方法中，可以引用类变量和成员变量 //D.在普通方法中，可以使用super和this关键字 //二、静态方法和非静态方法的区别（调用方法不同） //静态方法可以直接调用，类名调用和对象调用。（类名.方法名 / 对象名.方法名） //但是非静态方法只能通过对象调用。（对象名.方法名） //三、静态方法和非静态方法的区别（生命周期不同） //静态方法的生命周期跟相应的类一样长，静态方法和静态变量会随着类的定义而被分配和装载入内存中。一直到线程结束，静态属性和方法才会被销毁。（也就是静态方法属于类） //非静态方法的生命周期和类的实例化对象一样长，只有当类实例化了一个对象，非静态方法才会被创建，而当这个对象被销毁时，非静态方法也马上被销毁。（也就是非静态方法属于对象） //总结：类方法可以直接通过类名调用，实例方法必需先实例化类，再初始化对象，然后通过类的实例对象才能调用*对于同步方法块，锁的是synchronized 括号内的对象//入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { phone.sendSMS( ); // phone.hello(); // phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail //入口 public static void main(string[] args){ Phone phone = new Phone(); Phone phone2 = new Phone(); new Thread(() -> { phone. sendEmail(); }, name: "a" ).start(); //暂停毫秒,保证a线程先启动 try { TimeUnit.ILLISECONDs.sleep( timeout 200); } catch (InterruptedException e) { e.printstackTrace(); } new Thread(() -> { // phone.sendSMS( ); // phone.hello(); phone2.sendSMS(); }, name: "b" ).start(); } class Phone{ public static synchronized void sendEmail(){ try { TimeUnit.SECONDs.sleep( timeout: 3); } catch (InterruptedException e) { e.printStackTrace(); } system.out.println( "-----sendEmail" ); } public synchronized void sendsMs(){ system.out.println( "-----sendSMS" ); } public void hello(){ system.out.println( "-----hello" ); } } -----sendSMS -----sendEmail**当一个线程试图访问同步代码时它首先必须得到锁，正常退出或抛出异常时必须释放锁。*所有的普通同步方法用的都是同一把锁—实例对象本身，就是new出来的具体实例对象本身,本类this*也就是说如果一个实例对象的普通同步方法获取锁后，该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。*所有的静态同步方法用的也是同一把锁—类对象本身，就是我们说过的唯一模板class*具体实例对象this和唯一模板class，这两把锁是两个不同的对象，所以静态同步方法与普通同步方法之间是不会有竞态条件的*但是一旦一个静态同步方获取锁后，其他的静态同步方法都必须等待该方法释放锁后才能获取锁。公平锁和非公平锁公平锁：是指多个线程按照申请锁的顺序来获取锁类似排队打饭先来后到非公平锁：是指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象注意：synchronized 和 ReentrantLock 默认是非公平锁为什么会有公平锁和非公平锁1恢复挂起的线程到真正锁的获取还是有时间差的，从开发人员来看这个时间微乎其微，但是从CPU的角度来看，这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片，尽量减少CPU空闲状态时间。2使用多线程很重要的考量点是线程切换的开销，当采用非公平锁时，当1个线程请求锁获取同步状态，然后释放同步状态，所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大，所以就减少了线程的开销。可重入锁又名递归锁是指在同一个线程在外层方法获取锁的时候，再进入该线程的内层方法会自动获取锁(前提，锁对象得是同一个对象)，不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁，可重入锁的一个优点是可一定程度避免死锁。显示锁/隐式锁隐式锁（即synchronized关键字使用的锁）默认是可重入锁显式锁（即Lock）也有ReentrantLock这样的可重入锁。new Thread(-> { lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ---come in内层调用"); }finally { lock.unLock( ); } }finally { lock. unlock(); } },name: "t1").start(); //加锁和释放锁次数一致，程序运行正常！ new Thread(-> { lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); lock.lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ---come in内层调用"); }finally { lock.unLock( ); } }finally { //由于加锁次数和释放次数不一样，第二个线程始终无法获取到锁，导致一直在等待。 //lock. unlock(); } },name: "t1").start(); new Thread(() -> { lock. lock(); try{ system.out.println(Thread.currentThread().getName()+"\t ----come in外层调用"); }finally { lock.unlock(); } }, name: "t2" ).start(); //加锁和释放锁次数不一致，线程t2不会执行，程序一直等待！死锁死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去，如果系统资源充足，进程的资源请求都能够得到满足，死锁出现的可能性就很低，否则就会因争夺有限的资源而陷入死锁。线程死锁描述的是这样一种情况：多个线程同时被阻塞，它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞，因此程序不可能正常终止。如下图所示，线程 A 持有资源 2，线程 B 持有资源 1，他们同时都想申请对方的资源，所以这两个线程就会互相等待而进入死锁状态。public class DeadLockDemo { private static Object resource1 = new Object();//资源 1 private static Object resource2 = new Object();//资源 2 public static void main(String[] args) { new Thread(() -> { synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource2"); synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); } } }, "线程 1").start(); new Thread(() -> { synchronized (resource2) { System.out.println(Thread.currentThread() + "get resource2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "waiting get resource1"); synchronized (resource1) { System.out.println(Thread.currentThread() + "get resource1"); } } }, "线程 2").start(); } }小结指针指向monitor对象（也称为管程或监视器锁）的起始地址。每个对象都存在着一个monitor与之关联，当一个 monior被某个线程持有后，它便处于锁定状态。在Java虚拟机(HotSpot)中，monitor是由ObjectMonitor实现的，其主要数据结构如下〈位于HotSpot虚拟机源码 ObjectMonitor.hpp文件，C++实现的)
MybatisPlus 自动驼峰命名在使用 'mybatis-plus '时,驼峰命名映射是自动开启的，即：/* 实体类中的字段 userName 自动映射对应数据库中的 user_name, 如果数据库表中字段不是 user_name 字段，而是 userName 此时Java程序就会报错 */ private String userName;此时就需要关闭自动驼峰命名映射：spring: <settings> <setting name="mapUnderscoreToCamelCase" value="false"/> </settings>springboot:#在springboot的yml配置文件中配置 mybatis-plus: configuration: map-underscore-to-camel-case: false自动生成器的使用MyBatisX插件 自动根据数据生成domian 实体对象 Mapper对象（操作数据库的对象）、Mapper.xml（定义了Mapper对象和数据库的关联，可以在里面写自己的SQL）、Service（常见的增删改查）、Service的实现类
MybatisPlus快速入门使用第三方组件：1、导入对应的依赖2、研究依赖是如何配置3、代码的编写4、提高扩展技术能力！步骤创建数据库mybatis_plus创建User表DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com'); 真是开发中，version（乐观锁）、deleted（逻辑删除）、gmt_create、gmt_modified编写项目。初始化项目！使用SpringBoot初始化导入依赖 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.5</version> </dependency>使用MybatisPlus可以节省大量代码，尽量不要同时导入mybatis和mybatisplus 版本差异！、连接数据库 这一步和Mybatis相同！#mysql 5驱动不同 com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=juzi spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?userSSL=false&userUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #mysql 8 驱动不同com.mysql.cj.jdbc.Driver 需要增加时区配置 serverTimezone=GMT%2B8pojo -dao(之前需要 连接mybatis 配置mapper.xml)-service-controller使用了Mybatis-Plus 之后pojo/** * @author: ZzRG * @version: 1.0 * Date: 2022/6/27 */ @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }mapper接口/** * @author: ZzRG * @version: 1.0 * Date: 2022/6/27 */ //在对应的Mapper上面继承基本的接口 BaseMapper public interface UserMapper extends BaseMapper<User> { // 所有的curd 操作编写完成了 // 不需要像以前那样写一大堆文件 }注意点：我们需要在主启动器上去扫描 我们的Mapper包下的所有接口 @MapperScan("com.zzrg.xuexi.mapper")测试类中测试@SpringBootTest class MybatisPlusApplicationTests { //继承了BaserMapper的所有方法都来自父类， 我们也可以写自己的方法 @Autowired private UserMapper userMapper; @Test void contextLoads() { //Wrapper<T>是一个参数 条件构造器 我们可以先不用设置为null //查询所有用户 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }使用这里的关键所在1、SQL谁帮我们写的? MyBatis-Plus都写好了2、方法哪里来的? MyBatis-Plus 都写好了配置日志我们所有的sq|现在是不可见的,我们希望知道它是怎么执行的,所以我们必须要看日志!#配置日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl配置完日志之后，在学习时候就需要注意这个自动生成的SQLCRUD扩展自动插入insert插入 @Test public void testInsert(){ User user = new User(); user.setName("ZzRG"); user.setAge(18); user.setEmail("123@qq.com"); int insert = userMapper.insert(user);//帮我们自动生成id System.out.println(insert);//受影响的行数 System.out.println(user);//发现id自动回填 }数据库插入的id的默认值：全局的唯一id主键生成策略默认 ID_WORKER全局唯一id分布式系统的唯一id生成：雪花算发：snowflake是Twitter开源的分布式ID生成算法，结果是一个long型的ID。其核心思想是︰使用41bit作为毫秒数，10bit作为机器的ID (5个bit是数据中心，5个bit的机器ID)，12bit作为毫秒内的流水号（意味着每个节点在每毫秒可以产生4096个ID），最后还有一个符号位，永远是0。主键自增：@TableId(type = IdType.AUTO)我们需要配置主键自增数据库字段一定要自增！与其他源码解释public enum IdType{ AUTO(0),//数据库id自增 NONE(1),//未设置主键 INPUT(2),//手动输入 ID_WORKER(3),//默认的全局唯一id UUID(4),//全局唯一id uuid ID_WORKER_STR(5);// ID_WORKER 字符串表示法 }更新操作 @Test public void testUpdate(){ User user = new User(); user.setId(5L); //自动拼接动态sql user.setAge(15) user.setEmail("123456@qq.com"); int update = userMapper.updateById(user); System.out.println(update); System.out.println(user); } }注意：所有的sql都是自动动态配置！自动填充创建时间、修改时间!这些个操作一遍都是自动化完成的，我们不希望手动更新!阿里巴巴开发手册：所有的数据库表：gmt_create、gmt_modified 几乎所有的表都套配置上！且需要自动化！方法一:数据库级别（工作中不允许使用）1、在表中新增字段create_time,update_time2、再次测试插入方法,我们需要先把实体类同步 private Date createTime; private Date updateTime;方式二:代码级别 //字段填充内容 @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; 编写处理器package com.zzrg.xuexi.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import javax.xml.crypto.Data; import java.util.Date; /** * @author: ZzRG * @version: 1.0 * Date: 2022/6/27 */ @Slf4j @Component public class MyMetaObjectHandler implements MetaObjectHandler { //插入时的填充策略 @Override public void insertFill(MetaObject metaObject) { log.info("开始插入..."); //setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) this.setFieldValByName("createTime", new Date(),metaObject); this.setFieldValByName("updateTime", new Date(),metaObject); } //更新时的填充策略 @Override public void updateFill(MetaObject metaObject) { log.info("开始更新..."); this.setFieldValByName("updateTime", new Date(),metaObject); } }插入测试乐观锁乐观锁：故名思意十分乐观，它总是认为不会出现问题，无论干什么不去上锁!如果出现了问题，再次更新值测试悲观锁：故名思意十分悲观,它总是认为总是出现问题，无论干什么都会上锁!再去操作!我们这里主要讲解乐观锁机制!乐观锁实现方式:取出记录时，获取当前version·更新时，带上这个version执行更新时，set version = newVersion where version = oldVersion如果version不对，就更新失败乐观锁：1、查询，获取版本 version = 1 ---线程A update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1 ---线程B 抢先完成 这时候version = 2 线程A执行到的时候version不是1 然后会导致修改失败 update user set name = "kuangshen", version = version + 1 where id = 2 and version = 1测试一下Mp的乐观锁插件增加字段2、实体类上添加相应字段 @Version//乐观锁的注解 private Integer version;3、注册插件旧方法：/** * @author: ZzRG * @version: 1.0 * Date: 2022/6/27 */ //扫描mapper文件夹 @MapperScan("com.zzrg.xuexi.mapper") @EnableTransactionManagement @Configuration//配置类 public class MybatisPlusConfig { //注册乐观锁插件 (老版) public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } } 新版/** * @author: ZzRG * @version: 1.0 * Date: 2022/6/27 */ //扫描mapper文件夹 @MapperScan("com.zzrg.xuexi.mapper") @Configuration//配置类 public class MybatisPlusConfig { //注册乐观锁插件 /** * 新版 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mybatisPlusInterceptor; } } @Test public void testOptimisticLocker(){ //查询用户信息 User user = userMapper.selectById(1L); //修改信息 user.setName("橘子"); user.setEmail("110110@123.com"); //执行更新 userMapper.updateById(user); } @Test public void testOptimisticLocker2(){ //线程1 //查询用户信息 User user = userMapper.selectById(1L); user.setName("橘子"); user.setEmail("110110@123.com"); //线程2进行插队操作 User user2 = userMapper.selectById(1L); user2.setName("橘子21"); user2.setEmail("110110@123.com"); userMapper.updateById(user2); //自旋锁多次尝试提交！ userMapper.updateById(user);//没有乐观锁就会覆盖插队线程2的值 }查询操作 //批量查询id @Test public void testSelectByBatchId(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); users.forEach(System.out::println); } //条件查询Map @Test public void testSelectByBatchIds(){ HashMap<String, Object> map = new HashMap<>(); //自定义查询可以当多个条件 map.put("name","ZzRG"); map.put("id",1); map.put("age",3); List<User> users = userMapper.selectByMap(map); users.forEach(System.out::println); } 分页查询分页在网站上的使用1、原始limit进行分页2、pageHler分页插件3、MybatisPlus内置分页1、配置拦截器组件即可@Configuration @MapperScan("scan.your.mapper.package") public class MybatisPlusConfig { /** * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除) */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2)); return interceptor; } //旧的 @Bean public ConfigurationCustomizer configurationCustomizer() { return configuration -> configuration.setUseDeprecatedExecutor(false); } }2、直接使用Page对象即可@Test public void selectPage(){ //分页参数 Page<User> pageParams = new Page<>(1,5); //执行分页 userMapper.selectPage(pageParams,null); //查看分页参数成员 System.out.println(pageParams); } 删除操作 //测试删除 @Test public void testDeleteById(){ userMapper.deleteById(1L); } //批量删除 @Test public void testDeleteBachId(){ userMapper.deleteBatchIds(Arrays.asList(1L,2L,3L)); } //通过map删除 @Test public void testDeleteByMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("id",1L); map.put("name","橘子"); userMapper.deleteByMap (map); }在工作会遇到逻辑删除的问题！逻辑删除物理删除：从数据库中直接移除逻辑删除：再数据库中没有被移除，而是通过一个变量来让他失效! deleted =0 => deleted = 1管理员可以查看被删除的记录!防止数据的丢失，类似于回收站!1、在数据表中增加字段 ：deleted2、实体类增加 属性 @TableLogic//逻辑删除 private Integer deleted;3、配置 //逻辑删除的配置 @Bean public ISqlInjector sqlInjector(){ return new LogicSqlInjector(); }#配置逻辑删除 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0记录依旧在数据，但是值确实已经变化！执行查询语句！后面会自动拼接。性能分析插件我们在平时的开发中，会遇到一些慢sql。测试! druid...作用∶性能分析拦截器，用于输出每条SQL语句及其执行时间MP也提供性能分析插件，如果超过这个时间就停止运行!1、导入插件 /** * SQL执行效率 * @return */ @Bean @Profile({"dev","test"})//设置dev test 环境开启，保证效率 public PerformanceInterceptor performanceInterceptor(){ PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100);//ms 设置sql执行最大时间 如果超过则不执行 performanceInterceptor.setFormat(true);//是否格式化 return performanceInterceptor; }配置环境#设置开发环境 spring.profiles.active=dev2、测试使用 只要超出设定最大时间就会抛出异常j！条件构造器核心部分！！！写一些复杂SQL的就可以用它代替！