JAVAWEB开发之Hibernate详解(三)——Hibernate的检索方式、抓取策略以及利用二级缓存进行优化、解决数据库事务并发问题...

Hibernate的检索方式

Hibernate提供了以下几种检索对象的方式:
  • 导航对象图检索方式:根据已经加载的对象导航到其他对象。
  • OID检索方式:按照对象的OID来检索对象。
  • HQL检索方式:使用面向对象 的HQL查询语言。
  • QBC检索方式:使用QBC(Query By Criteria)API来检索对象.这种API封装了基于字符串形式的查询语句,提供了更加面向对象的查询接口。
  • 本地SQL检索方式:使用本地数据库的SQL查询语句
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似.在Hibernate提供的各种检索方式中,HQL使用最广的一种检索方式.它有如下的功能:
  • 在查询语句中设定各种查询条件
  • 支持投影查询,即检索出对象的部分属性
  • 支持分页查询
  • 支持连接查询
  • 支持分组查询,允许使用HAVING和GROUP BY关键字
  • 提供内置聚集函数,如sum()、count()、min()、max()
  • 能够调用用户定义的SQL函数或标准的SQL函数
  • 支持子查询
  • 支持动态绑定参数
HQL检索方式包括以下步骤:
  • 通过Session的createQuery()方法创建一个Query对象,它包括一个HQL查询语句. HQL查询语句中可以包含命名参数
  • 动态绑定参数
  • 调用Query的list()方法执行查询语句.该方法返回java.util.List类型的查询结果,在List集合中存放了符合查询条件的持久化对象。
Query接口支持方法链编程风格,它的setXxx()方法返回自身实例,而不是void类型
HQL vs SQL:
  • HQL查询语句是面向对象的,Hibernate负责解析HQL查询语句,然后根据对象-关系映射文件中的信息,把SQL查询语句翻译成相应的SQL语句.HQL查询语句中的主体是域模型中的类及类的属性
  • SQL查询语句是与关系数据库绑定在一起的。SQL查询语句中的主体是数据库及表的字段
知识点1:简单的查询

知识点2:sql检索方式

知识点3:使用别名

知识点4:多态查询(指查询出当前类及所有子类的实例)

注意:可以查询实现接口或继承自抽象类以及普通类的所有子类,但是必须是类的全路径
知识点5:对查询结果排序

知识点6:分页查询
分页查询:
  • setFirstResult(int firstResult): 设定从哪一对象开始检索,参数firstResult表示这个对象在查询结果中索引的位置,索引位置的起始值是0.默认情况下,Query从查询结果中的第一个对象开始检索。
  • setMaxResult(int maxResults): 设定一次最多检索出的对象的数目。在默认情况下,Query和Criteria接口检索出查询结果中的所有对象。

知识点7:检索单个对象

注意:当返回单个对象时无需再用List集合进行接收,直接使用对应的某个对象进行接收即可。
知识点8_1:绑定参数的形式,按照参数名称绑定。

知识点8_2:绑定参数的形式,按照参数位置绑定。

绑定参数:
Hibernate的参数绑定机制依赖于JDBC API中的PreparedStatement的预定义SQL语句功能。
HQL的参数绑定有两种形式:
  • 按照参数名称绑定:在HQL查询语句中定义命名参数,命名参数以":"开头
  • 按照参数位置绑定:在HQL查询语句中使用"?"来定义参数位置
相关方法:
  • setEntity(): 把参数与持久化类绑定(如果参数为对象时如 select o from Order o where c.customer=:customer)
  • setParamater(): 绑定任意类型的参数。该方法的第三个参数显式指定Hibernate映射类型。
HQL采用ORDER BY关键字对查询结果排序
关于QBC(Criteria条件查询)中常见的条件如下:

HQL和QBC支持的各种运算 对比如下:


连接查询 HQL语句中的连接的写法覆盖映射文件中的配置(如fetch=join)

知识点9:迫切左外连接

注意:迫切连接筛选出的数据可能有很多重复的 需要加distinct关键字去排重
知识点10:左外连接

知识点11: 内连接

知识点12:迫切内连接

知识点13:隐式内连接

知识点14:右连接

关联级别运行时的策略
1、若在HQL、QBC代码中没有显式的指定检索策略,使用映射文件中的检索策略。但HQL总是忽略映射文件中设置的迫切左外(内)连接检索策略也就是说,即使映射文件中设置了迫切左外(内)连接检索策略,如果HQL查询语句中没有显示指定这种策略,那么HQL仍然采用立即检索策略。
2.若代码中显示指定了检索策略,则覆盖映射文件中的检索策略
3.目前的hibernate版本只允许在一个查询语句中迫切左外连接检索一个集合。
4.HQL支持各种各样的连接查询
知识点15:使用SQL风格的交叉连接和隐式内连接(了解知识)

  • 投影查询:查询结果仅包含实体的部分属性,通过SELECT关键字实现。
  • Query的list()方法返回的集合中包含的是数组类型的元素,每个对象数组代表查询结果的一条记录。
  • 可以在持久化类中定义一个对象的构造器来包装投影查询返回的记录,使程序代码能完全运用面向对象的语义来访问查询结果集。
  • 可以通过DISTINCT关键字来保证查询结果不会返回重复元素。
知识点16_1:投影查询

知识点16_2:投影查询(使用构造函数)

报表查询用于对数据分组和统计, 与 SQL 一样, HQL 利用 GROUP BY 关键字对数据分组, 用 HAVING 关键字对分组数据设定约束条件.
在 HQL 查询语句中可以调用以下聚集函数
count()、min()、max()、sum()、avg()
知识点17_1:报表查询

知识点17_2: 报表查询 分组

知识点18:在映射文件中定义命名查询语句

知识点19:离线条件查询
web三层架构中会有这样一种情况,当涉及到高级查询时,会有一些字段信息 不在VO类的封装范围内(因为VO类一般是持久化类 每个属性基本上都是与表对应的),要想将请求信息通过service层传递到DAO层 传统方法是将需要查询的字段信息作为方法参数传递过去 会导致参数非常多, 现在Hibernate中提供了一个离线条件查询的功能 只需要在获取请求 解析后将查询条件封装到DetachedCriteria对象中 将这个离线对象作为参数传递过去即可。

具体用法如下:

Hibernate检索方式总结


Hibernate检索(抓取)策略

知识点1:初始化测试数据

知识点2:区分立即检索和延迟检索,查询编号为1的客户
立即检索:立即加载检索方法指定的对象
延迟检索:延迟加载检索方法指定的对象


发现如果持久化类对应的映射文件中<class>标签上的lazy属性设置为true 即使使用load去查找对应的对象也是立即检索。
知识点3_1:初始化延迟检索中的代理

知识点3_2: 理解延迟检索中的代理

所以要想使用延迟检索,还需要导入这个操作字节码的jar包(Hibernate自带的有)
知识点4:区分类级别和关联级别的检索


知识点5:类级别的检索策略
类级别可选的检索策略包括立即检索和延迟检索,默认为延迟检索。
类级别的检索策略可以通过<class>元素的lazy属性进行设置。
如果程序加载一个对象的目的是为了访问它的属性,可以采用立即检索。如果程序加载一个持久化对象的目的是仅仅为了获得它的引用,可以采用延迟检索。
无论<class>元素的lazy属性是true还是false,Session的get()方法及Query的list()方法在类级别总是使用立即检索策略。
若<class>元素的lazy属性为true或取默认值,Session的load()方法不会执行查询数据表的SELECT语句,仅返回代理对象的实例,该代理类实例具有如下特征:
  • 由Hibernate在运行时采用javassist工具动态生成。
  • Hibernate创建代理类实例时,仅初始化其OID属性。
  • 在应用程序第一次访问代理类实例的非OID属性时,Hibernate会初始化代理类实例。

知识点6:关联级别的检索策略
在映射文件中,用<set>元素来匹配一对多及多对多关联关系。<set>元素有lazy和fetch属性
  • lazy:主要决定orders集合被初始化的时机。即到底是在加载Customer对象时就被初始化,还是在程序访问orders集合时被初始化。
  • fetch:取值为select或subselect时,只决定初始化orders的查询语句的形式;若取值为join,则除了决定初始化查询语句的形式外,还决定了orders集合被初始化的时机。
  • 若把fetch设置为join,lazy属性将被忽略。
一对多和多对多关联的检索策略(set): <set>元素的lazy和fetch属性

可以使用Query测试subselect
测试示例

知识点6_1: 关联级别的检索策略 详细说明
(1) 延迟检索和增强延迟检索:
在延迟检索(lazy属性值为true)集合属性时,Hibernate在以下的情况下初始化集合代理类实例:
  • 用于程序第一次访问集合属性:iterrator()、size()、isEmpty()、contains()等方法。
  • 通过Hibernate.initalize()静态方法显式初始化。

增强延迟检索(lazy属性为extra):与lazy="true"类似。主要区别是增强延迟检索策略能进一步延迟Customer对象orders集合代理实例的初始化时机:
  • 当程序第一次访问orders属性的iterator()方法时,会导致orders集合代理类实例的初始化。
  • 当程序第一次访问order属性的size(),contains()和isEmpty()方法时,Hibernate不会初始化orders集合类的实例,仅通过特定的select语句查询必要的信息,不会检索所有的Order对象。

(2) 用带子查询的select语句整批量初始化orders集合(fetch属性为"subselect")
  • <set>元素的fetch属性:取值为"select"或"subselect"时,决定初始化orders的查询语句的形式;若取值为"join",则决定orders集合被初始化的时机。而默认值为select。
  • 当fetch属性为"subselect"时:假定Session缓存中有n个orders集合代理类实例没有被初始化,Hibernate能够通过带子查询的select语句,来批量初始化n个集合代理类实例。
(3) 迫切左外连接检索(fetch属性值设为"join")
当fetch属性为"join"时:
  • 检索Customer对象时,会采用迫切左外连接(通过左外连接加载与检索指定的对象关联的对象)策略来检索所有关联的Order对象。
  • lazy属性将被忽略。
  • Query的list()方法会忽略映射文件中配置的迫切左外连接检索策略,而依旧采用立即检索还是延迟加载策略由Set集合的lazy属性决定

知识点7:多对一和一对一关联的检索策略
<many-to-one>元素也有一个lazy属性和fetch属性

在测试select和proxy的时候,需要在Order.hbm.xml中设置
<class name=“cn.itcast.n_many2oneseach.Order” table=“orders” lazy=“false”>
测试代码简单示例:


多对一和一对一关联的检索策略 详细说明:
(1) 和<set>一样,<many-to-one>元素也有一个lazy属性和fetch属性
  • 若fetch属性设置为join,那么lazy属性将被忽略
  • 迫切左外连接检索策略的优点在于比立即检索策略使用的SELECT语句更少
  • 无代理延迟检索需要增强持久化类的字节码才能实现
(2) Query的list方法会忽略映射文件配置的迫切左外连接检索策略,而只能采用延迟检索或立即检索策略,根据customer类级别的lazy属性 lazy=true为延迟检索,lazy=false为立即检索
(3) 如果在关联级别使用了延迟加载或立即加载策略,可以设置批量检索的大小,以帮助提高延迟检索或立即检索的运行性能。

知识点7_1: 组合1 多对一立即检索+set立即检索

知识点7_2: 组合2 多对一片迫切左外+set立即检索

知识点7_3: 多对一立即检索+set迫切左外

知识点8:批量检索 从一的一端查询 查询所有的客户
<set>元素有一个batch-size属性,用来为延迟检索策略或立即检索策略设定批量检索的数量。批量检索能减少SELECT语句的数目,提高延迟检索或立即检索的性能。默认值是1.
注意:query.list()属于hql检索,hql检索忽略关联级别的迫切左外连接检索,只与lazy属性有关。

知识点9: 批量检索 从多的一端查询 查询所有订单

比较三种检索策略

事务并发处理问题与解决

多个事务并发运行时的并发问题
问题1:事务的特性与可能出现的问题
事务:事务就是逻辑上的一组操作,要么全部成功,要么全部失败。
事务特性:
  • 原子性:事务一组操作不可分割。
  • 一致性:事务执行前后,数据的完整性要保持一致。
  • 隔离性:一个事务在执行过程中不应该受到其他事务的干扰。
  • 持久性:一旦事务结束,数据就永久保存到数据库。
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题,这些并发问题可归纳为以下几类:
  • 第一类丢失更新:撤销一个事务时,把其它事务已经提交的更新数据覆盖。
  • 脏读:一个事务读取到了另一个事务 未提交的更新数据。
  • 虚读:一个事务读到了另一事务已提交的新插入的数据。
  • 不可重复读:一个事务读到了另一事物已提交的更新数据。
问题2:避免三种读问题与写问题
ANSI 事务隔离级别— ANSI SQL标准定义了隔离级别,但并不是SQL数据库独有 JTA也定义了同样的隔离级别。级别越高,成本越高。
READ_UNCOMMITED(未提交读):以上三种读的情况都有可能发生。
READ_COMMITED(已提交读):允许在并发事务已经提交后读取。可防止脏读,但虚读和不可重复读有可能发生。
REPEATABLE_READ(重复读):对相同字段的多次读取是一致的,除非数据被本身事务改变。可防止脏读、不可重复读,但是虚读仍有可能发生。
SERIALZABLE(串行的):完全确保不发生脏读、虚读、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的。
解决写问题:通过乐观锁和悲观锁(乐观锁包括版本控制和时间戳)
悲观锁和乐观锁都是保证数据准确性的机制。
  • 为保证数据的准确性,程序必须保证在一个线程修改数据的时候,该数据没有被其它线程修改。在传统的数据库编程中,程序修改数据时先锁定该数据行,使其它程序无法修改该行数据,修改完毕后释放数据锁,以保证数据的准确性。由于该机制需要锁定数据行,被锁定的数据只能被一个线程使用,因此称为悲观锁。
  • 乐观锁使用完全不同的方式。乐观锁通过version列保存当前数据的版本,如果程序修改了数据就将版本加1。反过来,如果版本列有了变化,说明数据被修改过了。程序保存数据时会检查数据的version列。如果version列已经发生了变化,程序就会重新读取、修改并保存数据。由于该机制不需要锁定数据行,允许多条线程同时访问同一条数据,因此被称为乐观锁。乐观锁的效率要高于悲观锁,因此现代编程更倾向于乐观锁。
XML配置乐观锁:
XML中使用<version/>配置乐观锁,name属性配置版本列。注意<version/>版本列要配置在<id/>主键的后面,<property/>普通属性的前面。例如:<version name="version"></version> 版本属性既可以为int、long等数据类型,也可以为Timestamp时间戳等类型,配置时使用type配置类型,例如:<version type="timestamp" column="version">或者直接使用<timestamp/>标签配置riqi9版本,与上面的配置是等价的。<timestamp column="version" >
问题3:Hibernate中设置事务隔离级别
设置隔离级别
每个数据库连接都有默认的隔离级别,通常是读已提交或可重复读.可以通
过数据库配置设置,也可在应用程序中设置.例如Hibernate:
hibernate.connection.isolation = 4
1—Read uncommitted isolation
2—Read committed isolation
4—Repeatable read isolation
8—Serializable isolation
注意:* Hibernate不可能改变在受管环境下由应用服务器提供的数据库连接的隔离级别,只能通过改变应用服务器配置的方式来改变.
* 设置隔离级别是全局选项,会影响所有的连接和事务.有时需要为某个特定事务指定更多的限制.
* Hibernate依赖于乐观的并发控制,使用版本检查和悲观锁实现附加的锁支持(了解)

管理session

(1) 尽管让程序自主管理Session对象的生命周期也是可行的,但是在实际Java应用中,把管理Session对象的生命周期交给Hibernate管理,可以简化Java应用程序代码和软件架构。
(2) Hibernate3自身提供了三种管理Session对象的方法:
  • Session对象的生命周期与本地线程绑定。
  • Session对象的生命周期与JTA事务绑定。
  • Hibernate委托程序管理Session对象的生命周期。
(3) 在Hibernate的配置文件中,hibernate.current_session_context_class属性用于指定Session管理方式,可选值包括:
  • thread:Session对象的生命周期与本地线程绑定。
  • jta*:Session对象的生命周期与JTA事务绑定。
  • managed:Hibernate委托程序来管理Session对象的生命周期。

知识点1: Session对象的生命周期与本地线程绑定

(1) 如果把Hibernate配置文件的hibernate.current_session_context_class属性值设为thread,Hibernate就会按照与本地线程绑定的方式来管理Session。
(2) Hibernate按以下规则把Session与本地线程绑定:
  • 当一个线程(threadA)第一次调用SessionFactory对象的getCurrentSession()方法时,该方法会创建一个新的Session(sessionA)对象,把该对象与threadA绑定,并将sessionA返回。
  • 当threadA再次调用SessionFactory对象的getCurrentSession()方法时,该方法返回sessionA对象。
  • 当threadA提交sessionA对象关联的事务时,Hibernate会自动清理sessionA对象的缓存,然后提交事务,关闭sessionA对象。当threadA撤销sessionA对象关联的事务时,也会自动关闭sessionA对象。
  • 若threadA再次调用SessionFactory对象的getCurrentSession()方法时,该方法会又创建一个新的Session(sessionB)对象,把该对象与threadB绑定,并将sessionB返回。
知识点2: 在Hibernate中如何配置
(1) 在hibernate.cfg.xml文件中增加
<!-- 配置session的线程本地化 threadLocal -->
<property name=“hibernate.current_session_context_class">thread</property>
(2) 不是调用SessionFactory.openSession()方法 而是调用SessionFactory.getCurrentSession() 获取session对象,从当前的线程提取session。
* 当前线程如果存在session对象,取出直接使用
* 当前线程如果不存在session对象,获取一个新的session对象和当前的线程绑定

二级缓存

知识点1:理解缓存的定义
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。
知识点2:理解二级缓存的定义
Hibernate中提供了两个级别的缓存
  • 第一个级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由Hibernate管理的,一般情况下无需进行干预。
  • 第二个级别的缓存是SessionFactory级别的缓存,它是属于进程级别的缓存。
Hibernate的缓存可以分为两类:
  • 内置缓存:Hibernate自带的,不可卸载。通常在Hibernate的初始化阶段,Hibernate会把映射元数据和预定义的SQL语句放到SessionFactory的缓存中,映射的元数据是映射文件中数据的复制,而预定义SQL时Hibernate根据映射元数据推导出来的。该内置缓存是只读的。
  • 外置缓存(二级缓存):一个可配置的缓存插件。在默认的情况下,SessionFactory不会启用这个缓存插件。外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘。
知识点3: Hibernate二级缓存的结构

知识点4:理解二级缓存的并发访问策略
二级缓存由4中并发访问策略:
transactional:事务型 特点如下:
  • 仅在受管理的环境中适用;
  • 提供Repeatable Read 事务隔离级别;
  • 适用经常被读,很少修改的数据;
  • 可以防止脏读和不可重复读的并发问题;
  • 缓存支持事务,发送异常的时候,缓存也能够回滚。
read-write:(读写型) 特点如下:
  • 提供Repeatable committed事务隔离级别;
  • 在非集群的环境中适用;
  • 是用于经常被读,很少修改的数据;
  • 可以防止脏读;
  • 更新缓存的时候会锁定缓存中的数据
nonstrict-read-write: 非严格读写型 特点如下:
  • 适用极少被修改,偶尔允许脏读的数据(两个事务同时修改数据的情况很少见);
  • 不保证缓存和数据库中数据的一致性;
  • 为缓存数据设置很短的过期时间,从而尽量避免脏读;
  • 不锁定缓存中的数据
read-only:只读型 特点如下:
  • 适用从来不会被修改的数据(如参考数据);
  • 在此模式下,如果对数据进行更新操作,会有异常;
  • 事务的隔离级别低,并发性能高;
  • 在集群环境中也能完美运作;
知识点5:缓存中存放的数据
(1) 适合放入二级缓存中的数据:
  • 很少被修改;
  • 不是很重要的数据,允许出现偶尔的并发问题;
(2) 不适合放入二级缓存中的数据
  • 经常被修改;
  • 财务数据,绝对不允许出现并发问题;
  • 与其他应用数据共享的数据;
知识点6:缓存提供的供应商
(1) Hibernate的二级缓存是进程或集群范围内的缓存,缓存中存放的是对象的散装数据(对于一级缓存缓存的是持久态对象的内存地址,如果配置了二级缓存 在存入一级缓存的同时 还会将这个持久态对象的所有属性全部拷贝然后在二级缓存new出一个对象空间存放拷贝的内容 这就是所谓的散装数据)
(2) 二级缓存是可配置的插件,Hibernate允许选用以下类型的缓存插件:
  • EHCache: 可作为进程范围内的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。
  • OpenSymphony: 可作为进程范围内的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询缓存提供了支持;
  • SwarmCache: 可作为集群范围内的缓存,但不支持Hibernate的查询缓存。
  • JBossCache:可作为集群范围内的缓存,支持Hibernate的查询缓存。
(3) 4种缓存插件支持的并发访问策略(x 代表支持, 空白代表不支持)


知识点7:配置进程范围内的二级缓存(配置ehcache缓存)
步骤如下:
第一步:拷贝ehcache-1.5.0.jar到当前工程的lib目录下(依赖backport-util-concurrent和commons-logging包)
第二步:开启二级缓存
<property name="hibernate.cache.use_second_level_cache">true</property>
第三步:要指定缓存的供应商
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
第四步:指定要使用二级缓存的类
(1) 方法一:在使用类的*.hbm.xml配置
选择使用二级缓存的持久化类,设置它的二级缓存的并发访问策略,<class>元素的<cache>子元素 表名Hibernate会 缓存对象的简单属性,但不会缓存集合属性,若希望缓存集合属性中的元素,必须在<set>元素中加入<cache>子元素。


(2) 方法二 在hibernate.cfg.xml文件中配置(建议使用)
<!-- 指定使用二级缓存的类 放在maping下面 -->
<!-- 配置类级别的二级缓存 -->
<class-cache class="cn.itcast.c3p0.Customer" usage="read-write"/>
<class-cache class="cn.itcast.c3p0.Order" usage="read-write"/>

<!-- 配置集合级别的二级缓存 -->
<collection-cache collection="cn.itcast.c3p0.Customer.orders"usage="read-write"/>
第五步:配置ehcache默认的配置文件ehcache.xml(名字固定)(放在classpath下)

知识点8: 测试二级缓存和散装数据
 public void  testCache() {
	Session session=HibernateUtils.openSession();
	Transaction tx=session.beginTransaction();
	Customer customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());
        System.out.println(customer);//测试散装数据
        tx.commit();
        session.close();
	
	session=HibernateUtils.openSession();
	tx=session.beginTransaction();
	customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());
	//cn.itcast.c3p0.Customer@1551b0
	System.out.println(customer); //测试散装数据
        tx.commit();
        session.close();
        session=HibernateUtils.openSession();
	tx=session.beginTransaction();
	customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());
	//cn.itcast.c3p0.Customer@1758500
	System.out.println(customer);  //测试散列 是重组的对象
        tx.commit();
        session.close();
}
知识点9:测试集合级别的二级缓存(存放查询条件,即OID)
@Test
public void test(){ 
Session session=Session session=HibernateUtils.openSession();
     Transaction tx=session.beginTransaction();
     //查询客户,关联集合,可以使用立即检索查看效果(2条sql语句)
     Customer customer=(Customer)session.load(Customer.class, 1);	
     System.out.println(customer.getName());
     System.out.println(customer.getOrders().size());
     tx.commit();
     session.close();
 
     session=Session session=HibernateUtils.openSession();
     tx=session.beginTransaction();
     //不再显示sql语句,从二级缓存中获取
     customer=(Customer)session.load(Customer.class, 1);
     System.out.println(customer.getName());
     System.out.println(customer.getOrders().size());
     tx.commit();
     session.close();
}
知识点10:测试类级别的二级缓存只适用于get和load获取数据,对于query接口可以将数据放置到类级别的二级缓存中,但是不能使用query接口的list方法从缓存中获取数据;query接口将查询的对象放置到二级缓存的查询区域;
@test
public void testQuery(){
    session= HibernateUtils.openSession();
    tx=session.beginTransaction();
    Query query=session.createQuery("from Customer where id=1");
    query.list();
    tx.commit();
    session.close();
    
    session=HibernateUtils.openSession();
    tx=session.beginTransaction();
    query=session.createQuery("from Customer where id=1");
    query.list();
    Customer c = (Customer)session.get(Customer.class,1);//不会查询数据库
    c.getName();
    tx.commit();
    session.close();

} 


知识点11:测试一级缓存更新数据会同步到二级缓存
public  void testUpdate() {
	Session session=sf.openSession();
	Transaction tx=session.beginTransaction();
	Customer customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());
	customer.setAge(45);
	tx.commit();
	session.close();

	session=sf.openSession();
	tx=session.beginTransaction();
	customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());   //45
	tx.commit();
	session.close();
}


知识点12:测试二级缓存的数据存放到临时目录

知识点13:配置进程范围内的二级缓存(配置ehcache缓存)

二级缓存标签配置详解:
  • <diskStore>:指定一个目录,当EHCache把数据写到硬盘上时,将把数据写到这个文件目录下,默认是C:\WINDOWS\Temp ;
  • <defaultCache>: 设置缓存的默认数据过期策略;
  • <cache> : 设定具体的命名缓存的数据过期策略,使用name属性,cn.test.second.Order;
  • 每个命名缓存代表代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
  • 如果使用了二级缓存,但是没有进行命名缓存策略的配置,则使用默认的缓存过期策略
二级缓存<cache>元素的属性配置:
  • name: 设置缓存的名称,它的取值为类的全限定名或类的集合的名字。
  • maxElementsInMemory: 设置基于内存的缓存中可存放的对象的最大数目。
  • eternal: 设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds和timeToLiveSeconds属性;默认值为false。
  • timeToIdleSeconds: 设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期的处于空闲状态。
  • timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。 如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
  • overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
  • diskPersistent 当jvm结束时是否持久化对象 true false 默认是false
  • diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
知识点14:测试二级缓存的数据存放到临时目录
//测试缓存溢出存放到临时目录
@Test
public  void testowerFlow() {
	Session session=Session session=HibernateUtils.openSession();
	Transaction tx=session.beginTransaction();
	Query query=session.createQuery(" from Order o");
           //30个对象
	query.list().size();
	tx.commit();
	session.close();
}
知识点15:时间戳缓存区域
Hibernate 提供了和查询相关的缓存区域:
  • 时间戳缓存区域: org.hibernate.cahce.UpdateTimestampCache
时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
  • T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
  • T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
  • T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2>T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果
测试时间戳缓存区
public  void testUpdateTimeStampCache() {
	Session session=Session session=HibernateUtils.openSession();
	Transaction tx=session.beginTransaction();
	Customer customer=(Customer)session.load(Customer.class, 4);//T1
	System.out.println(customer.getAge());
    //注意:不能使用set的方式更新数据,因为set的方式清理session的一级缓存和二级缓存没有关系
	Query query=session.createQuery("update Customer c  
                          set c.age=55 where c.id=4");
	query.executeUpdate();//T2
             tx.commit();
              session.close();
	
	session=Session session=HibernateUtils.openSession();
	tx=session.beginTransaction();
	//T2>T1重新查询数据库
	customer=(Customer)session.load(Customer.class, 4);
	System.out.println(customer.getAge());
             tx.commit();
              session.close();
}
知识点16:Query接口的iterate()方法
Query 接口的 iterate() 方法
  • 同 list() 一样也能执行查询操作
  • list() 方法执行的 SQL 语句包含实体类对应的数据表的所有字段
  • Iterate() 方法执行的SQL 语句中仅包含实体类对应的数据表的 ID 字段
  • 当遍历访问结果集时, 该方法先到 Session 缓存及二级缓存中查看是否存在特定 OID 的对象, 如果存在, 就直接返回该对象, 如果不存在该对象就通过相应的 SQL Select 语句到数据库中加载特定的实体对象
大多数情况下, 应考虑使用 list() 方法执行查询操作. iterate() 方法仅在满足以下条件的场合, 可以稍微提高查询性能:
  • 要查询的数据表中包含大量字段
  • 启用了二级缓存, 且二级缓存中可能已经包含了待查询的对象
Query接口的iterator() 方法
//iterator 先到数据库中检索符合条件的id,然后根据id分别到一级和二级缓冲中查找对象
//(没有在查询数据库,每次只能查一个,可能导致n+1次查询 )
public  void testIterator() {
	Session session=Session session=HibernateUtils.openSession();
	Transaction tx=session.beginTransaction();
	Query query=session.createQuery(" from Order o where o.id<11");
	query.list().size();
	tx.commit();
	session.close();
	
	session=HibernateUtils.openSession();
	tx=session.beginTransaction();
	query=session.createQuery(" from Order o");
	Iterator<Order> it=query.iterate();
	while(it.hasNext()){
		System.out.println(it.next().getOrderNumber());
	}
	tx.commit();
	session.close();
}

知识点17:查询缓存
对于经常使用的查询语句, 如果启用了查询缓存, 当第一次执行查询语句时, Hibernate 会把查询结果存放在查询缓存中. 以后再次执行该查询语句时, 只需从缓存中获得查询结果, 从而提高查询性能
查询缓存使用于如下场合:
  • 应用程序运行时经常使用查询语句
  • 很少对与查询语句检索到的数据进行插入, 删除和更新操作
使用查询缓存的步骤:
(1) 配置二级缓存, 因为查询缓存依赖于二级缓存
(2) 在 hibernate 配置文件中启用查询缓存
<property name=“hibernate.cache.use_query_cache">true</property>
(3) 对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable(true) 方法
查询缓存可以缓存属性
public  void testQueryCache() {
	Session session=HibernateUtils.openSession();
	Transaction tx=session.beginTransaction();
	Query query=session.createQuery("select c from Customer c");
	/*
	 * 设置查询缓存
	 *    * 如果查询缓存存在 直接返回
	 *    * 如果查询缓存不存在 查询数据库 将查询结果放置到查询缓存中
	 */
	query.setCacheable(true);//先不加,再添加,看效果
	query.list();
	tx.commit();
	session.close();
	
	Session session=Session session=HibernateUtils.openSession();
	tx=session.beginTransaction();
	query=session.createQuery("select c from Customer c");
	query.setCacheable(true);//先不加,再添加,看效果
	query.list();
	tx.commit();
	session.close();
}

二级缓存总结:
* 二级缓存:SessionFactory级别缓存.可以在多个session之间共享数据的.
* 二级缓存结构:
* 类缓存区,集合缓存区,更新时间戳,查询缓冲区.
* 二级缓存的适合放入的数据:
* 不经常修改的,允许偶尔出现并发问题.
* 二级缓存的配置:
* 在Hibernate中开启二级缓存.
* 配置二级缓存的提供商:
* EHCache
* 配置哪些类使用二级缓存:
* 在映射文件中配置.
* 在核心配置文件中配置(推荐).
类缓存区的特点: 缓存的是对象的散装数据;
集合缓存区的特点:缓存的是对象的id.需要依赖类缓冲区的配置.
查询缓存:
比二级缓存功能更加强大,而且查询缓存必须依赖二级缓存.
二级缓存:对类/对象的缓存.
查询缓存:针对类中属性的缓存.
查询缓存的配置:
* 配置查询缓存:
* 前提是二级缓存已经配置完毕.
* 在核心配置文件中:
<!-- 配置查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>




项目测试示例如下:

Customer
package cn.test.hibernate3.demo1;

import java.util.HashSet;
import java.util.Set;

/**
 * 客户的实体:
 *
 */
public class Customer {
	private Integer cid;
	private String cname;
	private Integer age;
	// 乐观锁解决丢失更新,提供一个整数类型的属性.
	private Integer ver;
	public Customer() {
	}
	
	
	public Customer(Integer cid, String cname) {
		super();
		this.cid = cid;
		this.cname = cname;
	}


	// 一个客户有多个订单.
	private Set<Order> orders = new HashSet<Order>();
	public Integer getCid() {
		return cid;
	}
	public void setCid(Integer cid) {
		this.cid = cid;
	}
	public String getCname() {
		return cname;
	}
	public void setCname(String cname) {
		this.cname = cname;
	}
	public Set<Order> getOrders() {
		return orders;
	}
	public void setOrders(Set<Order> orders) {
		this.orders = orders;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public Integer getVer() {
		return ver;
	}
	public void setVer(Integer ver) {
		this.ver = ver;
	}


	@Override
	public String toString() {
		return "Customer [cid=" + cid + ", cname=" + cname + ", age=" + age
				+ ", ver=" + ver + "]";
	}
	
}
Order
package cn.test.hibernate3.demo1;
/**
 * 订单的实体:
 *
 */
public class Order {
	private Integer oid;
	private String addr;
	// 订单属于某一个客户.放置一个客户的对象.
	private Customer customer;
	public Integer getOid() {
		return oid;
	}
	public void setOid(Integer oid) {
		this.oid = oid;
	}
	public String getAddr() {
		return addr;
	}
	public void setAddr(String addr) {
		this.addr = addr;
	}
	public Customer getCustomer() {
		return customer;
	}
	public void setCustomer(Customer customer) {
		this.customer = customer;
	}
	@Override
	public String toString() {
		return "Order [oid=" + oid + ", addr=" + addr + "]";
	}
	
}
Customer.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="cn.test.hibernate3.demo1.Customer"  table="customer"  lazy="true">
		<!-- <cache usage="read-write"/> -->
		<!-- 配置唯一标识 -->
		<id name="cid" column="cid">
			<generator class="native"/>
		</id>
		<version name="ver" column="version"/>
		
		<!-- 配置普通属性 -->
		<property name="cname" column="cname" length="20"/>
		<property name="age" column="age"/>
		<!-- 建立映射 -->
		<!-- 配置一个集合 <set>的name Customer对象中的关联对象的属性名称. -->
		<set name="orders" fetch="select" lazy="extra">
		    <!-- <cache usage="read-write"/> -->
			<!-- <key>标签中column:用来描述一对多多的一方的外键的名称. -->
			<key column="cno"></key>
			<!-- 配置一个<one-to-many>标签中class属性:订单的类的全路径 -->
			<one-to-many class="cn.test.hibernate3.demo1.Order"/>
		</set>
	</class>
	
	<query name="findAllCustomer">
		from Customer
	</query>
	
</hibernate-mapping>
Order.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
	<class name="cn.test.hibernate3.demo1.Order" table="orders">
		<!-- 配置唯一标识  -->
		<id name="oid" column="oid">
			<generator class="native"/>
		</id>
		<!-- 配置普通属性 -->
		<property name="addr" column="addr" length="50"/>
		<!-- 配置映射 -->
		<!-- 
		<many-to-one>标签
			name 	:关联对象的属性的名称.
			column	:表中的外键名称.
			class	:关联对象类的全路径
		-->
		<many-to-one name="customer" column="cno" class="cn.test.hibernate3.demo1.Customer"/>
	</class>
</hibernate-mapping>
HibernateUtils
package cn.test.hibernate3.utils;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

/**
 * Hibernate抽取工具类
 * 
 */
public class HibernateUtils {
	private static Configuration configuration;
	private static SessionFactory sessionFactory;

	static {
		configuration = new Configuration().configure();
		sessionFactory = configuration.buildSessionFactory();
	}

	public static Session openSession() {
		return sessionFactory.openSession();
	}

	public static Session getCurrentSession() {
		return sessionFactory.getCurrentSession();
	}

	public static void main(String[] args) {
		openSession();
	}
}
ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
 <diskStore path="d:/ehcache"/>
    <defaultCache
            maxElementsInMemory="5"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
</ehcache>
hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
	<session-factory>
		<!-- 必须去配置的属性 -->
		<!-- 配置数据库连接的基本信息 -->
		<property name="hibernate.connection.driver_class">
			com.mysql.jdbc.Driver
		</property>
		<property name="hibernate.connection.url">
			jdbc:mysql:///hibernate3_day03
		</property>
		<property name="hibernate.connection.username">root</property>
		<property name="hibernate.connection.password">root</property>
		<!-- Hibernate的方言 -->
		<!-- 不同方言生成底层SQL不同 -->
		<property name="hibernate.dialect">
			org.hibernate.dialect.MySQLDialect
		</property>

		<!-- 可选的属性 -->
		<!-- 显示SQL -->
		<property name="hibernate.show_sql">true</property>
		<!-- 格式化SQL -->
		<property name="hibernate.format_sql">true</property>

		<property name="hibernate.connection.autocommit">false</property>
		<!-- hbm:映射 to DDL:create、drop、alter 取值有:create-drop、create、update -->
		<property name="hibernate.hbm2ddl.auto">update</property>
		<!-- 设置事务隔离级别 -->
		<property name="hibernate.connection.isolation">4</property>
		<!-- 设置本地Session -->
		<property name="hibernate.current_session_context_class">
			thread
		</property>

		<!-- C3P0连接池设定 -->
		<!-- 使用c3po连接池 配置连接池提供的供应商 -->
		<property name="connection.provider_class">
			org.hibernate.connection.C3P0ConnectionProvider
		</property>
		<!--在连接池中可用的数据库连接的最少数目 -->
		<property name="c3p0.min_size">5</property>
		<!--在连接池中所有数据库连接的最大数目 -->
		<property name="c3p0.max_size">20</property>
		<!--设定数据库连接的过期时间,以秒为单位, 如果连接池中的某个数据库连接处于空闲状态的时间超过了timeout时间,就会从连接池中清除 -->
		<property name="c3p0.timeout">120</property>
		<!--每3000秒检查所有连接池中的空闲连接 以秒为单位 -->
		<property name="c3p0.idle_test_period">3000</property>

		<!-- Hibernate中开启二级缓存 -->
		<property name="hibernate.cache.use_second_level_cache">
			true
		</property>
		<!-- 配置二级缓存的提供商 -->
		<property name="hibernate.cache.provider_class">
			org.hibernate.cache.EhCacheProvider
		</property>
		<!-- 配置查询缓存 -->
		<property name="hibernate.cache.use_query_cache">true</property>

		<!-- 通知Hibernate加载哪些文件 -->
		<mapping resource="cn/test/hibernate3/demo1/Customer.hbm.xml" />
		<mapping resource="cn/test/hibernate3/demo1/Order.hbm.xml" />

		<!-- 配置哪些类使用二级缓存 -->
		<class-cache usage="read-write" class="cn.test.hibernate3.demo1.Customer" />
		<class-cache usage="read-write" class="cn.test.hibernate3.demo1.Order" />
		<!-- 集合缓存区 -->
		<collection-cache usage="read-write"
			collection="cn.test.hibernate3.demo1.Customer.orders" />

	</session-factory>
</hibernate-configuration>	
log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=off, stdout 
HibernateTest1
package cn.test.hibernate3.demo1.test;

import java.util.Arrays;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.demo1.Order;
import cn.test.hibernate3.utils.HibernateUtils;
/**
 * Hibernate的检索方式的测试类:HQL 
 *
 */
public class HibernateTest1 {

	@Test
	// 初始化数据
	public void demo1() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		Customer customer = new Customer();
		customer.setCname("桐谷静香");
		customer.setAge(35);

		for (int i = 0; i < 3; i++) {
			Order order = new Order();
			order.setAddr("朝阳区" + i);

			order.setCustomer(customer);
			customer.getOrders().add(order);

			session.save(order);
		}
		session.save(customer);

		tx.commit();
		session.close();
	}

	@Test
	// HQL检索:简单查询
	public void demo2() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 简单查询
		// List<Customer> list = session.createQuery("from Customer").list();

		List<Customer> list = session
				.createQuery("from Customer where cname=?")
				.setParameter(0, "武藤兰").list();
		for (Customer customer : list) {
			System.out.println(customer);
		}

		tx.commit();
		session.close();
	}

	@Test
	// 使用别名的检索方式
	public void demo3() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		// 使用as 起别名
		// List<Customer> list =
		// session.createQuery("from Customer as c").list();
		// as可以省略
		// List<Customer> list = session.createQuery("from Customer c").list();
		// 条件中使用别名
		// List<Customer> list =
		// session.createQuery("from Customer c where c.cname = ?").setParameter(0,
		// "美美").list();
		// HQL中不能select 后 写*号
		List<Customer> list = session.createQuery("select c from Customer c")
				.list();

		for (Customer customer : list) {
			System.out.println(customer);
		}

		tx.commit();
		session.close();
	}

	@Test
	// HQL 多态查询
	// 可以是接口或某父类及其抽象类所有子类的查询
	public void demo4() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		List list = session.createQuery("from java.lang.Object").list();
		for (Object object : list) {
			System.out.println(object);
		}

		tx.commit();
		session.close();
	}

	@Test
	// 排序查询
	public void demo5() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		List<Customer> list = session.createQuery(
				"from Customer c order by c.cid desc").list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		tx.commit();
		session.close();
	}

	@Test
	// 分页查询
	public void demo6() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		Query query = session.createQuery("from Order");

		query.setFirstResult(2);// 从哪开始 默认是0
		query.setMaxResults(3); // 每页显示多少条

		List<Order> list = query.list();
		for (Order order : list) {
			System.out.println(order);
		}
		tx.commit();
		session.close();
	}

	@Test
	// 唯一结果查询
	public void demo7() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// Customer customer = (Customer) session
		// .createQuery("from Customer where id=? ").setParameter(0, 1)
		// .uniqueResult();
		// System.out.println(customer);
		Long count = (Long) session.createQuery("select count(*) from Order")
				.uniqueResult();
		System.out.println(count);

		tx.commit();
		session.close();
	}

	@Test
	// 条件查询:
	public void demo8() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		// 按位置绑定参数
		// List<Customer> list =
		// session.createQuery("from Customer where cname = ?").setParameter(0,
		// "武藤兰").list();
		// List<Customer> list
		// =session.createQuery("from Customer where cname = ? and cid = ?")
		// .setParameter(0, "武藤兰").setParameter(1, 2).list();

		// 按名称绑定参数:
		/*
		 * Query query = session
		 * .createQuery("from Customer where cname = :cname and cid = :cid");
		 * query.setParameter("cname", "桐谷静香"); query.setParameter("cid", 3);
		 */

		Query query = session.createQuery("from Customer where cname like ?");
		query.setParameter(0, "%静%");
		List<Customer> list = query.list();

		System.out.println(list);

		tx.commit();
		session.close();
	}

	@Test
	// 聚集函数查询:
	public void demo9() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		Integer id = (Integer) session.createQuery(
				"select max(id) from Customer ").uniqueResult();
		Long count = (Long) session.createQuery(
				"select count(*) from Customer ").uniqueResult();

		List<Object[]> list = session
				.createQuery(
						"select count(*),o.customer.cname from Order o group by o.customer.cid ")
				.list();

		System.out.println(id);
		System.out.println(count);
		for (Object[] objects : list) {
			System.out.println(Arrays.toString(objects));
		}
		tx.commit();
		session.close();
	}

	@Test
	// 连接查询
	public void demo10() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 普通内连接:将数据封装到List<Object[]>
		// Query
		// query=session.createQuery("from Customer c inner join c.orders");
		// List<Object[]> list=query.list();
		// for (Object[] objects : list) {
		// System.out.println(Arrays.toString(objects));
		// }

		// 迫切内连接 使用distinct 去掉重复
		Query query = session
				.createQuery("select distinct c from Customer c inner join fetch c.orders");
		List<Customer> list = query.list();

		for (Customer customer : list) {
			System.out.println(customer);
		}

		tx.commit();
		session.close();
	}

	@Test
	// 投影查询
	public void demo11() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 只查询所有客户的名称:
		/*
		 * Query query = session.createQuery("select c.cname from Customer c");
		 * List<String> list = query.list(); for (String name : list) {
		 * System.out.println(name); }
		 */

		// 查询多个属性
		/*
		 * Query query =
		 * session.createQuery("select c.cid , c.cname from Customer c");
		 * List<Object[]> list = query.list(); for (Object[] objects : list) {
		 * System.out.println(Arrays.toString(objects)); }
		 */
		// 构造器查询
		Query query = session
				.createQuery("select new Customer(cid,cname) from Customer");
		List<Customer> list = query.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		tx.commit();
		session.close();
	}
	
	@Test
	// 命名查询:
	public void demo12(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		List<Customer> list=session.getNamedQuery("findAllCustomer").list();
		for (Customer customer : list) {
			System.out.println(customer);
		}
		tx.commit();
		session.close();
	}

}
HibernateTest2
package cn.test.hibernate3.demo1.test;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.demo1.Order;
import cn.test.hibernate3.utils.HibernateUtils;

/**
 * Hibernate检索方式测试类:QBC 
 * QBC: Query By Criteria
 */
public class HibernateTest2 {
	@Test
	// QBC:简单查询
	public void demo1() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 查询所有:
		List<Customer> list = session.createCriteria(Customer.class).list();
		System.out.println(list);

		tx.commit();
		session.close();
	}

	@Test
	// QBC: 排序查询
	public void demo2() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 查询所有:
		Criteria criteria = session.createCriteria(Order.class);
		criteria.addOrder(org.hibernate.criterion.Order.desc("oid"));
		List<Order> list = criteria.list();
		for (Order order : list) {
			System.out.println(order);
		}

		tx.commit();
		session.close();
	}

	@Test
	// 条件查询
	public void demo3() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		Criteria criteria = session.createCriteria(Customer.class);
		// 条件1
		Criterion criterion1 = Restrictions.or(Restrictions.eq("cname", "武藤兰"),
				Restrictions.eq("cid", 2));
		Criterion criterion2 = Restrictions.or(criterion1,
				Restrictions.like("cname", "%静%"));
		criteria.add(criterion2);
		List<Customer> list = criteria.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}

		tx.commit();
		session.close();
	}
	
	@Test
	// 分页查询
	public void demo4(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Criteria criteria=session.createCriteria(Order.class);
		criteria.setFirstResult(2);
		criteria.setMaxResults(3);
		List<Order> list=criteria.list();
		for (Order order : list) {
			System.out.println(order);
		}
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 离线条件查询:
	public void demo5(){
		DetachedCriteria criteria=DetachedCriteria.forClass(Customer.class);
		criteria.add(Restrictions.eq("cname", "武藤兰"));
		criteria.add(Restrictions.eq("cid", 1));
		
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		List<Customer> list=criteria.getExecutableCriteria(session).list();
		System.out.println(list);
		
		tx.commit();
		session.close();
	}

}
HibernateTest3
package cn.test.hibernate3.demo1.test;

import java.util.List;

import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.utils.HibernateUtils;

/**
 * Hibernate的查询方式:SQL
 * 
 */
public class HibernateTest3 {

	@Test
	public void demo1() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		/*
		 * List<Object[]> list =
		 * session.createSQLQuery("select * from customer") .list();
		 * 
		 * for (Object[] objects : list) {
		 * System.out.println(Arrays.toString(objects)); }
		 */

		SQLQuery query = session.createSQLQuery("select * from customer");
		query.addEntity(Customer.class);
		List<Customer> list=query.list();
		for (Customer customer : list) {
			System.out.println(customer);
		}

		tx.commit();
		session.close();
	}
}
HibernateTest4
package cn.test.hibernate3.demo1.test;

import java.util.List;

import org.hibernate.Hibernate;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.demo1.Order;
import cn.test.hibernate3.utils.HibernateUtils;

/**
 * Hibernate的抓取策略
 * 
 * 
 */
public class HibernateTest4 {
	@SuppressWarnings("unchecked")
	@Test
	// 批量抓取:
	// 从订单批量抓取客户
	// 在Customer.hbm.xml中<class>标签上设置batch-size=""
	public void demo9(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		// 查询所有的订单:
		List<Order> list = session.createQuery("from Order").list();
		for (Order order : list) {
			System.out.println(order.getCustomer().getCname());
		}
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 批量抓取:
	// 从客户批量抓取订单
	// <set>标签上设置batch-size=""
	public void demo8(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		// 查询所有客户
		List<Customer> list = session.createQuery("from Customer").list();
		for (Customer customer : list) {
			for (Order order : customer.getOrders()) {
				System.out.println(order.getAddr());
			}
		}
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 在Order.hbml.xml中<many-to-one>标签上配置fetch和lazy
	public void demo7(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		/**
		 * fetch="select",lazy="false"
		 */
		// 查询一号订单
		//Order order = (Order) session.get(Order.class, 1);// 发送多条SQL查询关联对象
		// 查询一号订单所属客户的名称:
		//System.out.println("客户名称:"+order.getCustomer().getCname());// 不发送SQL
		
		/**
		 * fetch="select",lazy="proxy"
		 * proxy:查询关联对象的时候,是否采用延迟,由另一端的类级别延迟来决定.
		 * 	* Customer的<class>上配置的是lazy="true".检索的时候采用延迟
		 * 	* Customer的<class>上配置的是lazy="false".检索的时候不采用延迟
		 */
		// 查询一号订单
		Order order = (Order) session.get(Order.class, 1);// 发送多条SQL查询关联对象
		// 查询一号订单所属客户的名称:
		System.out.println("客户名称:"+order.getCustomer().getCname());// 不发送SQL
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 在Order.hbml.xml中<many-to-one>标签上配置fetch和lazy
	public void demo6(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		/**
		 * fetch="join",lazy被忽略.
		 */
		// 查询一号订单
		Order order = (Order) session.get(Order.class, 1);// 发送一条迫切左外连接查询关联对象.
		// 查询一号订单所属客户的名称:
		System.out.println("客户名称:"+order.getCustomer().getCname());
		
		tx.commit();
		session.close();
	}
	
	@SuppressWarnings("unchecked")
	@Test
	// 在Customer.hbm.xml的<set>标签上配置fetch和lazy
	// fetch="subselect" 子查询
	public void demo5(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		/**
		 * 配置fetch="subselect" lazy="true"
		 */
//		List<Customer> list = session.createQuery("from Customer").list();// 发送查询所有客户的SQL
//		for (Customer customer : list) {
//			System.out.println("客户订单数量:"+customer.getOrders().size());// 发送一个子查询去查询关联对象.
//		}
		
		// 使用子查询 查询多个的情况.
		/*Customer customer = (Customer) session.get(Customer.class, 1);
		System.out.println("客户订单数量:"+customer.getOrders().size());*/
		
		/**
		 * 配置fetch="subselect" lazy="false"
		 */
//		List<Customer> list = session.createQuery("from Customer").list();// 发送查询所有客户的SQL,发送一个子查询查询订单
//		for (Customer customer : list) {
//			System.out.println("客户订单数量:"+customer.getOrders().size());// 不发送SQL
//		}
		
		/**
		 * 配置fetch="subselect" lazy="extra"
		 */
		List<Customer> list = session.createQuery("from Customer").list();// 发送查询所有客户的SQL
		for (Customer customer : list) {
			System.out.println("客户订单数量:"+customer.getOrders().size());// 只发送统计客户订单数量的sql
			for (Order order : customer.getOrders()) {
				System.out.println(order.getAddr());
			}
		}
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 在Customer.hbm.xml的<set>标签上配置fetch和lazy
	// fetch="select"
	public void demo4() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		/**
		 * 配置fetch="select" lazy="true"
		 * * 发送多条SQL.默认值就是fetch="select" lazy="true"
		 */
		// 查询一号客户
		// Customer customer = (Customer) session.get(Customer.class, 1);//发送一条查询客户的SQL
		// 查看一号客户的订单的数量:
		// System.out.println("订单的数量:" + customer.getOrders().size());//又发送一条查询订单的SQL
		
		/**
		 * 配置fetch="select" lazy="false"
		 */
		// 查询一号客户
		//Customer customer = (Customer) session.get(Customer.class, 1);//发送多条SQL查询
		// 查看一号客户的订单的数量:
		//System.out.println("订单的数量:" + customer.getOrders().size());//不发送SQL
		
		/**
		 * 配置fetch="select" lazy="extra"
		 * * extra:及其懒惰的.
		 */
		// 查询一号客户
		Customer customer = (Customer) session.get(Customer.class, 1);// 发送一条查询客户的SQL
		// 查看一号客户的订单的数量:
		System.out.println("订单的数量:" + customer.getOrders().size());// 查询订单的数量:select count(oid) from orders o where o.cid = ? 
		
		for (Order order : customer.getOrders()) {
			System.out.println(order);
		}
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 在Customer.hbm.xml的<set>标签上配置fetch和lazy
	// fetch="join",lazy的取值就会被忽略.
	public void demo3() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 查询一号客户
		Customer customer = (Customer) session.get(Customer.class, 1);// 发送了一条迫切左外连接的SQL
		
		// 查看一号客户的订单的数量:
		System.out.println("订单的数量:" + customer.getOrders().size());// 不发送SQL.
		
		tx.commit();
		session.close();
	}

	@Test
	// 默认的情况下 没有配置任何信息
	// 发送多条SQL查询其关联对象.
	public void demo2() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 查询一号客户
		Customer customer = (Customer) session.get(Customer.class, 1);// 发送一条查询客户的SQL.

		// 查看一号客户的订单的数量:
		System.out.println("订单的数量:" + customer.getOrders().size());// 使用订单的时候,又会发送一条SQL查询

		tx.commit();
		session.close();
	}

	@Test
	// 区分立即和延迟检索
	public void demo1() {
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();

		// 立即检索
		// Customer customer = (Customer) session.get(Customer.class, 1);

		// System.out.println(customer.getCname());

		// 延迟检索:
		Customer customer = (Customer) session.load(Customer.class, 1);

		Hibernate.initialize(customer);

		System.out.println(customer.getCname());

		tx.commit();
		session.close();
	}
}
HibernateTest5
package cn.test.hibernate3.demo1.test;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.utils.HibernateUtils;
//Hibernate的事务与并发的测试类:
public class HibernateTest5 {
	@Test
	public void demo9(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = new Customer();
		customer.setCname("李四");
		session.save(customer);
		
		tx.commit();
		// session.close();
	}
	
	@Test
	// 本地Session
	public void demo8(){
		Session session1 = HibernateUtils.getCurrentSession();
		Session session2 = HibernateUtils.getCurrentSession();
		
		System.out.println(session1 == session2);
	}	
	
	@Test
	// 本地Session
	public void demo7(){
		Session session1 = HibernateUtils.openSession();
		Session session2 = HibernateUtils.openSession();
		
		System.out.println(session1 == session2);
	}	
	
	@Test
	// 乐观锁解决丢失更新:
	public void demo6(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class,3);
		customer.setAge(36);
		
		tx.commit();
		session.close();
	}	
	
	@Test
	// 乐观锁解决丢失更新:
	public void demo5(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class,3);
		customer.setCname("铁男");
		
		tx.commit();
		session.close();
	}
	
	@SuppressWarnings("deprecation")
	@Test
	// 悲观锁解决丢失更新:
	public void demo4(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class,3,LockMode.UPGRADE);
		customer.setAge(36);
		
		tx.commit();
		session.close();
	}
	
	@SuppressWarnings("deprecation")
	@Test
	// 悲观锁解决丢失更新:
	public void demo3(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class,3,LockMode.UPGRADE);
		customer.setCname("铁男");
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 演示丢失更新
	public void demo2(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class, 3);
		customer.setAge(36);
		
		tx.commit();
		session.close();
	}
	
	@Test
	// 演示丢失更新
	public void demo1(){
		Session session = HibernateUtils.openSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class, 3);
		customer.setCname("铁男");
		
		tx.commit();
		session.close();
	}
}
HibernateTest6

package cn.test.hibernate3.demo1.test;

import java.util.Iterator;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;

import cn.test.hibernate3.demo1.Customer;
import cn.test.hibernate3.demo1.Order;
import cn.test.hibernate3.utils.HibernateUtils;

public class HibernateTest6 {
	@Test
	// 查询缓存的测试
	public void demo9(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Query query = session.createQuery("select c.cname from Customer c");
		// 使用查询缓存:
		query.setCacheable(true);
		query.list();
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		query = session.createQuery("select c.cname from Customer c");
		query.setCacheable(true);
		query.list();
		
		tx.commit();
	}
	
	@SuppressWarnings("unused")
	@Test
	// 更新时间戳
	public void demo8(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class, 2);
		session.createQuery("update Customer set cname = '奶茶' where cid = 2").executeUpdate();
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		Customer customer2 = (Customer) session.get(Customer.class, 2);
		
		tx.commit();
	}
	
	@SuppressWarnings("all")
	@Test
	// 将内存中的数据写到硬盘
	public void demo7(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		List<Order> list = session.createQuery("from Order").list();
		
		tx.commit();
	}
	
	@Test
	// 一级缓存的更新会同步到二级缓存:
	public void demo6(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class, 1);
		customer.setCname("芙蓉");
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		Customer customer2 = (Customer) session.get(Customer.class, 1);
		
		tx.commit();
	}
	
	@SuppressWarnings("unchecked")
	@Test
	// iterate()方法可以查询所有信息.
	// iterate方法会发送N+1条SQL查询.但是会使用二级缓存的数据
	public void demo5(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		// N+1条SQL去查询.
		Iterator<Customer> iterator = session.createQuery("from Customer").iterate();
		while(iterator.hasNext()){
			Customer customer = iterator.next();
			System.out.println(customer);
		}
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		iterator = session.createQuery("from Customer").iterate();
		while(iterator.hasNext()){
			Customer customer = iterator.next();
			System.out.println(customer);
		}
		
		tx.commit();
	}
	
	@SuppressWarnings("unchecked")
	@Test
	// 查询所有.Query接口的list()方法.
	// list()方法会向二级缓存中放数据,但是不会使用二级缓存中的数据.
	public void demo4(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		// 查询所有客户:
		// list方法会向二级缓存中放入数据的.
		List<Customer> list = session.createQuery("from Customer").list();
		for (Customer customer : list) {
			System.out.println(customer.getCname());
		}
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		// Customer customer = (Customer) session.get(Customer.class, 1);// 没有发生SQL ,从二级缓存获取的数据.
		// list()方法没有使用二级缓存的数据.
		list = session.createQuery("from Customer").list();
		for (Customer customer : list) {
			System.out.println(customer.getCname());
		}
		
		tx.commit();
	}
	
	@Test
	// 二级缓存的集合缓冲区特点:
	public void demo3(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer = (Customer) session.get(Customer.class, 1);
		// 查询客户的订单.
		System.out.println("订单的数量:"+customer.getOrders().size());
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		Customer customer2 = (Customer) session.get(Customer.class, 1);
		// 查询客户的订单.
		System.out.println("订单的数量:"+customer2.getOrders().size());
		
		tx.commit();
	}
	
	@SuppressWarnings("unused")
	@Test
	// 配置二级缓存的情况
	public void demo2(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer1 = (Customer) session.get(Customer.class, 1);// 发送SQL.
		
		Customer customer2 = (Customer) session.get(Customer.class, 1);// 不发送SQL.
		
		System.out.println(customer1 == customer2);
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		Customer customer3 = (Customer) session.get(Customer.class, 1);// 不发送SQL.
		Customer customer4 = (Customer) session.get(Customer.class, 1);// 不发送SQL.
		
		System.out.println(customer3 == customer4);
		
		tx.commit();
	}
	
	
	@SuppressWarnings("unused")
	@Test
	// 没有配置二级缓存的情况
	public void demo1(){
		Session session = HibernateUtils.getCurrentSession();
		Transaction tx = session.beginTransaction();
		
		Customer customer1 = (Customer) session.get(Customer.class, 1);// 发送SQL.
		
		Customer customer2 = (Customer) session.get(Customer.class, 1);// 不发送SQL.
		
		
		
		tx.commit();
		
		session = HibernateUtils.getCurrentSession();
		tx = session.beginTransaction();
		
		Customer customer3 = (Customer) session.get(Customer.class, 1);// 发送SQL.
		
		
		tx.commit();
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,我无法提供完整的项目源码和 SQL 文件。但是,我可以为您提供一些有用的资源和指导,以帮助您开始开发您自己的 Java Web 项目。 首先,您需要了解基本的 Java Web 开发知识,包括 Servlet、JSP、JDBC 等。如果您还不熟悉这些,可以先学习相关的教程和课程。 接下来,您可以根据您的具体项目需求,选择合适的开发工具和技术。例如,您可以选择使用 Eclipse、IntelliJ IDEA 等集成开发环境,以及 Spring、Struts、Hibernate 等框架。 对于无框架的 Java Web 项目,您可以按照以下步骤进行开发: 1. 创建一个 Java Web 项目,并配置好 Tomcat 等服务器。 2. 创建一个登录界面,包括用户名和密码的输入框,以及登录按钮。您可以使用 HTML 和 CSS 进行界面设计,使用 JSP 进行页面展示。 3. 在后台编写一个 Servlet,用于处理用户提交的登录请求。您可以在 Servlet 中获取用户输入的用户名和密码,并进行验证。如果验证通过,则跳转到主页面;如果验证失败,则返回登录界面,并提示用户重新输入。 4. 在数据库中创建一个用户表,用于存储用户的用户名和密码。您可以使用 JDBC 进行数据库操作。 5. 将用户的验证逻辑封装到一个 Java 类中,以便在多个 Servlet 中复用。 除此之外,您还可以添加其他功能,例如注册、注销、修改密码等。 希望这些指导对您有所帮助。祝您开发愉快!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值