hibernate v 3.2.4

hibernate的一些基础

三张表
Bookinfo (bookid, bookname, bookprice, pubulisid)
Public (publishid, publishname)
Auth (authid, authname, authage)


bookinfo 和 auth 是一对一的关系, 共享主键
在bookinfo中
<one-to-one name="auth" class="com.softeem.book.pojo.Auth" />
在auth中
<id ....
  <generator class="foreign">
   <param name="property">info</param>
  </generator>
</id>
<one-to-one name="info" class="com.softeem.book.pojo.BookInfo" />

一对一, 外键关联
bookinfo中不变
auth中
<id ....
  <generator class="uuid.hex" / > 无需foreign
</id>
<many-to-one name="info" column="BOOKINFOID" class="com.softeem.book.pojo.BookInfo"
  unique="true" <!-- 这句关键,表示唯一的多对一,也就变成一对一,而且auth表必须有BOOKINFOID这个字段 -->
/>


bookinfo 和 publish 是多对一的关系
在bookinfo中
<many-to-one name="publish" column="PUBLISHID" class="com.softeem.book.pojo.Publish" >
</many-to-one>
在publish中
<set name="books">
  <key column="PUBLISHID" />
  <one-to-many class="com.softeem.book.pojo.BookInfo" />
</set>

 
在以上这种配置情况下
java代码:
BookInfo book = (BookInfo)session.get(BookInfo.class, "123"); //数据库有该数据, 并且有对应的publish和auth数据
根据测试, bookinfo对auth的策略是预先抓取, bookinfo对publish是延迟加载
由此可见,在一对一中默认是预先加载
多对一中默认是延迟加载, 一对多默认是延迟加载


现在开始试验各种配置
bookinfo 对 auth
<one-to-one name="auth" class="com.softeem.book.pojo.Auth" lazy="proxy|no-proxy|false" />
lazy每种都试了,发现都是预先抓取,将constrained设为true就变成延迟加载了,得知这里lazy的无效
由于一对一默认是预先加载,若要是实现延迟加载,则需要设置constrained为true,并将一对一另一端的class设为lazy="true",
在这里即是将Auth的class的lazy="true",因为lazy默认为true,可以不写
<one-to-one name="auth" class="com.softeem.book.pojo.Auth" constrained="true"/>

bookinfo 对 publish, 设置lazy="false" 预先抓取,设置lazy="proxy|no-proxy", 延迟加载
<many-to-one name="publish" column="PUBLISHID" class="com.softeem.book.pojo.Publish" lazy="false" >
</many-to-one>


测试session的save方法
BookInfo info = new BookInfo();
info.setBookId("34534");
info.setBookName("oh yeah");
info.setBookPrice(12);
session.save(info);
若如下设置,会报异常, 将constrained设为false, 或去掉constrained属性 则可以
<one-to-one name="auth" class="com.softeem.book.pojo.Auth" constrained="true" />


session的load和get方法
get方法:
通过id在session缓存中查找对象,未找到,则在二级缓存中查找,若未找到,则从数据库中查找(立即查找)
若无对应数据,,则返回null
load方法:
需要分两种情况
立即加载对象,如 <class name="Publish" table="PUBLISH" lazy="false".....>
同get方法一样,唯一的区别在于数据库若无对应数据,则会抛出异常。
延迟加载对象,如 <class name="Publish" table="PUBLISH" lazy="true".....> (lazy默认为true)
同get方法基本一样,区别在于若在二级缓存中未找到,不会立即去查数据库,而是返回一个代理对象,直到这个对象被访问时,
才去查找数据库。


对象级联操作
一对一情况
cascade属性
cascade="none|save-update|delete|all|delete-orphan"
将cascade设置为none时
无法将临时对象保存到session中去,例如
Publish publish = (Publish)session.get(Publish.class, "999");  //存在记录
BookInfo info = new BookInfo();//bookid 是 uuid
info.setBookName("lol");
info.setBookPrice("lol");
publish.getBooks().add(info);
将一个临时对象通过这种方式保存到session中,会报异常。

将cascade设置为save-update时
以上代码不变,添加bookinfo成功。
但有一点得说一下这个代码
BookInfo book = (BookInfo)session.get(BookInfo.class, "123"); //存在记录
publish.getBooks().remove(book);
就算配置了save-update,这句代码并没有去删除数据库bookinfo对应的记录,而是做了update操作
将这条bookinfo的记录的publishid设为null,即剥离了改条bookinfo与publish的关系。
紧接上面代码接着写
book.setBookName("update name");
.....//提交事务
会发现数据库改条记录的bookname已经变为update name, 说明虽然剥离的与publish的关系,但它依然是一个持久化对象。

将cascade设置为delete-orphan时
这个属性要还是用到上面的代码,当执行publish.getBooks().remove(book);后,会发现首先这个book变为脱管对象,
而且数据库对应的这条记录已经被delete了。

将cascade设置为delete时
若删除publish,则对应的bookinfo都会被删除。
值得一提的, 若在publish的set里面没有设置inverse属性(默认为false),则hibernate在执行时,会先剥离publish与其
对应的所有bookinfo的关系,即update bookinfo set publishid = null where publishid=?
然后再执行delete,中间这个update显得很多余,若将inverse设为true,则这句update就省了。

将cascade设置为all时
包含save-update和delete

 


一对多,多对一情况
bookinfo和publish
例如,新增一个bookinfo记录
可以使用"publish添加bookinfo" 或是 "bookinfo添加publish"
一般思路会首先想到前者:
bookinfo的many-to-one中设置cascade="none"
publish的set中设置cascade="save-update"
代码如下
Publish publish = (Publish)session.get(Publish.class, "999");   //存在记录
BookInfo info = new BookInfo();
info.setBookName("lol");
info.setBookPrice(99);
publish.getBooks().add(info);
Hibernate会首先查询该publish,然后查询该pubilshid对应的所有bookinfo,然后再insert该bookinfo,但此时的publishid还是null,
所以后面接着一句update给publishid赋值,建立与publish的关系。
若将publish的set中设置inverse="true"(inverse为true表示双方的关系交由对方维护,这里即是bookinfo)
再执行以上代码,最后的insert和update会合成一句insert,但此时双方的关系由bookinfo维护,Hibernate不会再根据publish
中的publishid去关联bookinfo的publishid,所以该bookinfo记录的publishid为null。
需要修改代码如下
Publish publish = (Publish)session.get(Publish.class, "999");   //存在记录
BookInfo info = new BookInfo();
info.setBookName("lol");
info.setBookPrice(99);
publish.getBooks().add(info);
info.setPublish(publish);
则会给publishid赋上值,但依然是查询了该publisid对应的所有bookinfo记录。

不如使用"bookinfo添加publish"的方式
Publish publish = (Publish)session.get(Publish.class, "999");   //存在记录
BookInfo info = new BookInfo();
info.setBookName("lol");
info.setBookPrice(99);
info.setPublish(publish);
session.save(info);
Hibernate先查询该publish,然后进行insert。效率相当高

还有种情况,若想清除publish中对应的bookinfo集合,并赋一个新集合,
BookInfo info = new BookInfo();
info.setBookName("lol");
info.setBookPrice(99);
Set set = new HashSet();
set.add(info);
Publish publish = (Publish)session.get(Publish.class, "999");   //存在记录
publish.getBooks().clear();  //清除bookinfo集合
publish.getBooks().addAll(set);
info.setPublish(publish);
此时publish的<set cascade="save-update,delete-orphan" inverse="true">.....</set>
未做尝试


多对多情况
因为要描述多对多情况,将原来的表关系做一些改变,将bookinfo和auth改为多对多(本来就应该这样-_-),
多对多关系必须新建一张表BOOKINFOAUTH(BOOKINFOID, AUTHID), 在hibernate中无需新建xml和po来对应
那么bookinfo中原来的one-to-one注释掉
<set name="auths" table="BOOKINFOAUTH"> <!-- 这里table就对应关系表,name会在bookinfo的po中生成一个set集合的属性 -->
  <key column="BOOKINFOID" />  <!-- 这里搞了半天,这个column应该是关系表中对应bookinfo主键那个字段,即BOOKINFOID, 而不是bookinfo的主键BOOKID-->
  <many-to-many column="AUTHID" class="com.softeem.book.pojo.Auth" />
  <!--
    这里多对多的class就是表示当前的bookinfo与谁多对多,这里即AUTH,
    column就是关系表中对应auth主键的那个字段,而不是auth表的主键
  -->
</set> 
而auth中的配置是一样的
<set name="bookinfos" table="BOOKINFOAUTH">
 <key column="AUTHID"></key>
 <many-to-many column="BOOKINFOID" class="com.softeem.book.pojo.BookInfo" />   
</set>
添加两者间的关系,bookinfo和auth表都无需修改,只需在bookinfoauth中新增一条记录
代码
BookInfo info = (BookInfo)session.get(BookInfo.class, "oooooo");  //记录存在
Auth auth = (Auth)session.get(Auth.class, "123");   //记录存在
auth.getBookinfos().add(info);
info.getAuths().add(auth);
执行后会发现在关系表中添加了两条一样的记录,这是因为现在双方都有控制权,所以上面两句会重复insert两句一样的记录
一般会将关系表的两个字段做联合主键,那这时则会报错
解决的办法就是让一方释放控制权,使用inverse属性,在任一个set中设inverse为true, 即释放控制权,
则只有另一方才能insert,则不会造成重复。
删除关联关系
BookInfo info = (BookInfo)session.get(BookInfo.class, "oooooo");
Auth auth = (Auth)session.get(Auth.class, "123");
auth.getBookinfos().remove(info);  //这里我将bookinfo的set设为了inverse="true", 这会删除数据库对应的记录

在多对多中cascade不能设delete或all, 因为有两方面的对应关系。


事务
Hibernate自身不提供事务功能,是直接使用JDBC或JTA事务,默认是JDBC
配置JTA
<property name="hibernate.transaction.factory_class">
net.sf.hibernate.transaction.JTATransactionFactory
</property>
<!-- net.sf.hibernate.transaction.JDBCTransactionFactory -->
两者的不同在于
JDBC:
hibernateUtil.getSession()相当于JDBC的Connection.getConnection()
session.beginTransaction()相当于JDBC的conn.setAuto(false) //因为JDBC默认是自动提交模式,即每一个sql语句执行后事务将被提交,
而后一个新的事务又开始了。
tx.commit()相当于JDBC的conn.commit()
session.close()相当于JDBC的conn.close();
以上是JDBC的模式,可以看出先创建session,再获得事务(transaction), 即事务从session中获得。事务提交必须关闭session。
而JTA的模式是先获得事务,再启动session,关闭session,提交事务。
因此JTA的事务生命周期长于session,所以JTA一般用来管理跨session的长事务,分布式的数据库等

事务隔离级别:
隔离级别                     是否出现第一类丢失更新   是否出现脏读   是否出现虚读   是否出现不可重复读   是否出现第二类丢失更新
1:读操作未提交(Read Uncommited)        否                    是            是                 是                  是
2:读操作已提交(Read Commited)          否                    否            是                 是                  是
4:可重复读(Repeatable Read)            否                    否            是                 否                  否
8:可串行化(Serializable)               否                    否            否                 否                  否

第一类更新丢失:后发生的事务回滚覆盖了前面提交成功的事务
脏读:后发生的事务成功提交覆盖了前面的回滚的事务
虚读:后一个事务在前后两次读取数据时由于前面的已提交的事务进行了插入操作,而使数据统计前后不一致。
不可重复读:后一个事务在对同一条数据前后两次读取时由于前面的已提交的事务进行了对这条数据更新操作,而使数据前后不一致。
第二类更新丢失:后发生的事务成功提交覆盖了前面的成功提交的事务

读操作未提交显然太不安全。可串行化又过于安全,影响并发性能,性价比最好的是读操作已提交。

在hibernate中设置
<property name="hibernate.connection.isolation">4</property>
这是全局性的配置


事务功能靠的是锁(lock)
分为乐观锁和悲观锁
悲观锁悲观地认为每次资料存取时,其他的客户端也会取同一笔数据。
当一个事务开始直到结束,这把锁会禁止其他进程对它所保护的数据进行修改(只读)。
一般数据库不采用这种方式,性能太差,使用方式
Transaction tx = session.beginTransaction();
BookInfo info = (BookInfo)session.get(BookInfo.class, "oooooo", LockMode.UPGRADE); 
info.setBookPrice(info.getBookPrice() + 1);
session.update(info);
tx.commit();
在通过session.get()方式查找这个记录时的sql语句是这样的
select ........ from BOOKINFO bookinfo0_ where bookinfo0_.BOOKID=? for update
会在后面加个for update 即是悲观锁,也就查找到后直到事务结束,记录都会被锁,处于只读状态
LockMode.UPGRADE:不管Hibernate的缓存中是否存在指定对象,总是通过select语句到数据库中加载该对象;
         如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的指定对象是否和数据库中的版本一致。
        如果数据库系统支持悲观锁就执行select。。。for update语句,如果数据库系统不支持悲观锁,就执行普通的select语句。
LockMode.UPDGRADE_NOWWAIT:是利用oracle的for update nowait 子句加锁
Hibernate加锁的三种方式:Query  Criteria  Session
Hibernate还有三种内部加锁模式:
LockMode.NONE:无锁机制。   
LockMode.READ:不管Hibernate的缓存中是否存在指定对象,总是通过select语句到数据库中加载该对象;
       如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的指定对象是否和数据库中的版本一致。 
LockMode.WRITE:当Hibernate向数据库保存或者更新一个对象时,会自动使用此模式。
在程序中不能指定内部模式
session.get() session.load() 默认是LockMode.NONE

乐观锁乐观地认为资料的存取很少发生同时存取的问题,所以不作数据库层次上的锁定,而是使用应用程序上的逻辑实现版本控制解决
在不实行悲观锁策略下,数据不一致一旦发生,一种采用先更新为主,一种采用后更新为主,比较复杂的是检查发生变动的数据来实现
默认是以后更新为主。

复杂的方式:
1.使用版本检查
Hibernate通过版本号检查来实现后更新为主,在数据库中加入一个version字段记录,在读取数据库连同版本号一同读取,并在更新时
比较版本号与数据库中的版本号。
在bookinfo里加个version字段,类型为number,初始值为0
在配置里
<class name="BookInfo" table="BOOKINFO" lazy="true" optimistic-lock="version">
紧接id后面配置<version name="version" column="VERSION" type="integer" />
那么在事务提交更新数据库时会检查版本号,若版本号一样则更新,若不一样则抛出异常。
问题:无法防止人为的修改版本号行为
2.使用时间戳
与版本检查原理一样,加个字段LASTUPDATEDTIME,类型为Date。
配置里
<class name="BookInfo" table="BOOKINFO" lazy="true" optimistic-lock="version">
紧接id后面配置<timestamp name="lastudpateTime" column="LASTUPDATEDTIME" />  
优点:Hibernate会自动根据当前时间更新时间戳。
但稍微不安全,若在同一个毫秒级别上并发,则版本检查会无法判断。


Oracl对Blob和Clob的处理
以publish举例,descip为clob字段, photo为blob字段
说明:因为在Oracce中Blob/Clob字段独特的访问方式,Oracle Blob/Clob字段本身拥有一个游标(cursor) ,
JDBC必须通过游标对Blob/ Clob字段进行操作,在Blob/Clob字段被创建之前,我们无法获取其游标句柄,
这也就意味着,我们必须首先创建一个空Blob/Clob字段,再从这个空Blob/Clob字段获取游标,
写入我们所期望保存的数据。
例子
Clob:
写操作
Publish publish = new Publish();
publish.setPublishid("333");
publish.setPublishname("000000");
publish.setDescip(Hibernate.createClob(" "));  //这里必须先写个空字符的字符串
session.save(publish); 
tx.commit();
--------------
Publish pub = (Publish)session.get(Publish.class, "333", LockMode.UPGRADE);
//这里必须用悲观锁 锁定 ,否则会报"未锁定含有 LOB 值的行"
SerializableClob cl = (SerializableClob)pub.getDescip();
CLOB clob= (CLOB)cl.getWrappedClob();
Writer write = clob.getCharacterOutputStream();
write.write(str.toString());
write.flush();
write.close();
session.saveOrUpdate(pub);
tx.commit();

读操作
Publish publish = (Publish)session.get(Publish.class, "333");
Reader in = publish.getDescip().getCharacterStream();
BufferedReader reader = new BufferedReader(in);
StringBuffer sb = new StringBuffer();
while ((str = reader.readLine()) != null){
  sb.append(str);
}
System.out.println(sb.toString());

Blob:
写操作
Publish publish = new Publish();
publish.setPublishid("555");
publish.setPublishname("000000");
publish.setPhoto(Hibernate.createBlob(new byte[1]));  //这里必须先写个空字节
session.save(publish); 
tx.commit();
--------------
Publish publish = (Publish)session.get(Publish.class, "555", LockMode.UPGRADE);
//这里一样必须用悲观锁 锁定 ,否则会报"未锁定含有 LOB 值的行"
SerializableBlob sb = (SerializableBlob)publish.getPhoto();
BLOB blob = (BLOB)sb.getWrappedBlob();
OutputStream out = blob.getBinaryOutputStream();
FileInputStream file = new FileInputStream("D://11.jpg");
byte[] bt = new byte[10240];
int len;
while((len = file.read(bt)) != -1){
   out.write(bt,0,len);
}
out.flush();
file.close();
out.close();

读操作
Publish publish = (Publish)session.get(Publish.class, "555");
InputStream in = publish.getPhoto().getBinaryStream();
FileOutputStream out = new FileOutputStream("d://44.jpg");
byte[] buf = new byte[10240];
int len;
while ((len = in.read(buf)) != -1){
   out.write(buf, 0, len);
}
in.close();
out.close();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值