8 Spring data JPA 之 Jackson 在实体里面的注解详解

经过前⾯课时的讲解,相信你已经对实体⾥⾯的 JPA 注解有了⼀定的了解,但是实际⼯作中你会发现实体⾥⾯不仅有 JPA 的注解,也会⽤到很多 JSON 相关的注解。

我们⽤ Spring Boot ⾥⾯默认集成的 fasterxml.jackson 加以说明,这看似和 JPA 没什么关系,但是⼀旦我们和 @Entity ⼀起使⽤的时候,就会遇到⼀些问题,特别是新⼿同学,我们这⼀课时详细介绍⼀下⽤法。先来跟着我了解⼀下 Jackson 的基本语法。

8.1 Jackson 的基本语法

当我们⽤ spring boot starter 的时候就会默认加载 fasterxml 相关的 jar 包模块,包括核⼼模块以及 jackson 提供的⼀些扩展 jar 包,下⾯详细介绍。

8.1.1 三个核心模块

  1. jackson-core:核⼼包,提供基于“流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过⾼性能的流模式 API 的 JsonGenerator 和 JsonParser 来⽣成和解析 json。

  2. jackson-annotations:注解包,提供标准注解功能,这是我们必须要掌握的基础语法。

  3. jackson-databind:数据绑定包,提供基于“对象绑定”解析的相关 API(ObjectMapper) 和“树模型”解析的相关 API(JsonNode);基于“对象绑定”解析的API 和“树模型”解析的 API 依赖基于“流模式”解析的 API。如下图中⼀些标准的类型转换:

8.1.2 Jackson 提供的扩展 jar 包

  1. jackson-module-parameter-names:对原来的 jackson 进⾏了扩展,⽀持了构造⽅法和⽅法基本的参数⽀持。
  2. jackson-datatype:是对字段类型的⽀持做的⼀些扩展,包括下述⼏个部分。
    • jackson-datatype-jdk8:是对 jdk8 语法⾥⾯的⼀些 Optional、Stream 等⼀些新的类型做的⼀些⽀持。
    • jackson-datatype-jsr310:是对 jdk8 中的 JSR310 时间协议做了⽀持,如 Duration、Instant、LocalDate、Clock 等时间类型的序列化、反序列化。
    • .jackson-datatype-hibernate5:是对Hibernate的⾥⾯的⼀些数据类型的序列化、反序列化,如 HibernateProxy 等。

剩下不常⻅的咱们就不说了,jackson-datatype 其实就是对⼀些常⻅的数据类型做序列化、反序列化,省去了我们⾃⼰写序列化、反序列化的过程。所以在我们⼯作中,如果需要⾃定义序列化的时候,可以参考这些源码。

知道了这些脉络之后,剩下的就是我们要掌握的注解有哪些了,下⾯我来介绍⼀下。

8.1.3 Jackson 中常用的一些注解

正如上⾯所说,我们打开 jackson-annotations,就可以看到有哪些注解了,⼀⽬了然,闲着没事的时候就可以到这⾥⾯看看,这样你会越来越熟悉。下⾯我们挑选⼀些常⽤的介绍⼀下。

Jackson ⾥⾯常⽤的注解如下所示:

注解 示例
@JsonProperty 用于属性,把属性的名称序列化给 JSON 字符串时转换为另外一个名称
@JsonProperty
private String userName
@JsonFormat 用于属性或者方法,把属性的格式序列化时指定成指定的格式
@JsonFormat
private Date getCreateDate()
@JsonPropertyOrder 用于类,指定属性在序列化时 JSON 中的顺序
@JsonPropertyOrder({“birth_date”,“name”})
private class User
@JsonCreator 用于构造方法,和 @JsonProperty 配合使用,适用有参数的构造方法
@JsonCreator
private User(@JsonProperty(“name”)String name) {}
@JsonAnySetter 用于属性或者方法,设置未反序列化的属性名和值作为键值存储到 map 中
@JsonAnySetter
private void set(String key, Object value) {
map.put(key, value);
}
@JsonAnyGetter 用于方法,获取所有未序列化的属性,一般与 @JsonAnySetter 成对出现
@JsonAnyGetter
private Map<String,Object> get() {
return map;
}
@JsonIgnore 用于告诉 Jackson 在序列化,反序列化时忽略 Java 对象的某个属性(字段)
@JsonIgnore
private long personId = 0;
@JsonIgnoreProperties 注解在类声明上方,用于指定要忽略的类的属性列表
@JsonIgnoreProperties({“birth_date”,“name”})
private class User
@JsonAutoDetect 用于告诉 Jackson 在读写对象时,包括非 public 修饰的属性
@JsonDeserialize,@JsonSerialize 用户指定字段的自定义序列化、反序列化
@JsonInclude 用于告诉 Jackson 包括那些情况下的属性,例如,仅仅显示非空的字段
@JsonInclude(JsonInclude.Include.NON_EMPTY)

8.1.4 实例

⾸先,新建⼀个 UserJson 实体对象,将它转成 Json 对象,如下所示:

@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@JsonPropertyOrder({"createDate", "email"})
@JsonIgnoreProperties({"hibernateLazyInitializer"})
public class UserJson implements Serializable {@Id@GeneratedValue(strategy = GenerationType.AUTO)private Long id;@JsonProperty("my_name")private String name;private Instant createDate;@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")private Date updateDate;private String email;@JsonIgnoreprivate String sex;@JsonCreatorpublic UserJson(@JsonProperty("email") String email) {System.out.println("其他业务逻辑");this.email = email;}@Transient@JsonAnySetterprivate Map<String, Object> other = new HashMap<>();@JsonAnyGetterpublic Map<String, Object> getOther() {return other;}
}

然后,我们写⼀个测试⽤例,看⼀下运⾏结果。

@DataJpaTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class UserJsonRepositoryTest {@Autowiredprivate UserJsonRepository userJsonRepository;@BeforeAll@Rollback(false)@Transactionalvoid init() {UserJson user = UserJson.builder().name("jackxx").createDate(Instant.now()).updateDate(new Date()).sex("men").email("123456@126.com").build();userJsonRepository.saveAndFlush(user);}@Test@Rollback(false)public void testUserJson() throws JsonProcessingException {UserJson userJson = userJsonRepository.getById(1L);userJson.setOther(Maps.newHashMap("address", "shanghai"));ObjectMapper objectMapper = new ObjectMapper();objectMapper.registerModule(new JavaTimeModule());System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(userJson));}
}

最后,运⾏⼀下可以看到如下结果。

{"createDate" : 1646107023.872000000,"email" : "123456@126.com","id" : 1,"updateDate" : "2022-03-01 11:57","my_name" : "jackxx","address" : "shanghai"
}

我们通过例⼦可以很容易想到使⽤场景是 SpringMvc 的情况下,在 get 请求的时候我们要⽤到序列化;在 post 请求的时候我们要⽤到反序列化,将 json 字符串反向转化成实体对象。

那么在 Spring ⾥⾯ Jackson 都有哪些应⽤场景呢?我们来看⼀下。

8.2 Jackson 和 Spring 的关系

8.2.1 应用场景一:Spring MVC 的 View 层

在Spring MVC中,我们需要知道Mvc的JSON视图的加载原理。我们看⼀下源码,mvc 对象的转化类:HttpMessageConvertersAutoConfiguration,⾥⾯要利⽤JacksonHttpMessageConvertersConfiguration,如下所示:

⽽⾥⾯的 MappingJackson2HttpMessageConverter 正是采⽤ fasterxml.jackson 进⾏转化的,看下⾯的图⽚。

8.2.2 应用场景二:Open-Feign

我们在微服务之间相互调⽤的时候,都会⽤到 HttpMessageConverter ⾥⾯的JacksonHttpMessageConverter 进⾏转化。特别是在⽤ open-feign ⾥⾯的 Encode 和 Decode 的时候,我们就可以看到如下应⽤场景:

8.2.3 应用场景三:Redis 里面

redis、cacheable 都会⽤到 value 的序列化,都离不开 JSON 的序列化。

8.2.4 应用场景四:JMS 消息序列化

当我们项⽬之间解耦⽤到消息队列的时候,可能会基于 JMS 消息协议发送消息,其也是基于 JSON 的序列化机制来继续 converter 的,它在⽤ JmsTemplate 的时候也会遇到同样情况。

综上四个场景所述,我们是经常和 Entity 打交道的,⽽ @Entity ⼜要在各种场景转化成 JSONString,所以 Jackson 的原理我们还是要掌握⼀些的,下⾯来分析⼏个⽐较重要的。

8.3 Jackson 的原理分析

8.3.1 Jackson 的可见性原理分析

前⾯我们看到了注解 @JsonAutoDetect JsonAutoDetect.Visibility 类包含与 Java 中的可⻅性级别匹配的常量,表示 ANY、DEFAULT、NON_PRIVATE、NONE、PROTECTED_AND_PRIVATE和PUBLIC_ONLY。

那么我们打开这个类,看⼀下源码:

这⾥⾯的代码并不复杂,通过JsonAutoDetect 我们可以看到,Jackson 默认不是所有的属性都可以被序列化和反序列化。默认的属性可视化的规则如下:

  1. 若该属性修饰符是 public,该属性可序列化和反序列化。
  2. 若属性的修饰符不是 public,但是它的 getter ⽅法和 setter ⽅法是 public,该属性可序列化和反序列化。因为 getter ⽅法⽤于序列化,⽽ setter ⽅法⽤于反序列化。
  3. 若属性只有 public 的 setter ⽅法,⽽⽆ public 的 getter ⽅法,该属性只能⽤于反序列化。

所以我们可以通过私有字段的 public get 和 public set ⽅法控制是否可以序列化。这⾥可以和我们前⾯讲到的“JPA 实体⾥⾯的注解⽣效⽅式”做⼀下对⽐,也可以通过直接更改 ObjectMapper 设置可视化策略,如下所示:

ObjectMapper mapper = new ObjectMapper();
// PropertyAccessor ⽀持的类型有 ALL,CREATOR,FIELD,GETTER,IS_GETTER,NONE,SETTER
// Visibility ⽀持的类型有 ANY,DEFAULT,NON_PRIVATE,NONE,PROTECTED_AND_PUBLIC,PUBLIC_ONLY
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

这样,就可以直接看到所有字段了,包括私有字段。接着我们说⼀下反序列化相关⽅法。

8.3.2 反序列化最重要的方法

我们在做反序列化的时候要⽤到的三个重要⽅法如下所示。

public <T> T readValue(String content, Class<T> valueType)
public <T> T readValue(String content, TypeReference<T> valueTypeRef)
public <T> T readValue(String content, JavaType valueType)

可以看出,反序列化的时候要知道 java 的 Type 是很重要的,如下:

// 单个对象的写法:
UserJson user = objectMapper.readValue(json, UserJson.class);
// 返回List的返回结果的写法:
List<UserJson> personList2 = objectMapper.readValue(jsonListString, new TypeReference<List<UserJson>>(){});
// 返回List的返回结果,性能更快的写法:
List<UserJson> users = Arrays.asList(objectMapper.readValue(jsonListString, UserJson[].class));

我们也可以看一下 AbstractJackson2HttpMessageConverter ⾥⾯的⽤法。

这个时候你应该很好奇,readValue ⾥⾯是如何判断 java 类型的呢?我们看下 ObjectMapper 的源码⾥⾯做了如下操作:

public <T> T readValue(InputStream src, Class<T> valueType)throws IOException, StreamReadException, DatabindException
{_assertNotNull("src", src);return (T) _readMapAndClose(_jsonFactory.createParser(src), _typeFactory.constructType(valueType));
}

到这⾥,我们看到 typeFactory ⾥⾯的 constructType 可以取到各种 type,那么点击进去看看。

可以看到⾥⾯处理各种 java 类型和泛型的情况,当我们⾃⼰写反射代码的时候可以参考这⼀段,或者直接调⽤。此外,ObjectMapper ⾥⾯还⼀个重要的概念就是 Moduel,我们来看下。

8.3.3 Module 的加载机制

ObejctMapper ⾥⾯可以扩展很多 datatype,⽽不同的 datatype 封装到了不通的 modules ⾥⾯,我们可以 register 注册进去不同的 module,从⽽处理不同的数据类型。

⽬前 Modules 官⽅⽹站提供了很多内容,具体你可以查看这个⽹址:https://github.com/FasterXML/jackson#third-party-datatype-modules。这⾥我们重点说⼀下常⽤的加载机制。

我们通过在代码⾥⾯设置⼀个断点,就可以很清楚地知道常⽤的 ModuleType 都有哪些,如 Jdk8、jsr310、Hibernate5 等。在MVC ⾥⾯默认的 Module 也是图上那些,Hibernate5 是我们⾃⼰引⼊的,具体解决什么问题和如何⾃定义的呢?我们接着往下看。

我们通过在代码⾥⾯设置⼀个断点,就可以很清楚地知道常⽤的 ModuleType 都有哪些,

如 Jdk8、jsr310、Hibernate5 等。在MVC ⾥⾯默认的 Module 也是图上那些,Hibernate5 是我们⾃⼰引⼊的,具体解决什么问题和如何⾃定义的呢?我们接着往下看。

8.4 Jackson 与 JPA 常见的问题

我们⽤ JPA 的时候,特别是关联关系的时候,最常⻅的就是死循环了,你在使⽤时⼀定要注意。

8.4.1 如何解决死循环问题

第⼀种情况:我们在写 ToString ⽅法,特别是 JPA 的实体的时候,很容易陷⼊死循环,因为实体之间的关联关系配置是双向的,我们就需要 ToString 的时候把⼀⽅排除掉,如下所示:

第⼆种情况:在转化JSON的时候,双向关联也会死循环。按照我们上⾯讲的⽅法,这是时候我们要想到通过 @JsonIgnoreProperties(value={"address"}) 或者字段上⾯配置 @JsonIgnore,如下:

@JsonIgnore
private List<UserAddress> address;

此外,通过 @JsonBackReference 和 @JsonManagedReference 注解也可以解决死循环。

public class UserAddress {@JsonManagedReferenceprivate User user;
...}public class User implements Serializable {@OneToMany(mappedBy = "user",fetch = FetchType.LAZY)@JsonBackReferenceprivate List<UserAddress> address;
...}

如上述代码,也可以达到 @JsonIgnore 的效果,具体你可以⾃⼰操作⼀下试试,原理都是⼀样的,都是利⽤排除⽅法。那么接下来我们看下 HibernateModel5 是怎么使⽤的。

8.4.2 JPA 实体 JSON 序列化的常见报错及解决方法

我们在实际跑之前讲过的 user 对象,或者是类似带有 lazy 对象关系的时候,经常会遇到下⾯的错误:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.jpa.example1.entity.User$HibernateProxy$4u6Wef9i["hibernateLazyInitializer"])

这个时候该怎么办呢?下⾯介绍⼏个解决办法,第⼀个可以引⼊ Hibernate5Module。

解决⽅法⼀:引⼊ Hibernate5Module

代码如下:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Hibernate5Module());
String json = objectMapper.writeValueAsString(user);
System.out.println(json);

这样⼦就不会报错了。

Hibernate5Module ⾥⾯还有很多 Feature 配置,例如 FORCE_LAZY_LOADING,强制 lazy ⾥⾯加载就不会有上⾯的问题了。但是这个会有性能问题,我不建议使⽤。

还有 USE_TRANSIENT_ANNOTATION,利⽤ JPA 的 @Transient 注解配置,这个默认是开启的。所以基本上 feature 默认配置都是 ok 的,不需要我们动⼿,只要知道这回事就⾏了。

解决⽅法⼆:关闭 SerializationFeature.FAIL_ON_EMPTY_BEANS 的 feature

代码如下:

ObjectMapper objectMapper = new ObjectMapper();
// 直接关闭 SerializationFeature.FAIL_ON_EMPTY_BEANS
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
String json = objectMapper.writeValueAsString(user);
System.out.println(json);

因为是 lazy,所以 empty 的 bean 的时候不报错也可以。

解决⽅法三:对象上⾯排除“hibernateLazyInitializer”“handler”“fieldHandler”等

代码如下:

@JsonIgnoreProperties(value = {"address", "hibernateLazyInitializer", "handler", "fieldHandler"})
public class User implements Serializable {}

那有没有其他 ObjectMapper 的推荐配置了呢?

8.4.3 推荐的配置项

ObjectMapper objectMapper = new ObjectMapper();
// empty beans不需要报错,没有就是没有了
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 遇到不可识别字段的时候不要报错,因为前端传进来的字段不可信,可以不要影响正常业务逻辑
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 遇到不可以识别的枚举的时候,为了保证服务的强壮性,建议也不要关⼼未知的,甚⾄给个默认的,特别是微服务⼤家的枚举值随时在变,但是⽼的服务是不需要跟着⼀起变的
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true);
objectMapper.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true);

时间类型的最佳实践,如何返回 ISO 格式的标准时间

有的时候我们会发现,默认的 ObjectMapper ⾥⾯的 module 提供的时间转化格式可能不能满⾜我们的要求,可能要进⾏扩展,这里提供⼀个⾃定义 module 返回 ISO 标准时间格式的⼀个案例,如下:

@Test
@Rollback(false)
public void testUserJson2() throws JsonProcessingException {UserJson userJson = userJsonRepository.findById(1L).get();userJson.setOther(Maps.newHashMap("address", "shanghai"));//⾃定义 myInstant 解析序列化和反序列化 DateTimeFormatter.ISO_ZONED_DATE_TIME 这种格式SimpleModule myInstant = new SimpleModule("instant", Version.unknownVersion()).addSerializer(java.time.Instant.class, new JsonSerializer<Instant>() {@Overridepublic void serialize(java.time.Instant instant,JsonGenerator jsonGenerator,SerializerProvider serializerProvider)throws IOException {if (instant == null) {jsonGenerator.writeNull();} else {jsonGenerator.writeObject(instant.toString());}}}).addDeserializer(Instant.class, new JsonDeserializer<Instant>() {@Overridepublic Instant deserialize(JsonParser jsonParser,DeserializationContext deserializationContext) throws IOException {Instant result = null;String text = jsonParser.getText();if (!StringUtils.hasText(text)) {result = ZonedDateTime.parse(text,DateTimeFormatter.ISO_ZONED_DATE_TIME).toInstant();}return result;}});ObjectMapper objectMapper = new ObjectMapper();// 注册⾃定义的moduleobjectMapper.registerModule(myInstant);String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(userJson);System.out.println(json);
}

我们利⽤上⾯的 UserJson 案例,在测试⽤例⾥⾯⾃定义了 myInstant 来进⾏序列化和反序列化Instant这种类型,然后我们通过 objectMapper.registerModule(myInstant); 注册进去。那么我们看⼀下运⾏结果:

{"createDate" : "2022-03-20T01:46:49.466Z","email" : "123456@126.com","id" : 1,"updateDate" : "2022-03-20 09:46","my_name" : "jackxx","address" : "shanghai"
}

这时你会发现 createDate 的格式发⽣了变化,这样⼦的话,任何⼈看到我们这样的 JSON 结构就不必问我们到底是哪个时区的问题了。

8.5 本章小结

到这⾥,关于 Spring Data JPA 的基础知识也告⼀段落,这⼀课时详细讲解了 Jackson 的原理,分析了⼀下 JPA ⾥⾯经常会遇到的问题,并推荐了⼀些常⻅配置。有个需要注意的点就是双向关联关系,如果你暂时不得要领的话,我建议不要为了⽤⽽⽤,我们就按照 DB 的真实映射写法就可以,类似 MyBatis ⼀样,只不过不需要我们去关⼼和配置映射关系。

这⾥我还想说⼀个解题思路,就是当我们遇到问题的时候,要学着挖⼀挖问题的根源,这样解决问题才能够游刃有余。

Spring data JPA 之 Jackson 在实体里面的注解详解相关推荐

  1. Spring Data JPA 从入门到精通~Naming命名策略详解及其实践

    Naming 命名策略详解及其实践 用 JPA 离不开 @Entity 实体,我都知道实体里面有字段映射,而字段映射的方法有两种: 显式命名:在映射配置时,设置的数据库表名.列名等,就是进行显式命名, ...

  2. Spring Data JPA 从入门到精通~Auditing及其事件详解

    Auditing 及其事件详解 Auditing 翻译过来是审计和审核,Spring 的优秀之处在于帮我们想到了很多繁琐事情的解决方案,我们在实际的业务系统中,针对一张表的操作大部分是需要记录谁什么时 ...

  3. Spring Data JPA OneToMany级联,多方删除修改新增详解(好文章!!申精!!)

    前言 近期的项目中使用Spring Data JPA.JPA带来很大的便捷,但它内部映射关系及持久化机制如果理解不到位会出现很多问题.不同的配置将会产生不同的执行过程.如果不了解其运行机制,很容易在一 ...

  4. 终于有人把Spring Data JPA 讲明白了!

    01 什么是JPA? JPA的全称是 Java Persistence API , 中文的字面意思就是Java 的持久层 API , JPA 就是定义了一系列标准,让实体类和数据库中的表建立一个对应的 ...

  5. 使用Spring Data JPA进行分页和排序

    通过代码示例学习使用Spring Data JPA进行分页和排序 . 了解如何使用Springs PagingAndSortingRepository接口获取分页和排序结果. 1概述 在处理大量数据时 ...

  6. SpringBoot的Spring Data JPA配置

    配置文件加载的优先级顺序 项目根目录下config文件夹中的配置文件 项目根目录下的配置文件 resources目录下config文件夹中的配置文件 resources目录下的配置文件 注:相应的.y ...

  7. Spring Data Jpa 实体类自动创建数据库表失败解决

    先说一下我遇到的这个问题,首先我是通过maven创建了一个spring boot的工程,引入了Spring data jpa,结果实体类创建好之后,运行工程却没有在数据库中自动创建数据表. 找了半天发 ...

  8. Spring Data Jpa的JpaRepository的getOne()方法查询数据实体时报错could not initialize proxy

    问题描述:在使用Spring Data Jpa的JpaRepository的getOne()方法查询数据实体时报以下错误: org.hibernate.LazyInitializationExcept ...

  9. 对Spring Data JPA中的page对象下的content属性里的实体类对象转换为dto对象

    对Spring Data JPA中的page对象下的content属性里的实体类对象转换为dto对象. 刚开始试遍历content,进行转换,添加到新的list中,再set进去page.后来发现pag ...

最新文章

  1. 在bootstrap ace样式框架上修改的后台管理型模板(Tab页后台管理模板)
  2. 介绍KeyTool GUI工具2款
  3. 世界公认最好的记忆方法_全球公认最好的12个教育孩子的方法,值得每个家长收藏学习!...
  4. poj 1338 Ugly Numbers(丑数模拟)
  5. 浪潮世科和浪潮软件什么关系_社交图形浪潮
  6. Nagios添加主机监控失败-故障小结
  7. phpcmsV9 默认主题模板(templates文件目录结构)介绍
  8. iOS 13新增防骚扰功能,但开启后用户吐槽声一片
  9. 成龙表演、胡海泉当司仪、张柏芝送礼物,这场婚礼花销7千万 最后倒赚6千万!...
  10. 实现下拉菜单的宽度与登录人ID长度的匹配
  11. Windows Phone开发(14):数据模板 转:http://blog.csdn.net/tcjiaan/article/details/7350849...
  12. android多个app音量调节问题,「App 音量控制」依每个 App 的使用需求自动调整音量(Android)...
  13. 盖世无双之国产数据库风云榜-2022年02月
  14. java实现网站统计功能_网站访问量统计功能的实现
  15. 通过log查看camera sensor型号
  16. pandas并发处理数据神器Pandarallel的实战代码
  17. WordCAT 一款功能强大、免费共享的Word文档计算机辅助翻译软件
  18. [ CTF ]【天格】战队WriteUp-第六届“强网杯”全国安全挑战赛(初赛)
  19. 【大数据AI人工智能】图数据库的发展脉络与技术演进
  20. AD域控管理之授权普通用户或组管理计算机加入域和退出域的权限

热门文章

  1. Killer KCC(Killer control center/Killer Intelligence Center)安装失败问题解决 Windows 10 应用需要FAT32或ExFAT格式
  2. 解决Navicat连接不上MySQL的问题
  3. RAW格式 RAW RGB格式
  4. java实现中国象棋2:移动棋子
  5. v7000存储硬盘离线如何恢复数据
  6. using and configure emule (amule) on ubuntu
  7. 深度技术Ghost Win7 SP1装机旗舰版2012.11
  8. ble 低功耗蓝牙开发学习 嵌入式交流学习
  9. Prince2 简介
  10. c 调用python类_C调用Python解析Excel