目 录
5.17 Java Web程序中如何用延迟加载操作(OpenSessionInView) 15
6.4关联操作(查询join fetch/级联cascade) 21
10.2利用MyEclipse根据数据表自动生成实体类、hbm.xml 36
10.4 Hibernate中分页查询使用join fatch的缺点 37
一、Hibernate的概述
1.1 Hibernate框架的作用
Hibernate框架是一个数据访问框架(也叫持久层框架,可将实体对象变成持久对象,详见第5章)。通过Hibernate框架可以对数据库进行增删改查操作,为业务层构建一个持久层。可以使用它替代以前的JDBC访问数据。
1.2 Hibernate访问数据库的优点
1)简单,可以简化数据库操作代码。
2)Hibernate可以自动生成SQL,可以将ResultSet中的记录和实体类自动的映射(转化)。
3)Hibernate不和数据库关联,是一种通用的数据库框架(支持30多种数据库),可以方便数据库移植。任何数据库都可以执行它的API。因为Hibernate的API中是不涉及SQL语句的,它会根据Hibernate的配置文件,自动生成相应数据库的SQL语句。
1.3 JDBC访问数据库的缺点
1)需要编写大量的复杂的SQL语句、表字段多时SQL也繁琐、设置各个问号值。
2)需要编写实体对象和记录之间的代码,较为繁琐。
3)数据库移植时需要修改大量的SQL语句。
1.4 Hibernate的设计思想
Hibernate是基于ORM(Object Relation Mapping)思想设计的,称为对象关系映射。负责Java对象和数据库表数据之间的映射。
Hibernate是一款主流的ORM工具,还有其他很多ORM工具,如:MyBatis(以前叫iBatis)、JPA。Hibernate功能比MyBatis强大些,属于全自动类型,MyBatis属于半自动。但全自动会有些不可控因素,因此有些公司会用MyBatis。
ORM工具在完成Java对象和数据库之间的映射后:
1)在查询时,直接利用工具取出“对象”(不论是查询一条记录还是多条记录,取出的都是一个个对象,我们不用再去转化实体了)。
2)在增删改操作时,直接利用工具将“对象”更新到数据库表中(我们不用再去把对象转成数据了)。
3)中间的SQL+JDBC细节,都被封装在了工具底层,不需要程序员参与。
u 注意事项:
v Java程序想访问数据库,只能通过JDBC的方式,而Hibernate框架也就是基于ORM思想对JDBC的封装。
v Hibernate是以“对象”为单位进行数据库的操作。
二、Hibernate的基本使用
2.1 Hibernate的主要结构
1)hibernate.cfg.xml(仅1个):Hibernate的主配置文件,主要定义数据连接参数和框架设置参数。
u 注意事项:就是个xml文件,只是名字比较奇葩!
2)Entity实体类(n个,一个表一个):主要用于封装数据库数据。
3)hbm.xml映射文件(n个):主要描述实体类和数据表之间的映射信息。描述表与类,字段与属性的对应关系。
u 注意事项:hbm.xml是个后缀,如:命名可写Cost.hbm.xml。
2.2 Hibernate主要的API
1)Configuration:用于加载hibernate.cfg.xml配置信息。用于创建SessionFactory。
2)SessionFactory:存储了hbm.xml中描述的信息,内置了一些预编译的SQL,可以创建Session对象。
3)Session:负责对数据表执行增删改查操作。表示Java程序与数据库的一次连接会话,是对以前的Connection对象的封装。和JSP中的session不是一回事,就是名字一样而已。
4)Query:负责对数据表执行特殊查询操作。
5)Transaction:负责Hibernate操作的事务管理。默认情况下Hibernate事务关闭了自动提交功能,需要显式的追加事务管理(如调用Transaction对象中的commit();提交事务)!
u 注意事项:
v 这些API都是在Hibernate包下的,导包别导错!
v 第一次访问数据库比较慢,比较耗资源,因为加载的信息多。
2.3 Hibernate使用步骤
step1:建立数据库表。
step2:建立Java工程(Web工程也可),引入Hibernate开发包和数据库驱动包。必须引入的包:hibernate3.jar、cglib.jar、dom4j.jar、commons-collections.jar、commons-logging.jar…等
step3:添加hibernate.cfg.xml配置文件,文件内容如下:
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="dialect"><!-- 指定方言,决定Hibernate生成哪种SQL -->
org.hibernate.dialect.OracleDialect<!-- 不知道数据库版本就写OracleDialect -->
</property><!-- 可在hibernate3.jar中org.hibernate.dialect包下查看名字 -->
<property name="connection.url">
jdbc:oracle:thin:@localhost:1521:dbchang
</property>
<property name="connection.username">system</property>
<property name="connection.password">chang</property>
<property name="connection.driver_class">
oracle.jdbc.driver.OracleDriver
</property>
<!-- 框架参数,将hibernate底层执行的SQL语句从控制台显示 -->
<property name="show_sql">true</property>
<!-- 格式化显示的SQL -->
<property name="format_sql">true</property>
<!-- 指定映射描述文件 -->
<mapping resource="org/tarena/entity/Cost.hbm.xml" />
</session-factory>
</hibernate-configuration>
u 注意事项:应该放在源文件的src目录下,默认为hibernate.cfg.xml。文件内容是Hibernate工作时必须用到的基础信息。
step4:编写Entity实体类(也叫POJO类),例如:资费实体类Cost
private Integer id; //资费 ID private String feeName; //资费名称
private Integer baseDuration; //基本时长 private Float baseCost; //基本定费
private Float unitCost; //单位费用 private String status; //0:开通;1:暂停;
private String descr; //资费信息说明 private Date createTime; //创建日期
private Date startTime; //启用日期 private String costType; //资费类型
……getter/setter方法
u 注意事项:POJO类表示普通类(Plain Ordinary Old Object),没有格式的类,只有属性和对应的getter/setter方法,而没有任何业务逻辑方法的类。这种类最多再加入equals()、hashCode()、toString()等重写父类Object的方法。不承担任何实现业务逻辑的责任。
step5:编写hbm.xml映射(文件)描述信息:映射文件用于指明POJO类和表之间的映射关系(xx属性对应xx字段),一个类对应一个映射文件。例如:Cost.hbm.xml内容如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- 定义COST_CHANG表和Cost类型之间的映射信息 -->
<hibernate-mapping><!-- <hibernate-mapping package="包名写这也行"> -->
<!-- name:包名.类名,指定是哪个类;table:数据库中哪个表;catalog:对Oracle而言为哪个数据库,对MySQl而言为某个用户(MySQl是在用户下建表,Oracle是在库中建表),
不写也行(若用工具则会自动生成)。例如,select * from cost_chang则会在hibernate.cfg配置文件中定义的库(或用户)下去找表。若写了则为select * from system.cost_chang -->
<class name="org.tarena.entity.Cost" table="COST_CHANG" catalog="system">
<!-- <id></id>表明此为主键列,且必须写否则xml报错,主键映射 -->
<id name="id" type="java.lang.Integer">
<column name="ID" /><!-- 或双标签<column name="ID"></column> -->
<!-- 指定主键值生成方式,采用序列方式生成主键,仅对添加操作有效-->
<generator class="sequence">
<param name="sequence">COST_SEQ_CHANG</param> <!--指定序列名-->
</generator>
</id>
<property name="name" type="java.lang.String"><!-- 以下为非主键映射 -->
<column name="NAME" /><!--可有length、not-null属性,如:length="20" -->
</property>
<property name="baseDuration" type="java.lang.Integer"><!-- 映射顺序没关系 -->
<column name="BASE_DURATION" />
</property>
<property name="baseCost" type="java.lang.Float"><!-- 类型要和实体定义的相同 -->
<column name="BASE_COST" />
</property>
<property name="startTime" type="java.sql.Date"><!--列名写错则报错读不到实体-->
<column name="STARTIME" /><!--junit测试右键点Copy Trace查看错误列-->
</property>
<!--也可写成<property name=" " type=" " column=" "></property> ,主键列同理!-->
…………其他省略…………
</class>
</hibernate-mapping>
u 注意事项:
v 映射文件默认与POJO类放在一起;命名规则为:类名.hbm.xml。
v hbm.xml中已写出的属性与字段的映射要一一对应,若表中没有某个字段,却写了映射关系,则报错:找不到实体类。
step6:利用Hibernate API实现DAO
1)新建HibernateUtil类,用于封装创建Session的方法。如下:每个用户会对应一个Session,但是SessionFactory是共享的。
public class HibernateUtil {
private static SessionFactory sf;
static{//不用每次都加载配置信息,所以放static块中,否则每次都加载会耗费资源
Configuration conf=new Configuration();//加载主配置hibernate.cfg.xml
conf.configure("/hibernate.cfg.xml");
sf=conf.buildSessionFactory();//获取SessionFactory }
public static Session getSession(){//获取Session
Session session =sf.openSession(); return session; } }
2)新建CostDAO接口
public Cost findById(int id); public void save(Cost cost);
public void delete(int id); public void update(Cost cost);
public List<Cost> findAll();
3)新建CostDAOImpl类,用于实现CostDAO接口
public class CostDAOImpl implements CostDAO {
private Session session;
public CostDAOImpl () {//不想老写获得session的方法,就写在构造器中
session=HibernateUtil.getSession(); }
/** get方法执行查询,按主键当条件查询,如何判断是主键,是根据写的描述文件来定,get方法就是findById,就是按主键去查,需指定:操作哪个类和id(主键)条件值即可,其他条件查询做不了 */
public Cost findById(int id) {
//Session session=HibernateUtil.getSession();
Cost cost=(Cost)session.get(Cost.class,id); session.close(); return cost; }
/** save方法执行增加操作,注意1:获取事务并开启,增删改要注意,查询可以不管事务,因为没对数据库进行修改;注意2:主键值根据hbm.xml中的<generator>定义生成,执行后,会先获取序列值,再去做insert操作。
即先:select COST_SEQ_CHANG.nextval from dual; 然后:insert into …… */
public void save(Cost cost) {
//Session session=HibernateUtil.getSession();
Transaction tx=session.beginTransaction();//打开事务 session.save(cost);
tx.commit();//提交事务 session.close();//释放 }
/** delete方法执行删除操作,由于Hibernate以“对象”为单位进行数据库操作,所以这里要传进去一个对象,虽然是个对象,但还是按主键做条件删除,只要把主键值设置上就行,其他非主键值不用管。也可先通过id查再删 */
public void delete(int id) {
//Session session=HibernateUtil.getSession();
Transaction tx=session.beginTransaction(); Cost cost=new Cost();
cost.setId(id); session.delete(cost); tx.commit(); session.close(); }
/** update方法执行修改操作, */
public void update(Cost cost) {
//Session session=HibernateUtil.getSession();
Transaction tx=session.beginTransaction();
session.update(cost);//将cost对象更新到数据库 tx.commit();
session.close(); }
/** 特殊查询,SQL语句:String sql="select * from COST_CHANG";
HQL语句:String hql="from Cost"; (Hibernate Query Language)是面向对象的查询语句。
from后写映射的类名,它是Hibernate中特有的查询语句,根据映射的类去查询。 */
public List<Cost> findAll() {
//Session session=HibernateUtil.getSession();
String hql="from Cost";//HQL语句
Query query=session.createQuery(hql);
List<Cost> list=query.list();//执行查询,返回List集合
session.close(); return list; } }
4)新建TestCostDAO类,使用junit测试
@Test
public void testFindById(){//当get方法没有记录时,返回null
CostDAO costDao = new CostDAOImpl(); Cost cost = costDao.findById(1);
System.out.println(cost.getName());System.out.println(cost.getBaseDuration());
System.out.println(cost.getBaseCost()); System.out.println(cost.getUnitCost());
System.out.println(cost.getDescr()); }
@Test
public void testSave(){//id主键列由Hibernate管理,这里不用设置
Cost cost=new Cost(); cost.setName("2013计时");
cost.setUnitCost(0.8f); cost.setDescr("2013-08-09计时,0.8元/小时。");
cost.setStatus("0"); cost.setCreaTime(new Date(System.currentTimeMillis()));
CostDAO costDao = new CostDAOImpl(); costDao.save(cost); }
@Test
public void testUpdate(){//开通某个资费,把状态由0变为1
CostDAO costDAO=new CostDAOImpl();
/** 注意事项:更新部分字段,不能和实现类中的删除那样,做一个对象出来!否则没设置的字段将被改为空! 即不能:Cost cost=new Cost(); cost.setId(90);
cost.setStatus("1"); cost.setStartTime(new Date(System.currentTimeMillis())); */
Cost cost=costDAO.findById(90);//只能先通过id找到带有所有值的对象
cost.setStatus("1");//然后再对部分字段进行更新,才能避免把其他字段更新为空
cost.setStartTime(new Date(System.currentTimeMillis()));
costDAO.update(cost); }
@Test
public void testDelete(){
CostDAO costDAO=new CostDAOImpl(); costDAO.delete(90); }
@Test
public void testFindAll(){
CostDAO costDAO=new CostDAOImpl(); List<Cost> list=costDAO.findAll();
for(Cost c:list){ System.out.println(c.getName()); } }
2.4 HQL语句(简要介绍)
简要介绍见2.3节中step6中的3)特殊查询(本页最上)。详细介绍见第七章。
七、Hibernate查询方法
7.1 HQL查询
Hibernate Query Language简称HQL。
HQL语句是面向对象的一种查询语言。HQL针对Hibernate映射之后的实体类型和属性进行查询(若出现表名和字段名则为错误的!)。
7.2 HQL和SQL的相同点
1)都支持select、from、where、group by、order by、having子句……。
2)都支持+、-、*、/、>、<、>=、<=、<>等运算符和表达式。
3)都支持in、not in、between...and、is null、is not null、like、or等过滤条件。
4)都支持分组统计函数count、max、min、avg、sum。
7.3 HQL和SQL的不同点
1)HQL区分大小写(除了关键字外)。
2)HQL使用类名和属性名,不能使用表名和字段名。
3)HQL不能使用select * 写法。
4)HQL不能使用join...on中的on子句。
5)HQL不能使用数据库端的函数。
u 注意事项:HQL中select count(*)可以使用。
7.4 HQL典型案例
1)案例1:一个主键的情况
step1:借用之前的Account实体、Account.hbm.xml、hibernate.cfg.xml
step2:新建TestHQL类,用于测试HQL。查询操作可不写事务控制语句。
………………………………………………………………………………
八、Hibernate高级特性
8.1二级缓存
二级缓存是SessionFactory级别的。由SessionFactory对象负责管理。通过Factory创建的Session都可以访问二级缓存。
二级缓存默认是关闭的。如果二级缓存打开,则先去一级缓存找对象,找不到则去二级缓存,还找不到则访问数据库。
u 注意事项:
v 一级缓存和二级缓存都是存“单个对象”的!不能存集合、数组!
v Hibernate本身没有提供组件,需要第三方提供的组件。有很多第三方组件,这里用ehcache-1.2.3.jar。
8.2二级缓存开启方法及测试
step1:引入ehcache-1.2.3.jar,在src下添加ehcache.xml配置文件
ehcache.xml配置文件说明:
<diskStore path="java.io.tmpdir"/><!-- 磁盘存储路径 -->
<defaultCache <!--默认参数设置-->
maxElementsInMemory="2000" <!--存储的最大对象个数-->
eternal="false" <!--缓存对象的有效期,true为永久存在-->
timeToIdleSeconds="20" <!--空闲时间:某个对象空闲超过20秒则清出二级缓存-->
timeToLiveSeconds="120" <!--某个对象生存了120秒,则自动清出二级缓存-->
overflowToDisk="true"<!--当存储个数超出设置的最大值时,把超出对象存到磁盘-->
/>
step2:在hibernate.cfg.xml中添加开启配置参数,指定缓存类型
………………………………………
…………………………
………………
…………
……