热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Hibernate延迟加载深入分析-集合属性的延迟加载策略

本文深入分析了Hibernate延迟加载的机制,特别是集合属性的延迟加载策略。通过延迟加载,可以降低系统的内存开销,提高Hibernate的运行性能。对于集合属性,推荐使用延迟加载策略,即在系统需要使用集合属性时才从数据库装载关联的数据,避免一次加载所有集合属性导致性能下降。

Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。Hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hibernate 的运行性能。

下面先来剖析 Hibernate 延迟加载的“秘密”。

集合属性的延迟加载

当 Hibernate 从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里包含十万,甚至百万的记录,在初始化持久化实体的同时, 完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加 载所有的集合属性。

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。

例如下面 Person 类持有一个集合属性,该集合属性里的元素的类型为 Address,该 Person 类的代码片段如下:


清单 1. Person.java

Displaycode代码  收藏代码
  1.                
  2. public class Person   
  3. {   
  4. // 标识属性  
  5. private Integer id;   
  6. // Person 的 name 属性  
  7. private String name;   
  8. // 保留 Person 的 age 属性  
  9. private int age;   
  10. // 使用 Set 来保存集合属性  
  11. private Set
     addresses = new HashSet
    ();   
  12. // 下面省略了各属性的 setter 和 getter 方法  
  13. ...   
  14. }   

 

为了让 Hibernate 能管理该持久化类的集合属性,程序为该持久化类提供如下映射文件:


清单 2. Person.hbm.xml

Displaycode代码  收藏代码
  1.                    
  2.  "1.0" encoding="GBK"?>   
  3.  
  4. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  5. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">   
  6.  "org.crazyit.app.domain">   
  7.     
  8.  "Person" table="person_inf">   
  9.     
  10.  "id" column="person_id">   
  11.     
  12.  "identity"/>   
  13.     
  14.     
  15.  "name" type="string"/>   
  16.  "age" type="int"/>   
  17.     
  18.  "addresses" table="person_address" lazy="true">   
  19.     
  20.  "person_id"/>   
  21.  "Address">   
  22.     
  23.  "detail"/>   
  24.     
  25.  "zip"/>   
  26.     
  27.     
  28.     
  29.     

 

从上面映射文件的代码可以看出,Person 的集合属性中的 Address 类只是一个普通的 POJO。该 Address 类里包含 detail、zip 两个属性。由于 Address 类代码非常简单,故此处不再给出该类的代码。

上面映射文件中 元素里的代码指定了 lazy="true"(对于 元素来说,lazy="true"是默认值),它指定 Hibernate 会延迟加载集合属性里 Address 对象。

例如通过如下代码来加载 ID 为 1 的 Person 实体:

Displaycode代码  收藏代码
  1. Session session = sf.getCurrentSession();   
  2. Transaction tx = session.beginTransaction();   
  3. Person p &#61; (Person) session.get(Person.class, 1);  //<1>   
  4. System.out.println(p.getName());   

 

上面代码只是需要访问 ID 为 1 的 Person 实体&#xff0c;并不想访问这个 Person 实体所关联的 Address 对象。此时有两种情况&#xff1a;

  • 如果不延迟加载&#xff0c;Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 Address 对象。
  • 如果采用延迟加载&#xff0c;Hibernate 就只加载 Person 实体对应的数据记录。

很明显&#xff0c;第二种做法既能减少与数据库的交互&#xff0c;而且避免了装载 Address 实体带来的内存开销——这也是 Hibernate 默认启用延迟加载的原因。

现在的问题是&#xff0c;延迟加载到底是如何实现的呢&#xff1f; Hibernate 在加载 Person 实体时&#xff0c;Person 实体的 addresses 属性值是什么呢&#xff1f;

为了解决这个问题&#xff0c;我们在 <1>号代码处设置一个断点&#xff0c;在 Eclipse 中进行 Debug&#xff0c;此时可以看到 Eclipse 的 Console 窗口有如图 1 所示的输出&#xff1a;


图 1. 延迟加载集合属性的 Console 输出
图 1. 延迟加载集合属性的 Console 输出 

正如图 1 输出所看到的&#xff0c;此时 Hibernate 只从 Person 实体对应的数据表中抓取数据&#xff0c;并未从 Address 对象对应的数据表中抓取数据&#xff0c;这就是延迟加载。

那么 Person 实体的 addresses 属性是什么呢&#xff1f;此时可以从 Eclipse 的 Variables 窗口看到如图 2 所示的结果&#xff1a;


图 2. 延迟加载的集合属性值
图 2. 延迟加载的集合属性值 

从图 2 的方框里的内容可以看出&#xff0c;这个 addresses 属性并不是我们熟悉的 HashSet、TreeSet 等实现类&#xff0c;而是一个 PersistentSet 实现类&#xff0c;这是 Hibernate 为 Set 接口提供的一个实现类。

PersistentSet 集合对象并未真正抓取底层数据表的数据&#xff0c;因此自然也无法真正去初始化集合里的 Address 对象。不过 PersistentSet 集合里持有一个 session 属性&#xff0c;这个 session 属性就是 Hibernate Session&#xff0c;当程序需要访问 PersistentSet 集合元素时&#xff0c;PersistentSet 就会利用这个 session 属性去抓取实际的 Address 对象对应的数据记录。

那么到底抓取那些 Address 实体对应的数据记录呢&#xff1f;这也难不倒 PersistentSet&#xff0c;因为 PersistentSet 集合里还有一个 owner 属性&#xff0c;该属性就说明了 Address 对象所属的 Person 实体&#xff0c;Hibernate 就会去查找 Address 对应数据表中外键值参照到该 Person 实体的数据。

例如我们单击图 2 所示窗口中 addresses 行&#xff0c;也就是告诉 Eclipse 要调试、输出 addresses 属性&#xff0c;这就是要访问 addresses 属性了&#xff0c;此时就可以在 Eclipse 的 Console 窗口看到输出如下 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     addresses0_.person_id as person1_0_0_,   
  3.     addresses0_.detail as detail0_,   
  4.     addresses0_.zip as zip0_   
  5. from   
  6.     person_address addresses0_   
  7. where   
  8.     addresses0_.person_id&#61;?   

 

这就是 PersistentSet 集合跟据 owner 属性去抓取特定 Address 记录的 SQL 语句。此时可以从 Eclipse 的 Variables 窗口看到图 3 所示的输出&#xff1a;


图 3. 已加载的集合属性值
图 3. 已加载的集合属性值 

从图 3 可以看出&#xff0c;此时的 addresses 属性已经被初始化了&#xff0c;集合里包含了 2 个 Address 对象&#xff0c;这正是 Person 实体所关联的两个 Address 对象。

通过上面介绍可以看出&#xff0c;Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类。在延迟加载时&#xff0c;开始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 会持有一个 Hibernate Session&#xff0c;它可以保证当程序需要访问该集合时“立即”去加载数据记录&#xff0c;并装入集合元素。

与 PersistentSet 实现类类似的是&#xff0c;Hibernate 还提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等实现类&#xff0c;它们的功能与 PersistentSet 的功能大致类似。

熟悉 Hibernate 集合属性读者应该记得&#xff1a;Hibernate 要求声明集合属性只能用 Set、List、Map、SortedSet、SortedMap 等接口&#xff0c;而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等实现类&#xff0c;其原因就是因为 Hibernate 需要对集合属性进行延迟加载&#xff0c;而 Hibernate 的延迟加载是依靠 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 来完成的——也就是说&#xff0c;Hibernate 底层需要使用自己的集合实现类来完成延迟加载&#xff0c;因此它要求开发者必须用集合接口、而不是集合实现类来声明集合属性。

Hibernate 对集合属性默认采用延迟加载&#xff0c;在某些特殊的情况下&#xff0c;为 等元素设置 lazy&#61;"false"属性来取消延迟加载。

回页首

关联实体的延迟加载

默认情况下&#xff0c;Hibernate 也会采用延迟加载来加载关联实体&#xff0c;不管是一对多关联、还是一对一关联、多对多关联&#xff0c;Hibernate 默认都会采用延迟加载。

对于关联实体&#xff0c;可以将其分为两种情况&#xff1a;

  • 关联实体是多个实体时&#xff08;包括一对多、多对多&#xff09;&#xff1a;此时关联实体将以集合的形式存在&#xff0c;Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、 PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。
  • 关联实体是单个实体时&#xff08;包括一对一、多对一&#xff09;&#xff1a;当 Hibernate 加载某个实体时&#xff0c;延迟的关联实体将是一个动态生成代理对象。

当关联实体是单个实体时&#xff0c;也就是使用 映射关联实体的情形&#xff0c;这两个元素也可通过 lazy 属性来指定延迟加载。

下面例子把 Address 类也映射成持久化类&#xff0c;此时 Address 类也变成实体类&#xff0c;Person 实体与 Address 实体形成一对多的双向关联。此时的映射文件代码如下&#xff1a;


清单 3. Person.hbm.xml

Displaycode代码  收藏代码
  1.                    
  2.  "1.0" encoding&#61;"GBK"?>   
  3.     
  4.  
  5. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  6. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">   
  7.  "org.crazyit.app.domain">   
  8.     
  9.  "Person" table&#61;"person_inf">   
  10.     
  11.  "id" column&#61;"person_id">   
  12.     
  13.  "identity"/>   
  14.     
  15.     
  16.  "name" type&#61;"string"/>   
  17.  "age" type&#61;"int"/>   
  18.     
  19.  "addresses" inverse&#61;"true">   
  20.     
  21.  "person_id"/>   
  22.     
  23.  "Address"/>   
  24.     
  25.     
  26.   
  27.     
  28.  "Address" table&#61;"address_inf">   
  29.     
  30.  "addressId" column&#61;"address_id">   
  31.     
  32.  "identity"/>   
  33.     
  34.     
  35.  "detail"/>   
  36.     
  37.  "zip"/>   
  38.     
  39.  "person" class&#61;"Person"  
  40.  column&#61;"person_id" not-null&#61;"true"/>   
  41.     
  42.     

 

接下来程序通过如下代码片段来加载 ID 为 1 的 Person 实体&#xff1a;

Displaycode代码  收藏代码
  1. // 打开上下文相关的 Session   
  2. Session session &#61; sf.getCurrentSession();   
  3. Transaction tx &#61; session.beginTransaction();   
  4. Address address &#61; (Address) session.get(Address.class , 1); //<1>   
  5. System.out.println(address.getDetail());   

 

为了看到 Hibernate 加载 Address 实体时对其关联实体的处理&#xff0c;我们在 <1>号代码处设置一个断点&#xff0c;在 Eclipse 中进行 Debug&#xff0c;此时可以看到 Eclipse 的 Console 窗口输出如下 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     address0_.address_id as address1_1_0_,   
  3.     address0_.detail as detail1_0_,   
  4.     address0_.zip as zip1_0_,   
  5.     address0_.person_id as person4_1_0_   
  6. from   
  7.     address_inf address0_   
  8. where   
  9.     address0_.address_id&#61;?   

 

从这条 SQL 语句不难看出&#xff0c;Hibernate 加载 Address 实体对应的数据表抓取记录&#xff0c;并未从 Person 实体对应的数据表中抓取记录&#xff0c;这是延迟加载发挥了作用。

从 Eclipse 的 Variables 窗口看到如图 4 所示的输出&#xff1a;


图 4. 延迟加载的实体
图 4. 延迟加载的实体 

从图 4 可以清楚地看到&#xff0c;此时 Address 实体所关联的 Person 实体并不是 Person 对象&#xff0c;而是一个 Person_$$_javassist_0 类的实例&#xff0c;这个类是 Hibernate 使用 Javassist 项目动态生成的代理类——当 Hibernate 延迟加载关联实体时&#xff0c;将会采用 Javassist 生成一个动态代理对象&#xff0c;这个代理对象将负责代理“暂未加载”的关联实体。

只要应用程序需要使用“暂未加载”的关联实体&#xff0c;Person_$$_javassist_0 代理对象会负责去加载真正的关联实体&#xff0c;并返回实际的关联实体——这就是最典型的代理模式。

单击图 4 所示 Variables 窗口中的 person 属性&#xff08;也就是在调试模式下强行使用 person 属性&#xff09;&#xff0c;此时看到 Eclipse 的 Console 窗口输出如下的 SQL 语句&#xff1a;

Displaycode代码  收藏代码
  1. select   
  2.     person0_.person_id as person1_0_0_,   
  3.     person0_.name as name0_0_,   
  4.     person0_.age as age0_0_   
  5. from   
  6.     person_inf person0_   
  7. where   
  8.     person0_.person_id&#61;?   

 

上面 SQL 语句就是去抓取“延迟加载”的关联实体的语句。此时可以看到 Variables 窗口输出图 5 所示的结果&#xff1a;


图 5. 已加载的实体
图 5. 已加载的实体 

Hibernate 采用“延迟加载”管理关联实体的模式&#xff0c;其实就在加载主实体时&#xff0c;并未真正去抓取关联实体对应数据&#xff0c;而只是动态地生成一个对象作为关联实体的代理。当应用程序真正需要使用关联实体时&#xff0c;代理对象会负责从底层数据库抓取记录&#xff0c;并初始化真正的关联实体。

在 Hibernate 的延迟加载中&#xff0c;客户端程序开始获取的只是一个动态生成的代理对象&#xff0c;而真正的实体则委托给代理对象来管理——这就是典型的代理模式。

回页首

代理模式

代理模式是一种应用非常广泛的设计模式&#xff0c;当客户端代码需要调用某个对象时&#xff0c;客户端实际上也不关心是否准确得到该对象&#xff0c;它只要一个能提供该功能的对象即可&#xff0c;此时我们就可返回该对象的代理&#xff08;Proxy&#xff09;。

在这种设计方式下&#xff0c;系统会为某个对象提供一个代理对象&#xff0c;并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下&#xff0c;客户端代码不想或不能够直接调用被调用者&#xff0c;代理对象可以在客户和目标对象之间起到中介的作用。

对客户端而言&#xff0c;它不能分辨出代理对象与真实对象的区别&#xff0c;它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象&#xff0c;客户端代码面向接口编程&#xff0c;它仅仅持有一个被代理对象的接口。

总而言之&#xff0c;只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因&#xff0c;比如需要创建一个系统开销很大的对象&#xff0c;或者被调用对象在远程主机上&#xff0c;或者目标对象的功能还不足以满足需求……&#xff0c;而是额外创建一个代理对象返回给客户端使用&#xff0c;那么这种设计方式就是代理模式。

下面示范一个简单的代理模式&#xff0c;程序首先提供了一个 Image 接口&#xff0c;代表大图片对象所实现的接口&#xff0c;该接口代码如下&#xff1a;


清单 3. Image.java

Displaycode代码  收藏代码
  1.                
  2. public interface Image   
  3. {   
  4. void show();   
  5. }   

 

该接口提供了一个实现类&#xff0c;该实现类模拟了一个大图片对象&#xff0c;该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码。


清单 4. BigImage.java

Displaycode代码  收藏代码
  1.                
  2. // 使用该 BigImage 模拟一个很大图片  
  3. public class BigImage implements Image   
  4. {   
  5. public BigImage()   
  6. {   
  7. try   
  8. {   
  9. // 程序暂停 3s 模式模拟系统开销   
  10.              Thread.sleep(3000);   
  11. System.out.println("图片装载成功 ...");   
  12. }   
  13. catch (InterruptedException ex)   
  14. {   
  15. ex.printStackTrace();   
  16. }   
  17. }   
  18. // 实现 Image 里的 show() 方法  
  19. public void show()   
  20. {   
  21. System.out.println("绘制实际的大图片");   
  22. }   
  23. }   

 

上面的程序代码暂停了 3s&#xff0c;这表明创建一个 BigImage 对象需要 3s 的时间开销——程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式&#xff0c;当程序中创建 BigImage 时&#xff0c;系统将会产生 3s 的延迟。为了避免这种延迟&#xff0c;程序为 BigImage 对象提供一个代理对象&#xff0c;BigImage 类的代理类如下所示。


清单 5. ImageProxy.java

Displaycode代码  收藏代码
  1.                
  2. public class ImageProxy implements Image   
  3. {   
  4. // 组合一个 image 实例&#xff0c;作为被代理的对象  
  5. private Image image;   
  6. // 使用抽象实体来初始化代理对象  
  7. public ImageProxy(Image image)   
  8. {   
  9. this.image &#61; image;   
  10. }   
  11. /**   
  12. * 重写 Image 接口的 show() 方法  
  13. * 该方法用于控制对被代理对象的访问&#xff0c;  
  14. * 并根据需要负责创建和删除被代理对象  
  15. */   
  16. public void show()   
  17. {   
  18. // 只有当真正需要调用 image 的 show 方法时才创建被代理对象  
  19. if (image &#61;&#61; null)   
  20. {   
  21.  image &#61; new BigImage();   
  22.  }   
  23. image.show();   
  24. }   
  25. }   

 

上面的 ImageProxy 代理类实现了与 BigImage 相同的 show() 方法&#xff0c;这使得客户端代码获取到该代理对象之后&#xff0c;可以将该代理对象当成 BigImage 来使用。

在 ImageProxy 类的 show() 方法中增加了控制逻辑&#xff0c;这段控制逻辑用于控制当系统真正调用 image 的 show() 时&#xff0c;才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象&#xff0c;但程序并不是直接返回 BigImage 实例&#xff0c;而是先返回 BigImage 的代理对象&#xff0c;如下面程序所示。


清单 6. BigImageTest.java

Displaycode代码  收藏代码
  1.                
  2. public class BigImageTest   
  3. {   
  4. public static void main(String[] args)   
  5. {   
  6. long start &#61; System.currentTimeMillis();   
  7. // 程序返回一个 Image 对象&#xff0c;该对象只是 BigImage 的代理对象  
  8. Image image &#61; new ImageProxy(null);   
  9. System.out.println("系统得到 Image 对象的时间开销 :" &#43;   
  10. (System.currentTimeMillis() - start));   
  11. // 只有当实际调用 image 代理的 show() 方法时&#xff0c;程序才会真正创建被代理对象。  
  12. image.show();   
  13. }   
  14. }   

 

上面程序初始化 image 非常快&#xff0c;因为程序并未真正创建 BigImage 对象&#xff0c;只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时&#xff0c;程序需要真正调用 BigImage 对象的 show() 方法&#xff0c;程序此时才真正创建 BigImage 对象。运行上面程序&#xff0c;看到如图 6 所示的结果。


图 6. 使用代理模式提高性能
图 6. 使用代理模式提高性能 

看到如图 6 所示的运行结果&#xff0c;读者应该能认同&#xff1a;使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问&#xff1a;程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊&#xff0c;系统开销并未真正减少啊&#xff1f;只是这种系统开销延迟了而已啊&#xff1f;

我们可以从如下两个角度来回答这个问题&#xff1a;

  • 把创建 BigImage 推迟到真正需要它时才创建&#xff0c;这样能保证前面程序运行的流畅性&#xff0c;而且能减少 BigImage 在内存中的存活时间&#xff0c;从宏观上节省了系统的内存开销。
  • 有些情况下&#xff0c;也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下&#xff0c;使用代理模式可以显著地提高系统运行性能。

与此完全类似的是&#xff0c;Hibernate 也是通过代理模式来“推迟”加载关联实体的时间&#xff0c;如果程序并不需要访问关联实体&#xff0c;那程序就不会去抓取关联实体了&#xff0c;这样既可以节省系统的内存开销&#xff0c;也可以缩短 Hibernate 加载实体的时间。

回页首

小结

Hibernate 的延迟加载&#xff08;lazy load&#xff09;本质上就是代理模式的应用&#xff0c;我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能。Hibernate 充分利用了代理模式的这种优势&#xff0c;并结合了 Javassist 或 CGLIB 来动态地生成代理对象&#xff0c;这更加增加了代理模式的灵活性&#xff0c;Hibernate 给这种用法一个新名称&#xff1a;延迟加载。无论怎样&#xff0c;充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。

转:https://www.cnblogs.com/zhangshitong/p/5306442.html



推荐阅读
  • 详解MyBatis二级缓存的启用与配置
    本文深入探讨了MyBatis二级缓存的启用方法及其配置细节,通过具体的代码实例进行说明,有助于开发者更好地理解和应用这一特性,提升应用程序的性能。 ... [详细]
  • SpringBoot底层注解用法及原理
    2.1、组件添加1、Configuration基本使用Full模式与Lite模式示例最佳实战配置类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断配置类组 ... [详细]
  • 本文详细介绍了PHP中的几种超全局变量,包括$GLOBAL、$_SERVER、$_POST、$_GET等,并探讨了AJAX的工作原理及其优缺点。通过具体示例,帮助读者更好地理解和应用这些技术。 ... [详细]
  • 本文介绍如何通过Java代码调用阿里云短信服务API来实现短信验证码的发送功能,包括必要的依赖添加和关键代码示例。 ... [详细]
  • 本文详细对比了HashMap和HashTable在多线程环境下的安全性、对null值的支持、性能表现以及方法同步等方面的特点,帮助开发者根据具体需求选择合适的数据结构。 ... [详细]
  • 华为云openEuler环境下的Web应用部署实践
    本文详细记录了在华为云openEuler系统上进行Web应用部署的具体步骤,包括配置yum源、安装Apache、MariaDB、PHP及其相关组件,并完成WordPress的安装与配置过程。 ... [详细]
  • 本文介绍了如何通过创建自定义 XML 文件来修改 Android 中 Spinner 的项样式,包括颜色和大小的调整。 ... [详细]
  • 本文探讨了Android系统中联系人数据库的设计,特别是AbstractContactsProvider类的作用与实现。文章提供了对源代码的详细分析,并解释了该类如何支持跨数据库操作及事务处理。源代码可从官方Android网站下载。 ... [详细]
  • 本文介绍了一种在 Android 开发中动态修改 strings.xml 文件中字符串值的有效方法。通过使用占位符,开发者可以在运行时根据需要填充具体的值,从而提高应用的灵活性和可维护性。 ... [详细]
  • Java高级工程师学习路径及面试准备指南
    本文基于一位朋友的PDF面试经验整理,涵盖了Java高级工程师所需掌握的核心知识点,包括数据结构与算法、计算机网络、数据库、操作系统等多个方面,并提供了详细的参考资料和学习建议。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • 如何使用Maven将依赖插件一并打包进JAR文件
    本文详细介绍了在使用Maven构建项目时,如何将所需的依赖插件一同打包进最终的JAR文件中,以避免手动部署依赖库的麻烦。 ... [详细]
  • 本文详细介绍了 Redis 中的主要数据类型,包括 String、Hash、List、Set、ZSet、Geo 和 HyperLogLog,并提供了每种类型的基本操作命令和应用场景。 ... [详细]
  • 本文详细介绍了 `org.apache.tinkerpop.gremlin.structure.VertexProperty` 类中的 `key()` 方法,并提供了多个实际应用的代码示例。通过这些示例,读者可以更好地理解该方法在图数据库操作中的具体用途。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
author-avatar
半宅半学
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有