3

最近使用mongDb的时候,出现了一些问题。

mongoDb 实体id自增

在保存实体的时候报了这个错

Cannot aotogenerate id of type java.lang.Long for entity
不能自动生成id

image.png

之后就带着报错去谷歌了一下。 得到了如下结果:

Mongo ObjectIds 与 jave Long 类型不匹配。

image.png

先来介绍一下MongoDB 的对象 Id(ObjectId)

ObjectId

ObjectId 是一个12字节 BSON 类型数据,有以下格式:

前4个字节表示时间戳
接下来的3个字节是机器标识码
紧接的两个字节由进程id组成(PID)
最后三个字节是随机数。

image.png

例如 62ee651f cabe7f 59dc f10372

从 MongoDB 3.4 开始(最早发布于 2016 年 12 月),ObjectId 的设计被修改了,中间 5 字节的值由原先的“机器标识码+进程号”改为单纯随机值。

a 4-byte value representing the seconds since the Unix epoch,
a 5-byte random value, and
a 3-byte counter, starting with a random value.

MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任何类型的,默认是个ObjectId对象。

在一个集合里面,每个文档都有唯一的"_id"值,来确保集合里面每个文档都能被唯一标识。

MongoDB采用ObjectId,而不是其他比较常规的做法(比如自动增加的主键)的主要原因,因为在多个 服务器上同步自动增加主键值既费力还费时。

找出原因

之后就在实体定义中发现, 我定义的id是Long类型。
由于mongoDb自动进行id自增的时候,生成的id并不能转换为Long,所以报了错

@JsonIgnoreProperties(ignoreUnknown = true)
@Document
public class System {

  @Id
  @JsonView
  private Long id;
}

之后改为String 类型之后报错消失。

在数据库中查看数据,查看到的是ObjectId类型
image.png

传到前台,是string类型的数据
image.png

总结

在mongoDb中想要让自动设置id的话,需要设置@Id注释,并设置为string类型。

选择使用 String 属性作为 ID,那么 Mongo 会在保存时自动为其赋值(如果它是空的)。通过选择 String,您可以获得一个由数据库管理的自动分配的 ID,而不必费心手动设置该属性。

但是目前的项目中,id都是从gitlab获取的数据,所以不需要自动设置id,若采用Long类型,并自己设置id ,得到数据就是这样, 表示 id为22。
image.png

MongoDB 持久化实体映射

在网上查了有关实体注释时,遇到了一问题。

spring-data-mongodb主要有以下常用的注解.

@Id
主键,不可重复,自带索引

@Document
标注在实体类上,类似于hibernate的entity注解,标明由mongo来维护该表

@Document(collection="mongodb 对应 collection 名")

@Document
public class System {

  @Id
  @JsonView
  private String id;

跟以前不同的是,不用@Entity注解, 用@Document注解。

@Field : 属性

存储到数据库中的字段名。

加这个注解,就是以注解的值对应mongo的key
不加这个注解,默认以参数对应mongo的key

@Field("user_name")
private String userName;

另外@CompoundIndex 联合索引和@Transient:属性就不详细说了。


关联实体

一个比较重要的点就是关联实体。

在JPA中, 我们可以用@ManyToMany,@OneToMany等注释,生成中间表等操作。

但是在mongo中,这些注释貌似并不起作用。

我在尝试添加@ManyToMany后,并没有生成中间表

@DBRef注解

而在mongo实体中,类似@OneToMany等效果的注解是: @DBRef

这个注释表示关联另一个document对象。类似于mysql的表关联。

没有加该注释的情况下,是这样子。作为普通的数据存到labels里。

image.png


加了注释的情况,是这样。
这里拿了其他文章的测试结果,我还没测试

image.png

可以发现而是只保存了它的id和namespace. 相当于一个引用,或者说是外键。

另外值得注意的是,
这个注解并且不会处理级联保存,你必须单独先保存@DBRef关联的对象。

你需要先respority.save(@DBRef关联的对象),然后再保存原来的对象。

这是其他文章的测试结果:
image.png

小结:
看上去mongo处理的方式有点不同。但是目前项目中虽然使用了mongo,但是还在使用@Entity以及 @ManyToMany等注解,也没有发生什么报错。

但是从结果来看,@ManyToMany没有生成中间表。对mongo没有用。

restTemplate请求重试

image.png

在后台向gitlab请求数据的时候,无论尝试了几遍,都会报这个错误。但是别人却能请求。

SSL 异常关闭了。

在之前也报过这个错,网上给出了解决方法一般都是如下。设置一下https协议类型。

System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,SSLv3");

但是尝试了几次都没有效果。


之后查看代码,需要获取35个issue下的评论。 猜想到可能是因为请求次数和数据量相关。
image.png

于是就尝试了,当restTemplate请求失败时,增加重试机制

1,安装配置

编辑 pom.xml 文件,添加 相关依赖

<!-- 重试机制 -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.1.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

2.加入 @EnableRetry 注解

@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
@EnableRetry
@EnableAsync
public class NonlycodeApplication {

3. 使用@Retryable注解

 @Override
 @Retryable(value = {RestClientException.class, EOFException.class}, maxAttempts = 3,
          backoff = @Backoff(delay = 1000L,multiplier = 1))
  public <T> List<T> httGetWithPageRequest(String url, Map<String, String> variables, Integer latestPage, Class<T[]> valueType) throws JsonProcessingException {
   

@Retryable 注解的方法在发生异常时会重试,参数说明:
value:指定重试异常
maxAttemps:最大重试次数,默认 3
backoff:重试等待策略,默认没有

@Backoff 注解为重试等待策略,参数说明:
delay:指定重试的延时时间,默认为 1000L ,1秒
multiplier:指定延迟的倍数,默认为 0。比如 delay=5000l,multiplier=2 时,第一次重试为 5 秒后,第二次为 10 秒,第三次为 20 秒。

这样就能在调用这个方法的时候,只要其中某个异常发生时,被注解的方法就会进行重试。

由于 Retryable 用到了 aspect 增强,所以会有代理类的坑,就是方法内部调用,会使注解失效。

抛出异常

当超过重试次数是异常被抛出。

 //发起请求
    RestTemplate restTemplate = new RestTemplate(new HttpsClientRequestFactory());
    ResponseEntity<String> response = restTemplate.exchange(requestUrl, HttpMethod.GET, new HttpEntity<String>(headers), String.class, variables);

    // 判断请求是否发生异常
    if(!response.getStatusCode().is2xxSuccessful()){
      System.out.println("请求失败...");
      // 抛出异常
      throw new RestClientException(response.getBody());
    }

我们可以定义一个全局异常处理类。接收抛出的异常,返回给前台。

@ControllerAdvice
public class CustomExceptionHandler {
    @ExceptionHandler(RestClientException.class)
    public ResponseEntity<String> throwRestException(RestClientException restClientException){
        return new ResponseEntity<String>(restClientException.getMessage(),
                HttpStatus.BAD_REQUEST);
    }
}

经过测试之后,运行正常,成功进行完所有的请求。


weiweiyi
1k 声望123 粉丝