Hibernate基础教程
isbn: 978-7-115-17165-8/TP
第四章 持久化生命周期
4.1 生命周期简介
瞬时(transient):瞬时对象存在于内存中.Hibernate不管理瞬时对象,也不对瞬时对象的修改进行持久化.
持久(persistent):持久对象存在于数据库中,Hibernate管理持久对象的持久化.如果持久就像上的字段或属性发生了修改,Hibernate就会更新数据库中的对象.
分离(detached):分离对象在数据库中有对应的表示,但是对象的修改不会反应在数据库中,数据库中的修改也不会反映在对象上.
要想将分离对象的修改持久化,应用程序必须对它进行重关联操作(reattach),将它重关联到一个有效的Hibernate会话上.如果应用程序在新的Hibernate会话上调用load()、refresh()、merge()、update()或save()之一,并提供分离对象的引用,分离对象就可以与新的会话建立关联.在调用之后,分离对象就成为了持久对象,由新的Hibernate会话管理.
4.4 实体和关联
如果两个实体中只有一个实体包含对方的引用,这种关联就是单向的.如果关联是相互的,就称其为双向关联.
a.必须显示地管理关联的两端.
b.只有对关联拥有者的修改会反映在数据库中.
c.当从数据库中加载分离的实体时,它会反映数据库中持久化的外键关系.
想让双向关联的一端成为关联的拥有者,就必须给另一端标上inverse=”true”
一对一:任何一端都可以作为拥有者,但是应该选择其中之一(而且只能选择一个)–如果没有指定拥有者,就会导致循环依赖.
一对多:”多”端必须作为关联的拥有者.
多对一:同一对多,”多”端必须作为关联的拥有者.
多对多:任何一端都可以作为拥有者.
4.5 保存实体
save()已经持久化的对象是不合适的.同样,update()瞬时对象也不合适的.如果从应用程序代码判断对象的状态是不可能的,或者不方便,那么可以使用saveOrUpdate()方法.Hibernate使用对象的标识符决定是在是数据库中插入新行,还是更新现在的行.
为了支持EJB 3 EntityManager所需的行为,API中增加了一个persist()方法.这个方法的表现与save()方法相同,但是不保证立刻给实体分配主键值.
4.6对象相等性和同一性
从同一个Hibernate会话重复请求一个持久对象,就会返回一个类的相同java实例.所以可以使用==来比较对象.如果从多个hibernate绘画请求一个持久对象,Hibernate就会从每个会话提供不同的实例,用==会返回false,考虑到这一点,如果要比较不同会话中的对象,就需要java持久对象上实现equals()方法.
4.7加载实体
1.load()和get():除非非常确定对象存在,否则就不应该使用load()方法.如果不确定,那么使用get()方法,如果在数据库没有找到唯一的id,load()方法就会抛出异常,而get()方法仅返回一个null引用.
2.关于使用:
a.使用类作为参数:
session.get(Supplier.class,id);
b.使用实体名作为参数:
String entityName = session.getEntityNanme(supplier);//取得实体名,supplier是个Object
session.get(entityName,id)
3.锁模式:
NONE,READ,UPGRADE,UPGRADE_NOWAIT(第60页)
4.8刷新实体
public refresh(Object object) throws HibernateException
public refresh(Object object,LockMode lockMode) throws HibernateException
4.9更新实体
转存清除模式:ALAWAYS,AUTO,COMMIT,NEVER(第62页)
4.10删除实体:
a.public void delete(Object object) throws HibernateException
参数可以是一个具有标识符的瞬时对象,这个标识符设置为要删除的对象的id.
b.假设有一个父对象,它与一个子对象集合相关联,希望将他们一起删除.那么可以使用Hibernate映射中的cascade属性.如果将cascade属性设置为delete或all,就会删除所有相关联的对象.
c.大批量删除,session.createQuery(”delete from User”).executeUpdate();
与对每个实体标识符单独调用delete()相比,网络通信流会大大减少,内存需求也会大大减少.
大批量删除不会导致级联操作.如果需要级联效果,就需要亲自执行适当的删除操作,或者使用会话的delete()方法.
4.11级联操作
create,merge,delete,save-update,evict,replicate,lock,refresh这些值可以组合在一个逗号分割的列表中,从而允许这些操作的任意组合产生级联效果.如果所有操作都应该级联,那么可以使用简化值all.
另外,一种级联类型是delete-orphan.如果使用delete-orphan,阿么从父对象的集合中删除子对象时,就从数据苦中删除子对象.这种级联只使用于一对多关联.all级联类型不包含delete-orphan,必须使用”all-orphan”.使用如下:
supplier.getProducts().remove(product);
4.12延迟加载、代理和集合包装器
在使用XML映射时,默认启用延迟加载,但是在使用注解时默认不启用。
Hibernate只能通过会话访问数据库。如果试图访问还没有加载的关联,但是实体是分离的(虽然数据库中存在,但没让hiberante管理,譬如session已经close了),那么Hibernate会抛出一个异常:LazyInitializationException.解决方法是让实体再次进入持久状态,或者在实体脱离会话之前访问所需要的所有字段.
如果需要判断一个代理,持久化集合或属性是否已经延迟加载,那么调用org.hibernate.Hibernate类中的isInitialized()和isPropertyInitialized(),还可以调用这个类上的initialize()方法,从而完整地填充一个代理或集合.
看完第七章以后的笔记
1.拥有者的说明的意思是说:
1.实体拥有实体的关系需要手动维护。
2.假设实体所在表中有外键的列,但是它没有手动维护拥有关系,那么这个外键的列中就没有值。
3.session在没有关闭前,实体与实体的关系,是遵从save前对象的关系的。但是新打开一个session后,实体与实体的关系是从数据库中得来的。
譬如:实体A和实体B,1个A拥有多个B,一个B拥有1个A。那么在B表中有个外键的列,来存放相对的A的主键的值。
那么如果手动维护了A拥有B,但是没有维护B拥有A,然后保存A和B。那么B表中的外键的列是null,但是在当前session中,做检索A,是可以检索出A拥有B的信息的。但是session重新打开后,就需要按照数据库得来关系,因为B表中的外键是null,所以就检索不出A拥有B的信息了。
同理,如果维护了B拥有A,但是没有维护A拥有B,然后保存A和B;那么那么B表中的外键的列存放了A的主键。但是在当前session,检索B,可以检索出B拥有A的信息,但是检索A,就检索不出A拥有B的信息(按save前的对象关系)。但是session重新打开后,就需要按照数据库得来关系,于是可以得到A拥有B,也可以得到B拥有A。
所以,正确的做法是应该把实体间的关系都维护了。
2.关于cascade。比如cascade=”all”这个属性,一定是出现在含有【name属性】的元素中的。
譬如:
<set name=”adverts” table=”user_advert_link” cascade=”all”>
<key column=”userid”/>
<many-to-many class=”com.didikeke.bdc.hbm.Advert” column=”advertid”/>
</set>
3.关于一对一,一对多,多对一,多对多的关系。
a.一对一,可以用<component>组合在一张表了,也可以分开在两张表。
b.两张表的一对一。
A的映射:
<one-to-one name=”A” class=”com.didikeke.B” property-ref=”aid”/>
那么property-ref中的aid是B中的外键列,内容是A的主键值。
B的映射:
<many-to-one name=”advert” class=”com.didikeke.A”
column=”aid” unique=”true”/>
column可以加入aid,这样才可以和A中保持一致。
另外,加入unique=”true” 可以让该外键列值唯一,从而变成一对一。
c.在Advert的映射中
<set name=”p” cascade=”all”>
<key column=”advertid”/>
<one-to-many class=”com.didikeke.bdc.hbm.Picture”/>
</set>
这里<key column=”advertid”/>,是Picture中的外键列。
所以在Picture映射中:
<many-to-one name=”advert” class=”com.didikeke.bdc.hbm.Advert”
cascade=”all” not-null=”true” column=”advertid” />
column=”advertid” 来保持一致。
d.在User的映射中:
<set name=”adverts” table=”user_advert_link” cascade=”all”>
<key column=”userid”/>
<many-to-many class=”com.didikeke.bdc.hbm.Advert” column=”advertid”/>
</set>
多对多的关系存放在了表user_advert_link中,<key column=”userid”/>是user在user_advert_link中的外键列。
many-to-many中的column=”advertid”是Advert在user_advert_link中的外键列。
4.对继承关系进行映射
有三种
一.每个具体类占一张表来表示其所有属性。
譬如:
<hibernate-mapping>
<class name=”com.didikeke.Property”>
<id name=”id” type=”int”
<generator class=”native”/>
</id>
<!–title是从父类继承的属性,但是也要做映射,这样在能在Property表中有这样的字段 –>
<property name=”title” type=”string”/>
<!–state是Property特有的属性 –>
<property name=”state” type=”string”/>
</hibernate-mapping>
二.每个子类一个表,该表仅表示该子类特有的属性。
譬如:
<joined-subclass name=”com.didikeke.bdc.hbm.Property”
extends=”com.didikeke.bdc.hbm.Advert”>
<key column=”advertid”/>
<property name=”state” type=”string”/>
</joined-subclass>
这样Property的继承自Advert的属性就存在Advert表中。属于Property特有的属性就存放在Property表中。另外在Property表中有外键列advertid,根据【Advert主键】和【Property的advertid外键列】有相同的值,于是就找到了Property的所有属性。
三.所有的类层次都在一个表中。
<subclass name=”com.didikeke.bdc.hbm.Property”
extends=”com.didikeke.bdc.hbm.Advert”
discriminator-value=”property”>
<property name=”state” type=”string”/>
</subclass>
其中discriminator-value=”property”将会在【区分列】中,存放property,来代表是Property类。
同时,还需要在Advert中加入:
<discriminator column=”classType” type=”string”/>来声明【区分列】。
第八章 使用会话
1.acid:原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(duability)
2.jdbc和hibernate支持的隔离级别:
0 None 允许任何操作;数据库或驱动程序不支持事务
1 Read Uncommitted 允许脏读、不可重复读和幻象读
2 Read Committed 允许不可重复读和幻象读
3 Repeatable Read 允许幻象读
4 Serializable 必须绝对遵守规则
脏读(dirty read):会看到未提交事物的修改。
不可重复读(nonrepeatable read):对于同一个查询会看到不同的数据。
幻象读(phantom read):对于同一个查询会看到不同数量的行。
3.使用特定的隔离级别:P151
1.全局设置:hibernate.connection.isolation(配置)
2.局部设置:session.connnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
当然使用的时候最好把把隔离级别先保存下来,用来之后恢复原本的级别。
int isolation = session.connection().getTransactionIsolation();
4.锁
为了防止同时访问数据,数据库本身要求为数据上锁。可以只在短时数据操作期间持有锁,也可以一直持有到事务结束。前者称为乐观锁(optimistic locking),后者称为悲观锁(pessimistic locking)。
Read Uncommitted隔离级别总是使用乐观锁,Serializable隔离级别只使用悲观锁。
LockMode对象控制这个细粒度的隔离。它只用于get()方法,所以功能有限,功能有限,但比前边的直接控制隔离级别要好.
模式 描述
NONE 只有在缓存中找不到任何对象时,才从数据库中读取.
READ 忽略缓存的内容,都从数据库中读取.
UPGRADE 对于要访问的数据,获得方言特定的升级锁(如果能够从数据库获得的话)
UPGRADE_NOWAIT 于UPGRADE相似,但是如果数据和方言支持的华,在出现锁异常时,方法会立即失败.如果没有这个选项,或者数据库不支持它,查询就必须等待获得锁(或者等到超过时间限制)
WRITE 内部使用,当Hibernate已经在当前会话中写入一行时,它会使用这个模式.不能显式设置这个模式.但是调用getLock时可以返回它.
5.缓存P157
第九章 搜索和查询
1.HQL
a.在控制台输入的SQL中加入注释.
在Query的对象上调用setComment()
b.命名参数
防止SQL注入攻击(injection attack)
使用:
String hql = “from Product where price > :price”;
Query query = session.createQuery(hql);
query.setDouble(”price”,25.0);//如果是实体的话,那么setEntity();
List results = query.list();
c.如果select是列,那么在结果集中返回的是Object数组.
d.分页
query.setFirstResult(1);
query.setMaxResults(2);
query.list();
2.HQL和SQL命名查询
在映射文件中,加入<query name=”com.XXX”><![CDATA[select product.price from Product product]]></query>
或者 <sql-query name=”com.XXX.”>
<return-scalar column=”price” type=”double”/>
<![CDATA[
select product.price from Product as product]]>
</sql-query>
3.使用原生SQL
String sql=”select avg(product.price) as avgPrice from Product product”
SQLQuery query = session.createSQLQuery(sql);
query.addScalar(”avgPrice”,Hibernate.DOUBLE);
List results = query.list();
第十章 使用条件的高级查询
1.Criteria API
例1:
Criteria crit = session.createCrieria(Product.class);
crit.add(Restrictions.like(”name”,”Mou%”));
List results =crit.list();
例2(关联和排序):
Criteria crit = session.createCrieria(Product.class);
Criteria suppCrit=crit.createCriteria(”supplier”);
suppCrit.add(Restrictions.eq(”name”,”MegaInc”));
crit.addOrder(Order.desc(”name”));
List results=crit.list();
例3(投影和统计):
Criteria crit = session.createCrieria(Product.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.max(”price”));
projList.add(Projections.min(”price”));
projList.add(Projections.avg(”price”));
projList.add(Projections.countDistinct(”description”));
projList.add(Projections.rowCount());
crit.setProjection(projList);
List results = crit.list();
2.QBE
例:
Criteria crit = session.createCriteria(Product.class);
Product exampleProduct = new Product();
exampleProduct.setName(”Mouse”);
exampleProduct.setPrice(12.0);
Example example = Example.create(exampleProduct);
example.excludeZeroes();//忽略值为0的列
example.excudeProperty(”price”);//忽略price列
example.enableLike();//使用like方式
crit.add(example);
List results =crit.list();
第十一章 对搜索结果进行过滤
在映射文件中加入:
<class …
<filter name=”activatedFilter” condition=”:activatedParam = activated”/>
</class>
<filter-def name=”activatedFilter”>
<filter-param name=”activatedParam” type=”boolean”/>
</filter-def>
在代码中使用:
Filter filter = session.enableFilter(”activatedFilter”);
filter.setParameter(”activatedParam”,new Boolean(true));
session.beginTransaction();
List list=session.createQuery(”from User”).list;
session.getTransaction().commit();
附录A
A.1 EJB 3 和EntityManager
A.7 调用存储过程 call
A.8 事件
例:
Configure config = new Configuration();
config.setListener(”save-update”,new BookingSaveOrUpdateEventListener());
根据”save-update”,,所以seesion的saveOrUpdate();会被BookingSaveOrUpdateEventListener监听
BookingSaveOrUpdateEventListener的实现:
继承DefaultSaveOrUpdateEventListener,覆盖public Serializable onSaveOrUpdate(SaveOrUpdateEvent event) throws HibernateException
详细在p214
A.9拦截器
config.setInterceptor(new BookingInterceptor());
BookingInterceptor实现Interceptor接口即可.
附录C Hibernate和Spring
C.3
使用HibernateTemplate类实现getAll方法(DAO类应当首先继承了HibernateDaoSupport类)
public List<paper> getAll(){
return (List<Paper>)getHibernateTemplate().find(”from Paper”);
}
核心HibernateTempate方法
bulkUpdate() 、contains()、delete()、find()、get()、persist()、refresh()、save()、saveOrUpdate()、update()
如果所需要的操作比较复杂,无法在一行中执行,就使用execute方法调用一个HibernateCallback实例。
public Paper createArticle(final Integer paperId,final Article article){
HibernateCallback callback = new HibernateCallback(){
public Object doInHibernate(Session session){
Paper paper =(Paper)session.get(Paper.class,paperId);
paper.addArticle(article);
session.update(paper);
return paper;
}
};
return (Paper)getHibernateTemplate().execute(callback);
}
但是上边的代码比较复杂,可以使用没有使用HibernateTemplat的办法,如下:
public Paper createArticle(final Integer paperId,final Article article){
Session session = getSession();
Paper paper =(Paper)session.get(Paper.class,paperId);
paper.addArticle(article);
session.update(paper);
releaseSession(session);
return paper;
}
C.4 声明式事务
如果在代码中,比如上边的createArticle,显式的进行session.beginTransaction()之类的事务管理,那么在使用OpenInViewInteceptor(是什么意思呢??在什么书上讲了?)或OpenInViewFilter会让显式管理事务的代码失常。在使用HibernateTemplate时,也有相同的问题。所以应该使用声明式事务管理。,可以将bean的方法标为事务的边界。
第一步:
<bean id=”transactionManager” class=”org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=”sessionFactory” ref=”sessionFactory”/>
</bean>
其中sessionFactory是声明在spring配置文件中的hibernate的seesionFactory的Bean。
第二步:
<bean id=”daoTxTemplate” abstract=”true” class=”org.springframework.transaction.interceptor.TransactionProxyFactoryBean”
<property name=”transactionManager” ref=”transactionManager”/>
<property name=”transactionAttributes”>
<props>
<prop key=”create*”>
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED
</prop>
<prop key=”get*”>
PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED
</prop>
</props>
</property>
</bean>
即:对create开头或者get开头的方式进行事务管理。p.s.PROPAGATION_REQUIRED不知道是什么意思。
第三步:
<bean id=”paperDao” parent=”daoTxTemplate”>
<property name=”target”>
<bean class=”com.didikeke.PaperDaoImpl”>
<property name=”sessionFactory” ref=”sessionFactroy”/>
</bean>
</property>
</bean>
C.5管理会话
在使用Hibernate时,经常会遇到的一个问题是LazyInitializationException .在试图访问一个游离的实体的延迟加载的属性时(通常是由于加载这个实体的会话已经关闭了),就会发生这种异常。
在默认的情况下,在任何HibernateTemplate方法完成时(如果不使用HibernateTemplate,就是在释放会话时),HibernateDaoSupport派生类的DAO会关闭会话。因此,已经从DAO获得的实体会变为游离实体。在此之后,如果把他们传递给一个视图(譬如jsp),,此时再访问原来DAO方法完成之前没有访问过的任何延迟加载的实体属性时,就会产生LazyInitializationException。
因为,将实体的所有属性都标为即时加载的话,也不是很现实。有时候,不可能事先精确派断应该主动加载哪些实体。
所以,Spring提供了一种实现OpenSessionInView行为模式的机制。在确保在视图处理完成之前一直保留Session对象。在处理完成之后,就会关闭会话(必须在某个时候关闭会话,从而确保应用程序不会保留无用的会话,p.s.要是一直不释放,难保会话可能不够用。。)
<bean name=”openSessionInViewInterceptor” class=”org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor”>
<property name=”sessionFactory” ref=”sessionFactory”/>
<property name=”singleSession” value=”true”/>
</bean>
singleSession为ture是默认选项,表示使用单一Session对象,高效,但是有可能有副作用,尤其是在不使用声明式事务的情况下(俺暂时不理解是什么样的副作用)。如果设置为false,那么对于每个DAO操作需要获得单独的会话。