• [技术干货] cacheable怎么获取缓存值_SpringBoot2.x操作缓存的新姿势
    一、介绍spring cache 是spring3版本之后引入的一项技术,可以简化对于缓存层的操作,spring cache与springcloud stream类似,都是基于抽象层,可以任意切换其实现。其核心是CacheManager、Cache这两个接口,所有由spring整合的cache都要实现这两个接口、Redis的实现类则是 RedisCache 和 RedisManager。二、使用1、查询需要导入的依赖<dependency>   <groupId>org.springframework.bootgroupId>   <artifactId>spring-boot-starter-cacheartifactId>dependency><dependency>   <groupId>org.springframework.bootgroupId>   <artifactId>spring-boot-starter-data-redisartifactId>dependency>编写对于cache的配置@EnableCaching@SpringBootConfigurationpublic class CacheConfig {   @Autowired   private RedisConnectionFactory connectionFactory;   @Bean// 如果有多个CacheManager的话需要使用@Primary直接指定那个是默认的   public RedisCacheManager cacheManager() {       RedisSerializer<String> redisSerializer = new StringRedisSerializer();       Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);       ObjectMapper om = new ObjectMapper();       // 防止在序列化的过程中丢失对象的属性       om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);       // 开启实体类和json的类型转换       om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);       jackson2JsonRedisSerializer.setObjectMapper(om);       // 配置序列化(解决乱码的问题)       RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()  .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))             .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))               // 不缓存空值               .disableCachingNullValues()               // 1分钟过期               .entryTtl(Duration.ofMinutes(1)) ;       RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)               .cacheDefaults(config)               .build();       return cacheManager;   }}进行以上配置即可使用springboot cache了,还有一个key的生成策略的配置(可选)@Beanpublic KeyGenerator keyGenerator() {   return (target, method, params) -> {       StringBuffer key = new StringBuffer();       key.append(target.getClass().getSimpleName() + "#" + method.getName() + "(");       for (Object args : params) {           key.append(args + ",");       }       key.deleteCharAt(key.length() - 1);       key.append(")");       return key.toString();   };}注意:如果配置了KeyGenerator ,在进行缓存的时候如果不指定key的话,最后会把生成的key缓存起来,如果同时配置了KeyGenerator 和key则优先使用key。推荐:深入SpringBoot核心注解原理在controller或者service的类上面添加 @CacheConfig ,注解里面的参数详情见下表:在标有@CacheConfig的类里面编写一个查询单个对象的方法并添加 @Cacheable注解@Cacheable(key = "#id", unless = "#result == null") @PatchMapping("/course/{id}")public Course courseInfo(@PathVariable Integer id) {    log.info("进来了 .. ");    return courseService.getCourseInfo(id);}执行完该方法后,执行结果将会被缓存到Redis:@Cacheable注解中参数详情见下表:2、修改编写一个修改的方法,参数传对象,返回值也改成这个对象@PutMapping("/course")public Course modifyCoruse(@RequestBody Course course) {    courseService.updateCourse(course);    return course;}在方法上面添加 @CachePut(key = "#course.id") 注解,这个注解表示将方法的返回值更新到缓存中,注解中的参数和 @Cacheable 中的一样,这里就略过了。扩展:SpringBoot内容聚合3、删除编写删除方法,在方法上添加@CacheEvict 注解@CacheEvict(key = "#id")@DeleteMapping("/course/{id}")public void removeCourse(@PathVariable Integer id) {   courseService.remove(id);}@CacheEvict 的参数信息见下表:三、 基于代码的Cache的使用因为我们有配置的CacheManager,所以可以利用RedisCacheManager对象去手动操作cache,首先将CacheManager注入进来:@Resource private CacheManager cacheManager;@PatchMapping("/course2/{id}")public Course course2(@PathVariable Integer id) {   // 获取指定命名空间的cache   Cache cache = cacheManager.getCache("course");   // 通过key获取对应的value   Cache.ValueWrapper wrapper = cache.get(2);   if (wrapper == null) {       // 查询数据库       Course course = courseService.getCourseInfo(id);       // 加入缓存       cache.put(course.getId(), course);       return course;   } else {       // 将缓存的结果返回       // 因为配置了enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);       // 所以在进行强转的时候不会报错       return (Course) wrapper.get();    }}如果还看不明白,请去码云拉取源码 https://gitee.com/tianmaoliln/Spring-Boot-Cache.gitEND————————————————原文链接:https://blog.csdn.net/weixin_34476847/article/details/113050518
  • [技术干货] SpringBoot 缓存之 @Cacheable 详细介绍
    一、简介1、缓存介绍Spring 从 3.1 开始就引入了对 Cache 的支持。定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术。并支持使用 JCache(JSR-107)注解简化我们的开发。其使用方法和原理都类似于 Spring 对事务管理的支持。Spring Cache 是作用在方法上的,其核心思想是,当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存在缓存中。2、Cache 和 CacheManager 接口说明Cache 接口包含缓存的各种操作集合,你操作缓存就是通过这个接口来操作的。Cache 接口下 Spring 提供了各种 xxxCache 的实现,比如:RedisCache、EhCache、ConcurrentMapCacheCacheManager 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。小结:每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。二、@Cacheable 注解使用详细介绍1、缓存使用步骤@Cacheable 这个注解,用它就是为了使用缓存的。所以我们可以先说一下缓存的使用步骤:1、开启基于注解的缓存,使用 @EnableCaching 标识在 SpringBoot 的主启动类上。2、标注缓存注解即可① 第一步:开启基于注解的缓存,使用 @EnableCaching 标注在 springboot 主启动类上② 第二步:标注缓存注解注:这里使用 @Cacheable 注解就可以将运行结果缓存,以后查询相同的数据,直接从缓存中取,不需要调用方法。2、常用属性说明下面介绍一下 @Cacheable 这个注解常用的几个属性:cacheNames/value :用来指定缓存组件的名字key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。condition :可以用来指定符合条件的情况下才缓存unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)sync :是否使用异步模式。① cacheNames用来指定缓存组件的名字,将方法的返回结果放在哪个缓存中,可以是数组的方式,支持指定多个缓存。② key缓存数据时使用的 key。默认使用的是方法参数的值。可以使用 spEL 表达式去编写。③ keyGeneratorkey 的生成器,可以自己指定 key 的生成器,通过这个生成器来生成 key。这样放入缓存中的 key 的生成规则就按照你自定义的 keyGenerator 来生成。不过需要注意的是:@Cacheable 的属性,key 和 keyGenerator 使用的时候,一般二选一。④ condition符合条件的情况下才缓存。方法返回的数据要不要缓存,可以做一个动态判断。⑤ unless否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。⑥ sync是否使用异步模式。默认是方法执行完,以同步的方式将方法返回的结果存在缓存中。3、spEL 编写 key前面说过,缓存的 key 支持使用 spEL 表达式去编写,下面总结一下使用 spEL 去编写 key 可以用的一些元数据。
  • [技术干货] SpringBoot中RestClient端的详解(RestTemplate)
    Spring Boot 提供了 RestTemplate 来辅助发起一个 REST 请求,默认通过 JDK 自带的 HttpURLConnection 来作为底层 HTTP 消息的发送方式,使用 JackSon 来序列化服务器返回的 JSON 数据。RestTemplate 是核心类, 提供了所有访问 REST 服务的接口,尽管实际上可以使用 HTTP Client 类或者 java.net.URL来完成,但 RestTemplate 提供了阻STful 风格的 API。 Spring Boot 提供了 RestTemplateBuilder 来创建一个 RestTemplate。 RestTemplate定义11个基本操作方法,大致如下:delete(): 在特定的URL上对资源执行HTTP DELETE操作exchange(): 在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity,这个对象是从响应体中映射得到的     3.execute(): 在URL上执行特定的HTTP方法,返回一个从响应体映射得到的对象(所有的get、post、delete、put、options、head、exchange方法最终调用的都是excute方法),例如:@Override public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException { RequestCallback requestCallback = acceptHeaderRequestCallback(responseType); HttpMessageConverterExtractor<T> responseExtractor = <span style="white-space:pre"> </span>new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger); return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables); }    4.getForEntity(): 发送一个HTTP GET请求,返回的ResponseEntity包含了响应体所映射成的对象    5.getForObject() :发送一个HTTP GET请求,返回的请求体将映射为一个对象    6.postForEntity() :POST 数据到一个URL,返回包含一个对象的ResponseEntity,这个对象是从响应体中映射得到的   7.postForObject(): POST 数据到一个URL,返回根据响应体匹配形成的对象   8.headForHeaders(): 发送HTTP HEAD请求,返回包含特定资源URL的HTTP头   9.optionsForAllow(): 发送HTTP OPTIONS请求,返回对特定URL的Allow头信息  10.postForLocation() :POST 数据到一个URL,返回新创建资源的URL  11.put(): PUT 资源到特定的URL实际上,由于Post 操作的非幂等性,它几乎可以代替其他的CRUD操作.一、GET请求在RestTemplate中,发送一个GET请求,我们可以通过如下两种方式:第一种:getForEntitygetForEntity方法的返回值是一个ResponseEntity<T>,ResponseEntity<T>是Spring对HTTP请求响应的封装,包括了几个重要的元素,如响应码、contentType、contentLength、响应消息体等。其重载方法如下:<T> ResponseEntity<T> getForObject(URI url, Class<T> responseType) throws RestClientException;<T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> ResponseEntity<T> getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;比如下面一个例子:@Autowired RestTemplateBuilder restTemplateBuilder;  @RequestMapping("/gethello") public String getHello() { RestTemplate client = restTemplateBuilder.build (); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/hello", String.class); String body = responseEntity.getBody(); HttpStatus statusCode = responseEntity.getStatusCode(); int statusCodeValue = responseEntity.getStatusCodeValue(); HttpHeaders headers = responseEntity.getHeaders(); StringBuffer result = new StringBuffer(); result.append("responseEntity.getBody():").append(body).append("<hr>") .append("responseEntity.getStatusCode():").append(statusCode).append("<hr>") .append("responseEntity.getStatusCodeValue():").append(statusCodeValue).append("<hr>") .append("responseEntity.getHeaders():").append(headers).append("<hr>"); return result.toString(); }有时候我在调用服务提供者提供的接口时,可能需要传递参数,有两种不同的方式,如下:@RequestMapping("/sayhello") public String sayHello() { ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={1}", String.class, "张三"); return responseEntity.getBody(); }  @RequestMapping("/sayhello2") public String sayHello2() { Map<String, String> map = new HashMap<>(); map.put("name", "李四"); ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://HELLO-SERVICE/sayhello?name={name}", String.class, map); return responseEntity.getBody(); }可以用一个数字做占位符,最后是一个可变长度的参数,来一一替换前面的占位符也可以前面使用name={name}这种形式,最后一个参数是一个map,map的key即为前边占位符的名字,map的value为参数值第一个调用地址也可以是一个URI而不是字符串,这个时候我们构建一个URI即可,参数神马的都包含在URI中了,如下:@RequestMapping("/sayhello3") public String sayHello3() { UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://HELLO-SERVICE/sayhello?name={name}").build().expand("王五").encode(); URI uri = uriComponents.toUri(); ResponseEntity<String> responseEntity = restTemplate.getForEntity(uri, String.class);  return responseEntity.getBody(); }通过Spring中提供的UriComponents来构建Uri即可。当然,服务提供者不仅可以返回String,也可以返回一个自定义类型的对象,比如我的服务提供者中有如下方法:@RequestMapping(value = "/getbook1", method = RequestMethod.GET)public Book book1() { return new Book("三国演义", 90, "罗贯中", "花城出版社"); }对于该方法我可以在服务消费者中通过如下方式来调用:@RequestMapping("/book1") public Book book1() {     ResponseEntity<Book> responseEntity = restTemplate.getForEntity("http://HELLO-    SERVICE/getbook1", Book.class);     return responseEntity.getBody(); }第二种:getForObjectgetForObject函数实际上是对getForEntity函数的进一步封装,如果你只关注返回的消息体的内容,对其他信息都不关注,此时可以使用getForObject,重载方法如下:<T> T getForObject(URI url, Class<T> responseType) throws RestClientException;<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException; 二、POST请求在RestTemplate中,POST请求可以通过如下三个方法来发起:第一种:postForEntity、postForObjectPOST请求有postForObject()和postForEntity()两种方法,和GET请求的getForObject()和getForEntity()方法类似。getForLocation()是POST请求所特有的。<T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException;<T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;上面三个方法中,第一个参数都是资源要POST到的URL,第二个参数是要发送的对象,而第三个参数是预期返回的Java类型。在URL作为String类型的两个版本中,第四个参数指定了URL变量(要么是可变参数列表,要么是一个Map)。 该方法和get请求中的getForEntity方法类似,如下例子:@RequestMapping("/book3") public Book book3() { Book book = new Book(); book.setName("红楼梦"); ResponseEntity<Book> responseEntity = restTemplate.postForEntity("http://HELLO-SERVICE/getbook2", book, Book.class); return responseEntity.getBody(); } 第二种:postForLacationpostForLacation()会在POST请求的请求体中发送一个资源到服务器端,返回的不再是资源对象,而是创建资源的位置。postForLocation(String url, Object request, Object... uriVariables) throws RestClientException;postForLocation(String url, Object request, Map<String, ?> uriVariables) throws RestClientException;postForLocation(URI url, Object request) throws RestClientException; public String postSpitter(Spitter spitter) { RestTemplate rest = new RestTemplate(); return rest.postForLocation("http://localhost:8080/Spitter/spitters", spitter).toString(); }postForLocation也是提交新资源,提交成功之后,返回新资源的URI,postForLocation的参数和前面两种的参数基本一致,只不过该方法的返回值为Uri,这个只需要服务提供者返回一个Uri即可,该Uri表示新资源的位置。 三、PUT请求在RestTemplate中,对PUT请求可以通过put方法进行调用实现,比如:RestTemplate restTemplate=new RestTemplate();Longid=100011; User user=new User("didi",40); restTemplate.put("http://USER-SERVICE/user/{l}",user,id); put函数也实现了三种不同的重载方法:put(String url,Object request,Object... urlVariables)put(String url,Object request,Map urlVariables)put(URI url,Object request)put函数为void类型,所以没有返回内容,也就没有其他函数定义的responseType参数,除此之外的其他传入参数定义与用法与postForObject基本一致。 四、DELETE请求在RestTemplate中,对DELETE请求可以通过delete方法进行调用实现,比如:RestTemplate restTemplate=new RestTemplate(); Longid=10001L; restTemplate.delete("http://USER-SERVICE/user/{1)",id); delete函数也实现了三种不同的重载方法:delete(String url,Object... urlVariables)delete(String url,Map urlVariables)delete(URI url)由于我们在进行REST请求时,通常都将DELETE请求的唯一标识拼接在url中,所以DELETE请求也不需要request的body信息,就如put()方法一样,返回值类型为void。说明:第三种重载方法,url指定DELETE请求的位置,urlVariables绑定url中的参数即可。五、通用方法exchange()exchange方法可以在发送个服务器端的请求中设置头信息,其重载方法如下:<T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType) throws RestClientException;<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException;<T> ResponseEntity<T> exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException;MultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>(); headers.add("Accept", "application/json"); HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers); ResponseEntity<Spitter> response=rest.exchange("http://localhost:8080/Spitter/spitters/{spitter}", HttpMethod.GET, requestEntity, Spitter.class, spitterId); 补充:        如果期望返回的类型是一个列表,如 List,不能简单调用 xxxForObject,因为存在泛型的类型擦除, RestTemplate 在反序列化的时候并不知道实际反序列化的类型,因此可以使用 ParameterizedTypeReference 来包含泛型类型,代码如下:RestTemplate client= restTemplateBuilder.build(); //根据条件查询一纽订单 String uri = base+"/orders?offset={offset }"; Integer offset = 1; //元参数 HttpEntity body = null; ParameterizedTypeReference<List<Order> typeRef = new ParameterizedTypeReference<List<Order>(){}; ResponseEntity<List<Order> rs=client.exchange(uri, HttpMethod.GET, body, typeRef, offset); List<Order> order = rs.getBody() ; 注意到 typeRef 定义是用{}结束的,这里创建了一个 ParameterizedTypeReference 子类,依 据在类定义中的泛型信息保留的原则, typeRef保留了期望返回的泛型 List。exchange 是一个基础的 REST 调用接口,除了需要指明 HTTP Method,调用方法同其他方法类似。除了使用ParameterizedTypeReference子类对象,也可以先将返回结果映射成 json字符串,然后通过 ObjectMapper 来转为指定类型(笔记<SpringBoot中的JSON>中有介绍 ) 。————————————————原文链接:https://blog.csdn.net/fsy9595887/article/details/86420048
  • [技术干货] springboot集成IM即时通讯
    在使用 Server SDK 之前,需要准备环信 appkey、Client ID、ClientSecre。开发环境JDK1.8 &&MAVEN1. 在pom中引入sdk依赖2. 初始化EMService 根据业务资源,API 分为:Attachment  用于上传下载附件Block  用于限制访问(将用户加入黑名单、群组/聊天室禁言等)Contact  用于管理联系人(添加好友等)Group  用于管理群组Message  用于发送消息User  用于管理用户UserMetadata  用于管理用户属性Push  用于管理用户推送(设置推送免打扰等)Token  用于获取用户TokenRoom  用于管理聊天室每个业务资源对应一个方法,例如,用户相关的 API,都可以在 .user() 找到。举个例子,我们要注册一个用户,就可以这样写:API的返回值是响应式的,如果希望阻塞,可以使用上面例子中的 .block()。注意:如果你的项目不是响应式的编程,那么请在调用的 Server SDK API 的结尾添加 .block()对使用的 api 添加 try/catch ,如果使用的 api 没有抛出异常,代表请求成功,反之则请求失败,通过异常EMException对象的 getErrorCode()/getMessage() 拿到错误码以及错误描述
  • [技术干货] springboot 部分注解解析
    @ConfigurationProperties(prefix=“ XX”)告诉springboot将该注解修饰的类的所有属性和配置文件中相关的配置进行绑定,prefix属性表示的是从配置文件中为XX的下面的属性进行一一映射。该注解一般和@Component 注解一起使用。 因为是只有这个组件是容器内的组件才能用@ConfigurationProperties 这个注解如果不写@Component,提示在springboot的开启配置属性中未注册,所以需要标记为spring的Component组件或者,在配置扫描中配置,配置值得时候支持数据校验,可以给这个类上面加上@Valiadated注解,然后下面的属性上面就可以就加上类似@Email的校验。注解可以能获取yml配置文件中的复杂类型的值比如数组或者map,默认从全局配置文件中获取值@Lazy使bean懒加载,取消bean预初始化。@Primary自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否者将抛出异常。@Value也可以获取配置文件的@Value需要一个个的在类中给属性上面加这个注解 用{xx.bb}的形式单个设置值,但是value注解支持el表达式,但是value注解不能获取yml配置文件中的复杂类型的值比如数组或者map@PropertySource(value={""})加载value数组路径下的配置文件@ImportSource(localtions{}) 导入spring的配置文件,springboot里面没有spring的配置,自己编写的spring文件也不会生效,想使用的话 就需要加入这个注解标准在配置类上,locations数组可以加载多个配置文件。springboot推荐使用配置类来使用配置文件@Cofinguration指明当前类为配置类,等于spring的配置文件,在这个配置下 可以给方法上面加入@Bean注解给容器中加入组件 意思为把方法的返回值放入容器,默认的id为方法名@Profile springboot。1.多个配置文件的。比如application-dev.properties application-pro.properties application-{profile}.properties 可以在配置文件中选择激活哪一个配置文件,用来切换开发生产环境2.spring-profiles-active ={profile}如果使用的是yml格式的配置文件的话 可以使用文档块每个文档块用 --- 隔开在最上面使用 3.命令行:将项目install,然后在jar包所在磁盘地址栏cmd,运行如下命令启动项目java -jar spring-boot-02-config-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev;​ 酱紫可以直接在测试的时候,配置传入命令行参数,优先级最高,会覆盖上面的或者用idea运行项目,在中配置,效果和级别等同命令行4.虚拟机参数-Dspring.profiles.active=dev    优先级比配置文件高5.Active profiles 设置如果每个位置都有配置文件,每个配置文件都会加载,并且会配置互补。spring.config.location 可以在项目打完包之后指定运行一个配置文件,用在打包完后修改了配置.springBoot也可以从一下位置加载配置,优先级从高到低高优先级覆盖低优先级呃配置,所有的配置回互补1.多个命令行参数可以用空格隔开 --server.port=8081 --server.context-path=/abc6-9优先加载带profile的,由jar包外向jar包内寻找
  • [干货汇总] 透过实例demo带你认识gRPC
    本文分享自华为云社区《[gRPC介绍以及spring demo构架展示](https://bbs.huaweicloud.com/blogs/342879?utm_source=csdn&utm_medium=bbs-ex&utm_campaign=paas&utm_content=content)》,作者:gentle_zhou。 gRPC,即google Remote Procedure Call Protocol;在gRPC里,客户端可以直接调用不同机器上的服务应用的方法,就像本地对象一样,所以创建分布式应用和服务就变简单了。 gRPC是基于定义一个服务,指定一个可以远程调用的带有参数和返回类型的的方法。在服务端,服务实现这个接口并且运行gRPC服务处理客户端调用。在客户端,有一个stub提供和服务端相同的方法。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012226533359725.png) # 数据编码 数据编码即将请求的内存对象转化成可以传输的字节流发送给服务端,并将收到的字节流在转化成内存对象。 常见的数据编码方法有JSON,而gRPC则默认选用protobuf。 为什么选用protobuf呢?一个是因为它是谷歌自己的产品,二是它作为一种序列化资料结构的协定,在某些场景下传输的效率比JSON高。 一个.proto文件里的消息格式如下: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012247981492975.png) 而一个典型的JSON格式如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012255295334975.png) 我们可以看到在JSON里,内存方面,int字段的12345会占据5个字节,bool字段的true会占据4个字节,占据内存就会比较大,编码低效;还有一个缺点就是在JSON里,同一个接口同一个对象,只是int字段的值不同,每次却都还要传输int这个字段名。这样做的好处就是JSON的可读性很高,但同样在编码效率方面就会有所牺牲。 而Protobuf则是选用了VarInts对数字进行编码(VarInts则是动态的,征用了每个字节的最高位MSB,如果是1表示还有后序字节,如果是0表示后面就没字节了,以此来确定表示长度所需要的字节数量,解决了效率问题),同时给每个字段指定一个整数编号,传输的时候只传字段编号(解决了效率和冗余问题)。 但是只传字段编号的话,接收方如何知道各个编号对应哪个字段呢?那就需要靠提前约定了。Protobuf使用.proto文件当做密码本,记录字段和编号的对应关系。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012264489474276.png) Protobuf 提供了一系列工具,为 proto 描述的 message 生成各种语言的代码。传输效率上去了,工具链也更加复杂了。 # 请求映射 IDL,Interactive Data Language的缩写,交互式数据语言。 因为我们有.proto文件作为IDL,Protobuf就可以做到RPC描述。比如在.proto文件里定义一个Greeter服务,其中有一个 SayHello 的方法,接受 HelloRequest 消息并返回 HelloReply 消息。如何实现这个 Greeter 则是语言无关的,所以叫 IDL。gRPC 就是用了 Protobuf 的 service 来描述 RPC 接口的。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012289102692991.png) gRPC 在底层使用的是 HTTP/2 协议。这个 HTTP 请求用的是 POST 方法,对应的资源路径则是根据 .proto 定义确定的。我们前面提到的 Greeter 服务对应的路径是/demo.hello.Greeter/SayHello 。 一个 gRPC 定义包含三个部分,包名、服务名和接口名,连接规则如下 **/${包名}. ${服务名}/ ${接口名}** SayHello的包名是demo.hello,服务名是Greeter,接口名是SayHello,所以对应的路径就是 /demo.hello.Greeter/SayHello。 gRPC 支持三种流式接口,定义的办法就是在参数前加上 stream 关键字,分别是:请求流、响应流和双向流。 第一种叫请求流,可以在 RPC 发起之后不断发送新的请求消息。此类接口最典型的使用场景是发推送或者短信。 第二种叫响应流,可以在 RPC 发起之后不断接收新的响应消息。此类接口最典型的使用场景是订阅消息通知。 最后一种是双向流。可以在 RPC 发起之后同时收发消息。此类接口最典型的使用场景是实时语音转字幕。 如下就是普通接口和三种流式接口的结构样式: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012310100318117.png) 最简单的gRPC(非流式调用,unary)请求内容和相应内容如下所示: ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012318083189876.png) 如果单看非流式调用,也就是 unary call,gRPC 并不复杂,跟普通的 HTTP 请求也没有太大区别。我们甚至可以使用 HTTP/1.1 来承载 gRPC 流量。但是gRPC 支持流式接口,这就有点难办了。 我们知道,HTTP/1.1 也是支持复用 TCP 连接的。但这种复用有一个明显的缺陷,所有请求必须排队。也就是说一定要按照请求、等待、响应、请求、等待、响应这样的顺序进行。先到先服务。而在实际的业务场景中肯定会有一些请求响应时间很长,客户端在收到响应之前会一直霸占着TCP连接。在这段时间里别的请求要么等待,要么发起新的 TCP 连接。在效率上确实有优化的余地。一言以蔽之,HTTP/1.1 不能充分地复用 TCP 连接。 后来,HTTP/2 横空出世!通过引入 stream 的概念,解决了 TCP 连接复用的问题。你可以把 HTTP/2 的 stream 简单理解为逻辑上的 TCP 连接,可以在一条 TCP 连接上并行收发 HTTP 消息,而无需像 HTTP/1.1 那样等待。 所以 gRPC 为了实现流式接品,选择使用 HTTP/2 进行通信。所以,前文的 Greeter 调用的实际通信内容长这个样子。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/20/1653012328545131463.png) HTTP/2 的 header 和 data 使用独立的 frame(帧,简单来说也是一种 Length-Prefixed 消息,是 HTTP/2 通信的基本单位) 发送,可以多次发送。 # springboot里的grpc demo 整个项目可以分成三个project: 1. grpc-springboot-demo-api:proto文件(syntax=“proto3”; 定义服务,定义请求体,定义回应内容)写好之后用maven-install来编译生成所需的类; 2. grpc-springboot-demo-server:pom文件(springboot的启动依赖,grpc的依赖, api项目的依赖),springboot的启动类,GRPC服务器的启动类,提供服务的业务逻辑实现类 3. grpc-springboot-demo-consumer:pom文件(springboot的启动依赖,grpc的依赖, api项目的依赖),springboot启动类(与服务端启动类无差异),gRPC 客户端(主要作用是监听 gRPC 服务端,开启通道)。 对应MVC关系就是: - grpc-springboot-demo-api就是service(接口,为提供实现); - grpc-springboot-demo-server就相当于serviceImpl(service的实现类); - grpc-springboot-demo-consumer就是controller的角色。 具体代码可以看:[grpc-java springboot 同步异步调用 demo_卤小蛋学编程的博客-CSDN博客_grpc java 异步](https://blog.csdn.net/Applying/article/details/115024675) 拓展 repeated限定修饰符 repeated代表可重复,我们可以理解为数组。 比如下面的代码: ``` syntax = "proto3";//指定版本信息,不指定会报错 message Person //message为关键字,作用为定义一种消息类型 { string name = 1; //姓名 int32 id = 2; //id string email = 3; //邮件 } message AddressBook { repeated Person people = 1; } ``` 编译器就会把Person认定为数组,而我们在使用Person,用add往里面添加信息,代码如下: AddressBook addBopookReq = AddressBook.newBuilder().addName("Lily").build(); 就不需要指定index了,直接往数组里添加了一个新的addressbook,它的名字属性则是Lily。 # 参考资料 1. https://developers.google.com/protocol-buffers/docs/overview 2. https://taoshu.in/grpc.html 3. https://grpc.io/ 4. https://grpc.io/docs/languages/java/quickstart/ 5. [protobuf入门教程(四):repeated限定修饰符_Mike江的博客-CSDN博客_protobuf的repeated](https://blog.csdn.net/tennysonsky/article/details/73921025)
  • [技术干货] SpringBoot到底是什么?如何理解parent、starter、引导类以及内嵌Tomcat?[转载]
    一、前言前面已经讲解了快速上手SpringBoot入门程序制作的四种方式,相信各位小伙伴们已经可以熟练的使用这些方式来创建一个简单的web程序了,但是仅仅知道这些还是不够的。接下来,带大家一起了解parent、starter、引导类、以及内嵌Tomcat相关的知识!二、百度百科Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。三、简化开发从百度百科中可以看出,其目的是用来简化Spring!那么到底简化在什么地方呢?让我们想想在学习SSM时,做过原始SpringMVC程序的小伙伴应该知道,写SpringMVC程序,最基础的spring-web和spring-webmvc这两个坐标是必须的,这些还不包含我们使用的json啊等等坐标,现在呢?一个坐标搞定!以前写配置类或者配置文件,然后用什么东西就要自己写加载bean这些东西,现在呢?什么都没写,照样能用。有以下优点:简化依赖配置简化常用工程相关配置内置服务器,比如Tomcat别着急,让我们慢慢来探讨探讨其中的奥秘~四、parent介绍打开创建好的springboot程序,可以看见pom.xml文件中的<parent> </parent> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.4</version> <relativePath/> </parent>这里的<version>2.6.4<version>就是自己使用的springboot版本,打开后可以发现其中又继承了一个坐标,引入了很多依赖<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.6.4</version> </parent>再次点击打开,就可以找到其中的奥秘了。从下图我们可以发现各式各样的依赖版本号属性,下面列出依赖版本属性的局部,可以看的出来,定义了若干个技术的依赖版本号再看看下图,各式各样的的依赖坐标信息,可以看出依赖坐标定义中没有具体的依赖版本号,而是引用了第一组信息中定义的依赖版本属性值注意:上面的依赖坐标定义是出现在<dependencyManagement>标签中的,其实是对引用坐标的依赖管理,并不是实际使用的坐标。因此当我们的项目中继承了这组parent信息后,在不使用对应坐标的情况下,前面的这组定义是不会具体导入某个依赖的最后来看看使用不同的springboot版本时,其对应的pom依赖文件有什么不同。我这里对比的是springboot2.5.6版本和springboot2.6.4从图中可以清楚的看到,当我们使用不同的springboot版本时,他们的依赖版本就会不同。这也确保了,在使用springboot时,我们可以在某种程度上避免版本冲突的复杂问题,方便了程序员们的开发!五、starter介绍SpringBoot关注到开发者在实际开发时,对于依赖坐标的使用往往都有一些固定的组合方式,比如使用spring-webmvc就一定要使用spring-web。每次都要固定搭配着写,非常繁琐,而且格式固定,没有任何技术含量。​SpringBoot一看这种情况,把所有的技术使用的固定搭配格式都给开发出来,以后我们使用某个技术,就不用一次写一堆依赖了,直接用springboot做好的这个东西就好了,对于这样的固定技术搭配,SpringBoot给它起了个名字叫做starter。​starter定义了使用某种技术时对于依赖的固定搭配格式,也是一种最佳解决方案,使用starter可以帮助开发者减少依赖配置 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>比如我想开发web应用,就需要引入上面的web对应的starter依赖,并没有写SpringMVC的坐标,点击spring-boot-starter-web我们会发现在spring-boot-starter-web中又定义了若干个具体依赖的坐标通过上图我们可以细心的发现叫做spring-boot-starter-json的名字中也有starter,打开看看里面有什么?我们可以发现,这个starter中又包含了若干个坐标,其实就是使用SpringMVC开发通常都会使用到Json,使用json又离不开这里面定义的这些坐标,看来还真是方便,SpringBoot把我们开发中使用的东西能用到的都给提前做好了。仔细看完会发现,里面有一些我们没用过的。的确会出现这种过量导入的可能性,不过没关系,可以通过maven中的排除依赖剔除掉一部分。不过你不管它也没事,大不了就是过量导入呗。​到这里基本上得到了一个信息,使用starter可以帮开发者快速配置依赖关系六、starter与parent的区别朦朦胧胧中感觉starter与parent好像都是帮助我们简化配置的,但是功能又不一样:​starter是一个坐标中定了若干个坐标,以前写多个的,现在写一个,是用来减少依赖配置的书写量的​parent是定义了几百个依赖版本号,以前写依赖需要自己手工控制版本,现在由SpringBoot统一管理,这样就不存在版本冲突了,是用来减少依赖冲突的温馨提示​ SpringBoot官方给出了好多个starter的定义,方便我们使用,而且名称都是如下格式命名规则:spring-boot-starter-技术名称七、引导类介绍配置说完了,我们发现SpringBoot确实帮助我们减少了很多配置工作,下面说一下程序是如何运行的。目前程序运行的入口就是SpringBoot工程创建时自带的那个类了,带有main方法的那个类,运行这个类就可以启动SpringBoot工程的运行,我的是这个:@SpringBootApplication public class Springboot0101Application { public static void main(String[] args) { SpringApplication.run(Springboot0101Application.class, args); }写代码测试一下,先创建一个User类,把它放在容器中@Component public class User { }然后再写一个BookController类,也把它放在容器中@RestController @RequestMapping("/books") public class BookController { @GetMapping("/getBooks") public String getBooks() { System.out.println("springboot程序正在运行呢~"); return "Hello,SpringBoot is running"; } }看看我对应类的目录结构:最后写代码测试一下:@SpringBootApplication public class Springboot0101Application { public static void main(String[] args) { ConfigurableApplicationContext applicationContext = SpringApplication.run(Springboot0101Application.class, args); BookController bookBean = applicationContext.getBean(BookController.class); System.out.println("The message of bookBean : " + bookBean); User userBean = applicationContext.getBean(User.class); System.out.println("The message of userBean : " + userBean); } }运行结果:看到结果,小伙伴们不难猜想了——SpringBoot程序启动是创建了一个Spring容器对象吧?答案就是如此!Springboot0101Application这个类在SpringBoot程序中是所有功能的入口,称这个类为引导类。​作为一个引导类最典型的特征就是当前类上方声明了一个注解@SpringBootApplication点击进入@SpringBootApplication,我们可以看到:这里面有我们之前学习SSM时用到的包扫描注解,再点击进入@SpringBootConfiguration内:我们可以发现,它最终使用了@Configuration注解,所以,归根到底,我们使用的引用类,也是一个配置类。八、内嵌Tomcat1、Tomcat定义位置程序现在已经运行了,通过引导类的main方法运行了起来。但是运行java程序不应该是执行完就结束了吗?但是我们现在明显是启动了一个web服务器啊,不然网页怎么能正常访问呢?这个服务器是在哪里写的呢?认真想一想,它就在我们引入的spring-boot-starter-web场景starter中,我们打开它来看一看:这里面有一个核心的坐标,tomcat-embed-core,叫做tomcat内嵌核心。就是这个东西把tomcat功能引入到了我们的程序中。2、Tomcat运行原理再来说第二个问题,这个服务器是怎么运行的?Tomcat服务器是一款软件,而且是一款使用java语言开发的软件,既然是使用java语言开发的,运行的时候肯定符合java程序运行的原理,java程序运行靠的是什么?对象呀,一切皆对象,万物皆对象。那tomcat运行起来呢?也是对象。如果是对象,那Spring容器是用来管理对象的,这个对象能不能交给Spring容器管理呢?答案是可以的!tomcat服务器运行其实是以对象的形式在Spring容器中运行的,怪不得我们没有安装这个tomcat,而且还能用。闹了白天这东西最后是以一个对象的形式存在,保存在Spring容器中悄悄运行的。具体运行的是什么呢?其实就是上前面提到的那个tomcat内嵌核心具体内嵌核心依赖如下:<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.58</version> <scope>compile</scope> <exclusions> <exclusion> <artifactId>tomcat-annotations-api</artifactId> <groupId>org.apache.tomcat</groupId> </exclusion> </exclusions> </dependency>3、更换内嵌Tomcat那既然是个对象,如果把这个对象从Spring容器中去掉是不是就没有web服务器的功能呢?当然可以,通过依赖排除可以去掉这个web服务器功能。根据SpringBoot的工作机制,用什么技术,加入什么依赖就行了。我选择的是SpringBoot提供的内置服务器jetty更换代码如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency>让我们运行一下看看是什么样的结果:输出结果是没有问题的,但是服务器就不是默认的Tomcat了,而是我选择的jetty服务器链接:https://bbs.huaweicloud.com/blogs/336909
  • [技术干货] spring之Spring Boot
    什么是 Spring BootSpring Boot 是由 Pivotal 团队提供的基于 Spring 的全新框架,其设计目的是为了简化 Spring 应用的搭建和开发过程。该框架遵循“约定大于配置”原则,采用特定的方式进行配置,从而使开发者无需定义大量的 XML 配置。通过这种方式,Spring Boot 致力于在蓬勃发展的快速应用开发领域成为领导者。Spring Boot 并不重复造轮子,而且在原有 Spring 的框架基础上封装了一层,并且它集成了一些类库,用于简化开发。换句话说,Spring Boot 就是一个大容器。我们可以注意到,没有写任何的配置文件,更没有显示使用任何容器,只需要启动 Main 方法即可开启 Web 服务,从而访问到 HelloController 类里定义的路由地址。这就是 Spring Boot 的强大之处,它默认集成了 Tomcat 容器,通过 Main 方法编写的 SpringApplication.run 方法即可启动内置 Tomcat。它是如何启动的,内部又是如何运行的呢?具体原理我将在《第03课:Spring Boot 启动原理》一节中具体分析。在上面的示例中,我们没有定义应用程序启动端口,可以看到控制台,它开启了 8080 端口,这是 Spring Boot 默认的启动端口。Spring Boot 提供了默认的配置,我们也可以改变这些配置,具体方法将在后面介绍。我们在启动类里加入 @SpringBootApplication 注解,则这个类就是整个应用程序的启动类。如果不加这个注解,启动程序将会报错,读者可以尝试一下。Spring Boot 还有一个特点是利用注解代码繁琐的 XML 配置,整个应用程序只有一个入口配置文件,那就是 application.yml 或 application.properties。接下来,我将介绍其配置文件的用法。properties 和 yaml在前面的示例代码中,我们并没有看到该配置文件,那是因为 Spring Boot 对每个配置项都有默认值。当然,我们也可以添加配置文件,用以覆盖其默认值,这里以 .properties 文件为例,首先在 resources 下新建一个名为 application.properties(注意:文件名必须是 application)的文件,键入内容为:server.port=8081server.servlet.context-path=/api并且启动 Main 方法,这时程序请求地址则变成了:http://localhost:8081/api/hello。Spring Boot 支持 properties 和 yaml 两种格式的文件,文件名分别对应 application.properties 和 application.yml,下面贴出 yaml 文件格式供大家参考:server:  port: 8080  servlet:    context-path: /api可以看出 properties 是以逗号隔开,而 yaml 则换行+ 两个空格 隔开,这里需要注意的是冒号后面必须空格,否则会报错。yaml 文件格式更清晰,更易读,这里作者建议大家都采用 yaml 文件来配置。以上示例只是小试牛刀,更多的配置将在后面的课程中讲解。本教程的所有配置均采用 yaml 文件。打包、运行Spring Boot 打包分为 war 和 jar 两个格式,下面将分别演示如何构建这两种格式的启动包。在 pom.xml 加入如下配置:<packaging>war</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>    </plugins></build>这个时候运行 mvn package 就会生成 war 包,然后放到 Tomcat 当中就能启动,但是我们单纯这样配置在 Tomcat 是不能成功运行的,会报错,需要通过编码指定 Tomcat 容器启动,修改 DemoApplication 类:import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.builder.SpringApplicationBuilder;import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;@SpringBootApplicationpublic class DemoApplication extends SpringBootServletInitializer {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }    @Override    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {        return application.sources(DemoApplication.class);    }}这时再打包放到 Tomcat,启动就不会报错了。在上述代码中,DemoApplication 类继承了 SpringBootServletInitializer,并重写 configure 方法,目的是告诉外部 Tomcat,启动时执行该方法,然后在该方法体内指定应用程序入口为 DemoApplication 类,如果通过外部 Tomcat 启动 Spring Boot 应用,则其配置文件设置的端口和 contextPath 是无效的。这时,应用程序的启动端口即是 Tomcat 的启动端口,contextPath 和 war 包的文件名相同。接下来我们继续看如果达成 jar 包,在 pom.xml 加入如下配置:<!-- 需要将包类型改为 jar 包 --><packaging>jar</packaging><build>    <finalName>api</finalName>    <resources>        <resource>            <directory>src/main/resources</directory>            <filtering>true</filtering>        </resource>    </resources>    <plugins>        <plugin>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-maven-plugin</artifactId>            <configuration>                <fork>true</fork>                <!-- 指定 Main 方法所在类 -->                <mainClass>com.lynn.DemoApplication</mainClass>            </configuration>            <executions>                <execution>                    <goals>                        <goal>repackage</goal>                    </goals>                </execution>            </executions>        </plugin>        <plugin>            <artifactId>maven-resources-plugin</artifactId>            <version>2.5</version>            <configuration>                <encoding>UTF-8</encoding>                <useDefaultDelimiters>true</useDefaultDelimiters>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-surefire-plugin</artifactId>            <version>2.18.1</version>            <configuration>                <skipTests>true</skipTests>            </configuration>        </plugin>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-compiler-plugin</artifactId>            <version>2.3.2</version>            <configuration>                <source>1.8</source>                <target>1.8</target>            </configuration>        </plugin>    </plugins></build>然后通过 mvn package 打包,最后通过 java 命令启动:java -jar api.jar如果是 Linux 服务器,上述命令是前台进程,点击 Ctrl+C 进程就会停止,可以考虑用 nohup 命令开启守护进程,这样应用程序才不会自动停止。
  • [技术干货] SpringBoot框架中MyBatis的应用
    MyBatis的学习1.中文文档Mybayis中文文档下载地址      MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。      MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。如何获取mybatis:maven仓库<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> githubmabatis在github中的下载地址2.持久层Dao层,Service层,Controller层3.学习过程3.1 导入依赖<dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>3.2 创建模块写一个核心配置文件<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSl=true&amp;useUnicode=true&amp;characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>写一个工具类package utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MyBatisUtil { private static SqlSessionFactory sqlSessionFactory; static { try { //使用mybatis第一步获取sqlSession对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ SqlSession sqlSession=sqlSessionFactory.openSession(); return sqlSession; } } 写一个实体类package pojo; public class User { private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }写一个接口类package dao; import pojo.User; import java.util.List; //编写一个接口类 public interface UserDao { List<User> getUserList(); } mybatis中直接通过xml文件连接数据库和实现接口类
  • [问题求助] springboot 如何通过华为云存储路径返回图片
    【功能模块】【操作步骤&问题现象】1、2、【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [问题求助] 启动SpringBoot服务失败
    【功能模块】部署【操作步骤&问题现象】1、执行部署时启动SpringBoot服务失败2、看了帮助文档想知道下是否端口被占用,被占用该如何修改【截图信息】【日志信息】(可选,上传日志内容或者附件)
  • [技术干货] SpringBoot 整合mongoDB并自定义连接池的示例代码
    得力于SpringBoot的特性,整合mongoDB是很容易的,我们整合mongoDB的目的就是想用它给我们提供的mongoTemplate,它可以很容易的操作mongoDB数据库。为了自定义连接池,我们在配置类中主要与MongoClientOptions、MongoCredential、MongoClient、MongoDbFactory打交道。最终的目的就是配置好一个MongoDbFactory的bean交由Spring管理。Maven 依赖1234<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>配置文件123456789101112131415161718192021mongodb:  database: bfa_mongo  username: "xxx"  password: "xxxxx"  address: "host:port"  authenticationDatabase: [设置你的认证数据库,如果有的话]  # 连接池配置  clientName: ${spring.application.name} # 客户端的标识,用于定位请求来源等  connectionTimeoutMs: 10000     # TCP连接超时,毫秒  readTimeoutMs: 15000       # TCP读取超时,毫秒  poolMaxWaitTimeMs: 3000        #当连接池无可用连接时客户端阻塞等待的时长,单位毫秒  connectionMaxIdleTimeMs: 60000   #TCP连接闲置时间,单位毫秒  connectionMaxLifeTimeMs: 120000    #TCP连接最多可以使用多久,单位毫秒  heartbeatFrequencyMs: 20000      #心跳检测发送频率,单位毫秒  minHeartbeatFrequencyMs: 8000    #最小的心跳检测发送频率,单位毫秒  heartbeatConnectionTimeoutMs: 10000  #心跳检测TCP连接超时,单位毫秒  heartbeatReadTimeoutMs: 15000    #心跳检测TCP连接读取超时,单位毫秒  connectionsPerHost: 20       # 每个host的TCP连接数  minConnectionsPerHost: 5     #每个host的最小TCP连接数  #计算允许多少个线程阻塞等待可用TCP连接时的乘数,算法:threadsAllowedToBlockForConnectionMultiplier*connectionsPerHost,当前配置允许10*20个线程阻塞  threadsAllowedToBlockForConnectionMultiplier: 10注意:其中的address参数可以配置为一个数组(代表集群模式)123address:    - "host:port"   - "host2:port2"MongoConfig配置类配置类中使用了lombok,如果你没有用lombok依赖和IDE插件,你要重写getter、Setter方法:代码稍长,可以复制在IDEA中查看:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163import com.mongodb.MongoClient;import com.mongodb.MongoClientOptions;import com.mongodb.MongoCredential;import com.mongodb.ServerAddress;import lombok.Getter;import lombok.Setter;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.BeanFactory;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.mongodb.MongoDbFactory;import org.springframework.data.mongodb.core.SimpleMongoDbFactory;import org.springframework.data.mongodb.core.convert.DbRefResolver;import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;import org.springframework.data.mongodb.core.convert.MappingMongoConverter;import org.springframework.data.mongodb.core.convert.MongoCustomConversions;import org.springframework.data.mongodb.core.mapping.MongoMappingContext;import org.springframework.validation.annotation.Validated; import javax.validation.constraints.Min;import javax.validation.constraints.NotNull;import javax.validation.constraints.Size;import java.util.ArrayList;import java.util.Arrays;import java.util.List; @Slf4j@Configuration@EnableConfigurationProperties(MongoConfig.MongoClientOptionProperties.class)public class MongoConfig {     /**     * monogo 转换器     * @return     */    @Bean    public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory,                                                       MongoMappingContext context, BeanFactory beanFactory, MongoCustomConversions conversions) {        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);        // remove _class field//    mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null));        mappingConverter.setCustomConversions(conversions);        return mappingConverter;    }     /**     * 自定义mongo连接池     * @param properties     * @return     */    @Bean    public MongoDbFactory mongoDbFactory(MongoClientOptionProperties properties) {        //创建客户端参数        MongoClientOptions options = mongoClientOptions(properties);         //创建客户端和Factory        List<ServerAddress> serverAddresses = new ArrayList<>();        for (String address : properties.getAddress()) {            String[] hostAndPort = address.split(":");            String host = hostAndPort[0];            Integer port = Integer.parseInt(hostAndPort[1]);            ServerAddress serverAddress = new ServerAddress(host, port);            serverAddresses.add(serverAddress);        }         //创建认证客户端        MongoCredential mongoCredential = MongoCredential.createScramSha1Credential(properties.getUsername(),                properties.getAuthenticationDatabase() != null ? properties.getAuthenticationDatabase() : properties.getDatabase(),                properties.getPassword().toCharArray());         MongoClient mongoClient = new MongoClient(serverAddresses.get(0), mongoCredential, options);        //集群模式        if (serverAddresses.size() > 1) {            mongoClient = new MongoClient(serverAddresses, new ArrayList<>(Arrays.asList(mongoCredential)));        }        /** ps: 创建非认证客户端*/        //MongoClient mongoClient = new MongoClient(serverAddresses, mongoClientOptions);        return new SimpleMongoDbFactory(mongoClient, properties.getDatabase());    }     /**     * mongo客户端参数配置     * @return     */    public MongoClientOptions mongoClientOptions(MongoClientOptionProperties properties) {        return MongoClientOptions.builder()                .connectTimeout(properties.getConnectionTimeoutMs())                .socketTimeout(properties.getReadTimeoutMs()).applicationName(properties.getClientName())                .heartbeatConnectTimeout(properties.getHeartbeatConnectionTimeoutMs())                .heartbeatSocketTimeout(properties.getHeartbeatReadTimeoutMs())                .heartbeatFrequency(properties.getHeartbeatFrequencyMs())                .minHeartbeatFrequency(properties.getMinHeartbeatFrequencyMs())                .maxConnectionIdleTime(properties.getConnectionMaxIdleTimeMs())                .maxConnectionLifeTime(properties.getConnectionMaxLifeTimeMs())                .maxWaitTime(properties.getPoolMaxWaitTimeMs())                .connectionsPerHost(properties.getConnectionsPerHost())                .threadsAllowedToBlockForConnectionMultiplier(                        properties.getThreadsAllowedToBlockForConnectionMultiplier())                .minConnectionsPerHost(properties.getMinConnectionsPerHost()).build();    }     @Getter    @Setter    @Validated    @ConfigurationProperties(prefix = "mongodb")    public static class MongoClientOptionProperties {         /** 基础连接参数 */        private String database;        private String username;        private String password;        @NotNull        private List<String> address;        private String authenticationDatabase;         /** 客户端连接池参数 */        @NotNull        @Size(min = 1)        private String clientName;        /** socket连接超时时间 */        @Min(value = 1)        private int connectionTimeoutMs;        /** socket读取超时时间 */        @Min(value = 1)        private int readTimeoutMs;        /** 连接池获取链接等待时间 */        @Min(value = 1)        private int poolMaxWaitTimeMs;        /** 连接闲置时间 */        @Min(value = 1)        private int connectionMaxIdleTimeMs;        /** 连接最多可以使用多久 */        @Min(value = 1)        private int connectionMaxLifeTimeMs;        /** 心跳检测发送频率 */        @Min(value = 2000)        private int heartbeatFrequencyMs;         /** 最小的心跳检测发送频率 */        @Min(value = 300)        private int minHeartbeatFrequencyMs;        /** 计算允许多少个线程阻塞等待时的乘数,算法:threadsAllowedToBlockForConnectionMultiplier*connectionsPerHost */        @Min(value = 1)        private int threadsAllowedToBlockForConnectionMultiplier;        /** 心跳检测连接超时时间 */        @Min(value = 200)        private int heartbeatConnectionTimeoutMs;        /** 心跳检测读取超时时间 */        @Min(value = 200)        private int heartbeatReadTimeoutMs;         /** 每个host最大连接数 */        @Min(value = 1)        private int connectionsPerHost;        /** 每个host的最小连接数 */        @Min(value = 1)        private int minConnectionsPerHost;    }}MappingMongoConverter可以自定义mongo转换器,主要自定义存取mongo数据时的一些操作,例如 mappingConverter.setTypeMapper(new DefaultMongoTypeMapper(null)) 方法会将mongo数据中的_class字段去掉。最后通过 new SimpleMongoDbFactory(mongoClient, properties.getDatabase())方法配置了一个MongoDbFactory交由Spring管理,Springboot会拿这个MongoDbFactory工厂bean来new一个MongoTemplate,在MongoDbFactoryDependentConfiguration类下可以看到SpringBoot帮你做得事:1234567891011121314151617181920212223242526272829/** * Configuration for Mongo-related beans that depend on a {@link MongoDbFactory}. * * @author Andy Wilkinson */@Configuration@ConditionalOnBean(MongoDbFactory.class)class MongoDbFactoryDependentConfiguration {     private final MongoProperties properties;    MongoDbFactoryDependentConfiguration(MongoProperties properties) {        this.properties = properties;    }         //SpringBoot创建MongoTemplate实例    @Bean    @ConditionalOnMissingBean    public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoConverter converter) {        return new MongoTemplate(mongoDbFactory, converter);    @ConditionalOnMissingBean(MongoConverter.class)    public MappingMongoConverter mappingMongoConverter(MongoDbFactory factory, MongoMappingContext context,            MongoCustomConversions conversions) {        DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);        MappingMongoConverter mappingConverter = new MappingMongoConverter(dbRefResolver, context);        mappingConverter.setCustomConversions(conversions);        return mappingConverter;         //...}SpringBoot利用我们配置好的MongoDbFactory在配置类中生成一个MongoTemplate,之后我们就可以在项目代码中直接@Autowired了。因为用于生成MongoTemplate的MongoDbFactory是我们自己在MongoConfig配置类中生成的,所以我们自定义的连接池参数也就生效了。到此这篇关于SpringBoot 整合mongoDB并自定义连接池的文章就介绍到这了转载自https://www.jb51.net/article/238879.htm
  • [技术讨论] spring boot是Spring Data JPA和mybatis哪个用着更方便?
    spring boot是Spring Data JPA和mybatis哪个用着更方便?
  • [java] 不会用SpringBoot连接Redis,那就赶紧看这篇
    摘要:如何通过springboot来集成操作Redis。本文分享自华为云社区《SpringBoot连接Redis操作教程》,作者: 灰小猿。今天来和大家分享一个如何通过springboot来集成操作Redis。一、SpringBoot连接Redisspringboot连接Redis时需要在pom文件中导入所需的jar包依赖,依赖如下: <!-- 加入jedis依赖 --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> (1)使用Jedis类直接连接Redis服务器在springboot环境下连接redis的方法有很多,首先最简单的就是直接通过jedis类来连接,jedis类就相当于是redis的客户端表示。连接方法如下: /** * redis连接测试01 */ @Test public void redisTest01() { //连接本地的 Redis 服务 Jedis jedis = new Jedis("localhost"); // 如果 Redis 服务设置了密码,需要用下面这行代码输入密码 // jedis.auth("123456"); System.out.println("连接成功"); //查看服务是否运行 System.out.println("服务正在运行: "+jedis.ping()); }运行后结果:通过这种方式进行连接时,springboot会自动的去本地寻找redis服务器进行连接,如果没有找到那么就会报错,如果你去阅读jedis的底层源码,你会发现Jedis类有多种构造方法,常用的几个是使用默认地址和端口//不传值,那么使用默认的127.0.0.1地址,6379端口就访问 public Jedis()使用指定地址和默认端口//只传入目的地址,那么使用指定的地址和默认的端口号去访问 public Jedis(String host)使用指定地址和端口//传入目的地址和端口号,那么使用指定的地址和端口号去访问 public Jedis(String host, int port)(2)通过配置文件进行连接在springboot中,当然是可以通过配置文件的形式来设置各种连接参数了,Redis也是一样的,在yml文件中进行如下配置:注意:这是没有使用连接池的,如果使用连接池,需要在下边增加配置,关于使用连接池的可以继续往下看。##redis配置信息 spring: redis: database: 0 #redis数据库索引,默认为0 host: 127.0.0.1 #redis服务器地址 port: 6379 #redis服务器连接端口 password: #redis服务器连接密码,默认为null timeout: 5000 #redis连接超时时间通过配置文件来进行配置之后,我们就可以使用springboot中的一个工具类来操作Redis的操作了,springboot会自动读取配置文件中的配置信息,然后通过该配置信息去连接Redis服务器,springboot中提供操作Redis的工具类有两个,分别是:StringRedisTemplate和RedisTemplate,StringRedisTemplate和RedisTemplate的区别如下在进行序列化时,RedisTemplate使用的是 JdkSerializationRedisSerializer,而StringRedisTemplate使用的是StringRedisSerializerStringRedisTemplate继承了RedisTemplate<String,String>,而RedisTemplate 定义为 RedisTemplate<K, V>,所有StringRedisTemplate就限定了K,V为String类型的相同处体现在他们对Redis的操作上,RedisTemplate和StringRedisSerializer都定义了五种对Redis的操作,分别对应这Redis中的五种数据类型。redisTemplate.opsForValue();  //操作字符串 redisTemplate.opsForHash();   //操作hash redisTemplate.opsForList();   //操作list redisTemplate.opsForSet();   //操作set redisTemplate.opsForZSet();   //操作有序set那么在使用的时候,这两个类应该如何选择呢?如果你的redis数据库里面本来存的是字符串数据,或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,》但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。接下来我以StringRedisSerializer为例子,来给大家演示一下使用StringRedisSerializer操作Redis的方法, /** * springboot主从连接测试, * 使用springRedisTemplate操作 */ @Test public void redisTest06() { // 操作字符型 stringRedisTemplate.opsForValue().set("test06","Test06"); System.out.println(stringRedisTemplate.opsForValue().get("test06")); // 设置key的过期时间,30秒 stringRedisTemplate.expire("test06", 30 * 1000, TimeUnit.MILLISECONDS); // 根据key获取过期时间 Long test06ExpireTime = stringRedisTemplate.getExpire("test06"); System.out.println("根据key获取过期时间:" + test06ExpireTime); // 根据key获取过期时间,并且换算成指定单位 Long test06ExpireTimeToUnit = stringRedisTemplate.getExpire("test06", TimeUnit.SECONDS); System.out.println("根据key获取过期时间,并且换算成指定单位:" + test06ExpireTimeToUnit); // 检查key是否存在,返回布尔类型 Boolean test06IsExist = stringRedisTemplate.hasKey("test06"); System.out.println("检查key是否存在,返回布尔类型:" + test06IsExist); }在上面的操作中,有一点关于获取和设置key过期时间的操作,当时在操作的时候对其进行了一下探究,在这里分享给大家stringRedisTemplate中获取过期时间的getExpire()方法的说明如果最开始没有设置过期时间,那么就返回-1,数据在没有达到Redis数据最大限额的情况下会一直存在.如果设置了过期时间,但是数据还未过期,就返回剩余时间,如果到了过期时间,那么数据会被删除如果数据被删除或者不存在,那么就返回-2.
  • [技术干货] Spring Boot 3.0 M1 发布,正式弃用 Java 8,最低要求 Java 17
    弃用 Java 8,最低要求 Java 17; Java EE 迁移到了 Jakarta EE; 看到这个心理慌的要命........
总条数:53 到第
上滑加载中