ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Hibernate5 与 Spring Boot2 最佳性能实践(1)

2021-05-30 10:57:16  阅读:222  来源: 互联网

标签:Hibernate5 Hibernate SpringBoot 示例 Spring Boot2 github https com


1. 通过字节码增强实现属性延迟加载


默认情况下,实体的属性是立即加载的,即一次加载所有属性。你确定这是你想要的吗?


"描述:"即使目前没有这样的需求,了解可以延迟加载属性也很重要。通过 Hibernate 字节码插装或者 subentities 也可以实现。该特性对于存储了大量 `CLOB`、`BLOB`、`VARBINARY` 类型数据时非常有用。


> 译注:字节码增强(Bytecode enhancement)与字节码插装(Bytecode instrumentation)的区别。字节码增强分在线、离线两种模式。在线模式指在运行时执行,持久化类在加载时得到增强;离线模式指在编译后的步骤中进行增强。字节码插装,指在“运行时”向 Java 类加入字节码。实际上不是在运行时,而是在 Java 类的“加载”过程中完成。


技术要点


  • 在 Maven `pom.xml` 中激活 Hibernate 字节码插装(像下面这样使用 Maven 字节码增强插件)

  • 为需要延迟加载的列标记 `@Basic(fetch = FetchType.LAZY)`

  • 在 View 中禁用 Open Session


[示例代码][1]


[1]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootAttributeLazyLoadingBasic


2. 通过 Log4J 2 查看绑定参数


开发中,如果不能监测调用的SQL语句绑定的参数,很有可能造成潜在的性能损失(例如 N+1 问题)。


> 译注:“N+1 问题”即执行一次查询 N 条主数据后,由于关联引起的 N 次从数据查询,因此会带来了性能问题。一般来说,通过延迟加载可以部分缓解 N+1 带来的性能问题。


"更新:"如果项目中"已经"配置了 Log4J 2,可以采用以下方案。如果没有配置,建议使用 `TRACE`(感谢 Peter Wippermann 的建议)或 `log4jdbc`(感谢 Sergei Poznanski 的建议以及 [SO][2] 的答案)。这两种方案不需要取消默认 Spring Boot 日志功能。使用 `TRACE` 的例子参见[这里][3],`log4jdbc` 的示例参见[这里][4]。 


[2]:https://stackoverflow.com/questions/45346905/how-to-log-sql-queries-their-parameters-and-results-with-log4jdbc-in-spring-boo/45346996#45346996

[3]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootLogTraceViewBindingParameters

[4]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootLog4JdbcViewBindingParameters


"基于 Log4J 2 方案:"最好的办法还是监视SQL语句绑定的参数,可以通过 Log4J 2 logger 设置。


技术要点


  • 在 Maven `pom.xml` 中移除默认 Spring Boot 日志依赖(参考上面的更新说明)

  • 在 Maven `pom.xml` 中加入 Log4j 2 依赖

  • 在 `log4j2.xml` 中添加以下配置:


```xml
<Logger name="org.hibernate.type.descriptor.sql" level="trace"/>
```


示例输出


图片


[示例代码][5]


[5]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootLog4j2ViewBindingParameters


3.如何通过 datasource-proxy 监视查询细节


如果无法保证批处理正常工作,很容易会遇到严重的性能损失。即使已经配置了批处理并且认为会在后台运行,还是有一些情况会造成批处理被禁用。为了确保这一点,可以使用 `hibernate.generate_statistics` 显示详细信息(包括批处理细节),也可以使用 datasource-proxy。


"描述:"通过 [datasource-proxy][6] 查看查询细节(包括查询类型、绑定参数、批处理大小等)。


[6]:https://github.com/ttddyy/datasource-proxy


技术要点


  • 在 Maven `pom.xml` 中加入 `datasource-proxy` 依赖

  • 为 `DataSource` bean 创建 Post Processor 进行拦截

  • 用 `ProxyFactory` 和 `MethodInterceptor` 实现包装 `DataSource` bean

 

示例输出


图片


[示例代码] [here][7]


[7]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDataSourceProxy


4. 通过 saveAll(Iterable<S> entities) 在 MySQL(或其他 RDBMS)中执行批量插入


默认情况下,100次插入会生成100个 `INSERT` 语句,带来100个数据库行程开销。


"描述:"批处理机制对 `INSERT`、`UPDATE` 和 `DELETE` 进行分组,能够显著降低数据库行程数。批处理插入可以调用 `SimpleJpaRepository#saveAll(Iterable<S> entities)` 方法,下面是在 MySQL 中的应用步骤。


技术要点


  • 在 `application.properties` 中设置 `spring.jpa.properties.hibernate.jdbc.batch_size`

  • 在 `application.properties`中设置 `spring.jpa.properties.hibernate.generate_statistics`:检查批处理是否正常工作

  • 在 `application.properties` JDBC URL 中设置 `rewriteBatchedStatements=true`:针对 MySQL 优化

  • 在 `application.properties` JDBC URL 中设置 `cachePrepStmts=true`:启用缓存。启用 prepStmtCacheSize、prepStmtCacheSqlLimit 等参数前必须设置此参数

  • In `application.properties` JDBC URL 中设置 `useServerPrepStmts=true`:切换到服务端生成预处理语句,可能会带来显著性能提升

  • 在实体类中使用 [assigned generator][8]:MySQL `IDENTITY` 会禁用批处理

  • 在实体类中为 `Long` 属性添加 `@Version` 注解:不仅可以避免批处理生成额外的 `SELECT`,还能减少多个请求事务中丢失 update。使用 `merge()` 替代 `persist()` 时会生成额外的 `SELECT`。`saveAll()` 实际调用 `save()`,如果实体对象ID非空会被看作已有对象。这时调用 `merge()` 触发 Hibernate 生成 `SELECT` 检查数据库中是否存在相同标识

  • 注意:传入 `saveAll()` 的对象数量不要“覆盖“持久化上下文。通常情况下,`EntityManager` 会定期执行 flush 和 clear,但是 `saveAll()` 执行过程中不会。因此,如果 `saveAll()` 传入了大量数据,所有数据都会命中持久化上下文(1级缓存),并一直保持直到执行 flush 操作。这里的配置适用于规模较小的数据,对于大数据的情况请参考例5


[8]:https://vladmihalcea.com/how-to-combine-the-hibernate-assigned-generator-with-a-sequence-or-an-identity-column/


示例输出



[示例代码][9]


[9]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertsJpaRepository


5. 通过 EntityManager 在 MySQL(或其他 RDBMS)中执行批量插入


批处理可以提高性能,但是在执行 flush 前需要关注持久化上下文中的数据量。在内存中存储大量数据会导致性能下降,例4中的方法只适合数据量相对较少的情况。


"描述:"通过 `EntityManager` 在 MySQL(或其他 RDBMS)中执行批量插入。这种方法可以更好地控制持久化上下文(1级缓存) `flush()` 和 `clear()` 操作。Spring Boot 中 `saveAll(Iterable<S>entities)` 做不到这点。其它好处,可以调用 `persist()` 而不是 `merge()` 方法,Spring Boot `saveAll(Iterable< S>entities)` 与 `save(S entity)` 默认调用前者。


技术要点


  • 在 `application.properties` 中设置 `spring.jpa.properties.hibernate.jdbc.batch_size`

  • 在 `application.properties` 中设置 `spring.jpa.properties.hibernate.generate_statistics`:检查批处理是否正常工作

  • 在 `application.properties` JDBC URL 中设置  `rewriteBatchedStatements=true`:针对 MySQL 优化

  • 在 `application.properties` JDBC URL 中设置 `withcachePrepStmts=true`:启用缓存。启用 prepStmtCacheSize、prepStmtCacheSqlLimit 等参数前必须设置此参数

  • 在 `application.properties` JDBC URL 中设置 `withuseServerPrepStmts=true`:切换到服务端生成预处理语句,可能会带来显著性能提升

  • 在实体类中使用 [assigned generator][8]:MySQL `IDENTITY` 会禁用批处理

  • 在 DAO 中定期对持久化上下文执行 flush 和 clear,避免“覆盖“持久化上下文


示例输出


图片


[示例代码][10]


[10]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertsEntityManager


你可能也会对下面内容感兴趣


  • [6. 如何在 MySQL 中通过 JpaContext/EntityManager 执行批量插入][11]"

  • [7. 在 MySQL 中实现 Session 级批处理(Hibernate 5.2 或更高版本)][12]"


[11]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertsEntityManagerViaJpaContext

[12]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchInsertsViaSession


8. 通过 Spring Data/EntityManager/Session 直接获取结果


从数据库获取数据的方式决定了应用的执行效率,要优化查询必须了解每种获取数据方法的特点。在了解实体类'主键'的情况下,*直接获取*是最简单且实用的办法。


"描述:"下面是使用 Spring Data、`EntityManager` 和 Hibernate `Session` 直接获取数据的示例:


技术要点


  • 通过 Spring Data 直接获取数据,调用 `findById()`

  • 通过 `EntityManager#find()` 直接获取数据

  • 通过 Hibernate `Session#get()` 直接获取数据


[示例代码][13]


[13]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDirectFetching


9. 通过 Spring Data Projection 实现 DTO


获取超出需要的数据是导致性能下降的常见问题之一。不仅如此,得到实体后不做修改也是一样。


"描述:"通过 Spring Data Projection(DTO)从数据库只获取必须的数据。也可以查看例子25至32。


技术要点


  • 编写接口(projection),包含数据库所需数据表指定列的 getter 方法

  • 编写返回 `List<projection>` 的查询

  • 可能的话,要限制返回的行数(例如,通过 `LIMIT`)。这个例子中,使用了 Spring Data repository 的内置 query builder 机制


示例输出(选择前2列,只获取 "name" 和 "age")


图片


[示例代码][14]


[14]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoViaProjections


10. 如何在 MySQL 中存储 UTC 时区


在数据库中存储不同格式或指定格式的日期、时间和时间戳会带来日期转换问题。


"描述:" 这个例子展示了如何在 MySQL 中以 UTC 时区存储日期、时间和时间戳。对其他 RDBMS(例如 PostgreSQL),只要移除 `useLegacyDatetimeCode=false` 对应调整 JDBC URL 即可。


技术要点


  • `spring.jpa.properties.hibernate.jdbc.time_zone=UTC`

  • `spring.datasource.url=jdbc:mysql://localhost:3306/db_screenshot?useLegacyDatetimeCode=false`


[示例代码] [here][15]


> 译注:运行时修改示例 url 为 jdbc:mysql://localhost:3306/db_screenshot?createDatabaseIfNotExist=true&useLegacyDatetimeCode=false,设置参数 spring.jpa.hibernate.ddl-auto=create


[15]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootUTCTimezone


11. 通过 Proxy 得到父实体


执行的 SQL 越多,性能损失越大。尽可能减少执行的 SQL 数量非常重要,通过 Reference 是最易于使用的优化方法。


"描述:"`Proxy` 在子实体可以通过指向父实体的一个持久化引用表示时非常有用。这种情况下,执行`SELECT` 语句从数据库获得父实体会带来性能损失且没有意义。Hibernate 能够对未初始化的 `Proxy` 设置基础外键值。


技术要点


  • 底层依赖 `EntityManager#getReference()`

  • 在 Spring 中调用 `JpaRepository#getOne()`

  • 在这个示例中,使用了 Hibernate `load()` 方法

  • 示例中有 `Tournament` 和 `TennisPlayer` 两个实例,一个 tournament 包含多个 player(`@OneToMany`)

  • 通过 `Proxy` 获取 tournament 对象(不会触发 `SELECT`),接着创建一个 TennisPlayer 对象,把 `Proxy` 设为 player 的 tournament,最后保存 player(触发 `INSERT` 操作,在 tennis player 中插入 `tennis_player`)


示例输出


命令行只输出一条 `INSERT`,没有 `SELECT` 语句。


[示例代码][16]


[16]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootPopulatingChildViaProxy


12. N+1 问题


“N+1问题”可能造成严重的性能损失。减少损失的首要任务是定位问题。


N+1 本质上是一个延迟加载问题(预先加载也不例外)。缺乏对实际执行SQL进行监测,很可能会造成 N+1 问题,最好的解决办法是 JOIN+DTO(例36至例42)。


技术要点


  • 定义 `Category` 和 `Product` 两类实体,关系为 `@OneToMany`

  • 延迟加载 `Product`,不主动加载 `Category`(只生成1条查询)

  • 循环读取 `Product` 集合, 对每个产品获取 `Category`(生成N条查询)


示例输出


图片


[示例代码][17]


[17]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootSimulateNPlus1


13. 通过 HINT_PASS_DISTINCT_THROUGH 优化 Distinct SELECT


把 `SELECT DISTINCT` 传递给 RDBMS 会[影响性能][18]。


[18]:http://in.relation.to/2016/08/04/introducing-distinct-pass-through-query-hint/


"描述:" Hibernate 5.2.2 开始,可以通过 `HINT_PASS_DISTINCT_THROUGH` 优化 `SELECT DISTINCT`。不会把 `DISTINCT` 关键字传给 RDBMS,而是由 Hibernate 删除重复数据。


技术要点


  • 使用 `@QueryHints(value = @QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = "false"))`


示例输出


图片


[示例代码][19]


[19]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootHintPassDistinctThrough


14. 启用脏数据跟踪


Java 反射执行速度慢,通常被看作性能损失。


"描述:"Hibernate 5 之前,脏数据检查机制基于 Java Reflection API。自 Hibernate 5 开始,转而采用了**字节码增强**技术。后者的性能更好,实体数量较多时效果尤其明显。


技术要点


  • 在 `pom.xml` 中增加插件配置(例如,使用 Maven bytecode enhancement 插件)


示例输出



  • 字节码增强效果可以在 `User.class` 上[看到][20]


[示例代码][21]


[20]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/blob/master/HibernateSpringBootEnableDirtyTracking/Bytecode%20Enhancement%20User.class/User.java

[21]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootEnableDirtyTracking


15. 在实体和查询上使用 Java 8 Optional


把 Java 8 `Optional` 作为处理 `null` 的“银弹”可能弊大于利,最好的方式还是按照设计的意图使用。


"描述:"下面的示例展示了如何在实体和查询中正确使用 Java 8 `Optional`。


技术要点


  • 使用 Spring Data 内建查询方法返回 `Optional`(例如 `findById()`)

  • 自己编写查询方法返回 `Optional`

  • 在实体 getter 方法中使用 `Optional`

  • 可以使用 `data-mysql.sql` 脚本验证不同场景


[示例代码][22]


[22]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootEnableDirtyTracking


16. 如何正确建立 @OneToMany 双向关系


实现 `@OneToMany` 双向关系有几个陷阱,相信你也希望一开始就能实现正确。


"描述:"下面的示例应用展示了如何正确实现 `@OneToMany` 双向关联。


技术要点


  • "总是"建立父子级联

  • 对父亲标记 `mappedBy`

  • 对父亲使用 `orphanRemoval`,移除没有引用的子对象

  • 在父节点上使用 helper 方法实现关联同步

  • "总是"使用延迟加载

  • 使用业务主键或实体标识符,参考[这篇介绍][23]覆写 `equals()` 和 `hashCode()` 方法。


[示例代码][24]


[23]:https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/

[24]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootOneToManyBidirectional


17. JPQL/HQL 查询数据


在不具备直接查询的情况下,可以考虑通过 JPQL/HQL 查询数据。


"描述:"下面的示例展示了如何通过 `JpaRepository`、`EntityManager` 和 `Session` 进行查询。


技术要点


  • 对 `JpaRepository` 使用 `@Query` 注解或者创建 Spring Data Query

  • 对 `EntityManager` 与 `Session` 使用 `createQuery()` 方法


[示例代码][25]


[25]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootQueryFetching


18. 避免在 MySQL 与 Hibernate 5 中使用 AUTO Generator 类型


在 MySQL 开发过程中,尽量避免使用 `TABLE` 生成器,最好[永远不要使用][26]。


[26]:https://vladmihalcea.com/why-you-should-never-use-the-table-identifier-generator-with-jpa-and-hibernate/


"描述:" 在使用 MySQL 和 Hibernate 5 开发时,`GenerationType.AUTO` 类型的生成器会调用 `TABLE` 生成器,造成严重的性能损失。可以通过 `GenerationType.IDENTITY` 调用 `IDENTITY` 生成器或者使用 *native* 生成器。


技术要点


  • 使用 `GenerationType.IDENTITY` 取代 `GenerationType.AUTO`

  • 使用[示例代码][27],调用  *native* 生成器


[27]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootAutoGeneratorType


示例输出


图片


[示例代码][28]


[28]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootAutoGeneratorType


19. 多余的 save() 调用


大家都喜欢使用 `save()`。由于 Hibernate 采用了脏数据检查机制避免多余调用,`save()` 对于托管实体并不适用。


"描述:" 下面的示例展示了对于托管实体调用 `save()` 方法是多余的。


技术要点


  • Hibernate 会为每个托管实体调用 `UPDATE` 语句,不需要显示调用 `save()` 方法

  • 多余的调用意味着性能损失(参见[这篇文章][29])


[示例代码][30]


[29]https://vladmihalcea.com/jpa-persist-and-merge/

[30]https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootRedundantSave


20. PostgreSQL (BIG)SERIAL 与批量插入


在 PostgreSQL 中,使用 `GenerationType.IDENTITY` 会禁用批量插入。


"描述:" `(BIG)SERIAL` 与 MySQL 的 `AUTO_INCREMENT` 功能“接近”。在这个示例中,我们通过 `GenerationType.SEQUENCE` 开启批量插入,同时通过 `hi/lo` 算法进行了优化。


技术要点


  • 使用 `GenerationType.SEQUENCE` 取代 `GenerationType.IDENTITY`

  • 通过 `hi/lo` 算法在一个数据库行程中完成多个标识符读取(还可以使用 Hibernate `pooled` 和 `pooled-lo` 标识符生成器,它们是 `hi/lo` 的改进版)


示例输出


图片


[示例代码][31]


[31]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootBatchingAndSerial


> 译注:示例中 `createDatabaseIfNotExist=true` 参数对 PostgreSQL 无效,需要手动创建 `db_users` 数据库。


21. JPA 继承之 Single Table


JPA 支持 `SINGLE_TABLE`、`JOINED` 和 `TABLE_PER_CLASS` 继承策略,有着各自优缺点。以 `SINGLE_TABLE` 为例,读写速度快但不支持对子类中的列设置 `NOT NULL`。


"描述:"下面的示例展示了 JPA Single Table 继承策略(`SINGLE_TABLE`)。


技术要点


  • 这是 JPA 默认的继承策略(`@Inheritance(strategy=InheritanceType.SINGLE_TABLE)`)

  • 所有继承结构中的类都会被映射到数据库中的单个表


示例输出(下面是四个实体得到的单个表)


图片


[示例代码][32]


[32]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootSingleTableInheritance


22. 如何对 SQL 语句统计和断言


如果不对 SQL 语句进行统计和断言,很容易对后台执行的 SQL 语句失去控制,进而造成性能损失。 


"描述:"下面的示例展示了如何对后台 SQL 语句进行统计和断言。统计 SQL 非常有用,能够确保不会生成多余的 SQL(例如,可以对预期的语句数量断言检测 N+1 问题)。


技术要点


  • 在 Maven `pom.xml` 中添加 `datasource-proxy` 依赖和 Vlad Mihalcea 的 `db-util`

  • 新建 `ProxyDataSourceBuilderwithcountQuery()`

  • `SQLStatementCountValidator.reset()` 重置计数

  • 通过 `assertInsert{Update/Delete/Select}Count(long expectedNumberOfSql` 对 `INSERT`、`UPDATE`、`DELETE` 和 `SELECT` 进行断言


示例输出(期望的 SQL 语句数量与实际生成的数量不一致时抛出异常)


图片


[示例代码][33]


[33]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootCountSQLStatements


23. 如何使用 JPA 回调


为实体绑定事件处理时,记得使用 JPA 内建回调,不要重新发明轮子。


"描述:"下面的示例展示了如何启用 JPA 回调(`Pre/PostPersist`、`Pre/PostUpdate`、`Pre/PostRemove` 和 `PostLoad`)。


技术要点


  • 在实体中编写回调方法并挑选合适的注解

  • Bean Class 中带注解的回调方法返回类型必须为 `void` 且不带参数


示例输出


图片


[示例代码][34]


[34]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootJpaCallbacks


24. @OneToOne 与 @MapsId


双向 `@OneToOne` 效率不及单向 `@OneToOne`,后者与父表共享主键。


"描述:" 下面的示例展示了为何建议使用 `@OneToOne` 和 `@MapsId` 取代 `@OneToOne`。


技术要点


  • 在子实体上使用 `@MapsId`

  • 对于 `@OneToOne` 关联,基本上会与父表共享主键。


[示例代码][35]


[35]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootOneToOneMapsId


25. 通过 SqlResultSetMapping 设置 DTO


超出需要获取数据是不好的习惯。另一种常见的错误,没有打算修改实体对象却获取并存储到持久化上下文中,同样会导致性能问题。例25至例32展示了如何使用不同方法提取 DTO。


"描述:"下面的示例展示了如何通过 `SqlResultSetMapping` 和 `EntityManager` 使用 DTO 提取需要的数据。


技术要点


  • 使用 `SqlResultSetMapping` 和 `EntityManager`

  • 使用 Spring Data Projection 时,请检查例9中的注意事项


[示例代码][36]


[36]:https://github.com/AnghelLeonard/Hibernate-SpringBoot/tree/master/HibernateSpringBootDtoSqlResultSetMapping


标签:Hibernate5,Hibernate,SpringBoot,示例,Spring,Boot2,github,https,com
来源: https://blog.51cto.com/u_15127686/2832723

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有