hibernate 学习笔记精要
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
使用hibernate编程,继承了面向对象编程的特点,使我们屏蔽了直接的拼sql语句对数据库表进行操作。
JDBC操作数据库很繁琐,sql语句编写并不是面向对象的,可以在对象和关系表间建立关联来简化编程,跨数据库平台,只需改写方言Dialect
学习建立user library->hibernate,并加入相应的jar包:可以在项目右键build path->configure build path->add library选择user library,在其中新建library,命名为hibernate,加入所需的包(hibernate core、required、slf4j jar、log4j.jar)
配置文件hibernate.cfg.xml一般放在src根目录下面,这样当读取这个配置文件时,就可以直接使用configuration.configure()方法,而不需设置这个配置文件所在路径
所有的POJO最好都放到一个package下面,这样与之对应的映射文件*.hbm.xml也都放在了那下面
在hibernate3以后开始支持annotation,目标是建立符合JPA标准的annotation,可以下载与hibernate版本对应的annotation包,引入三个jar包:hibernate-commons-annotations.jar,ejb3-persistence.jar,hibernate-annotations.jar。
new Configuration时,要使用AnnocationConfiguration
使用注解时,如果想要当自己一键入@就给提示,content-assist的activation中加上@符号就可以了。
目前比较流行的ORM框架有hibernate,toplink,jdo,ibatis(“半自动化”的ORM实现,而非“一站式”),jpa;jpa(意愿一统天下)
slf是一个日志框架,针对不同的实现,我们只需引入相关的jar包,比如slf4j nop,log4j, jdk logging, commons-logging,这种方式很像JPA,JDBC
我们可以常常看看project->etc下面的一些文件,主要是些配置文件,我们用的时候可以参考,比如log4j.properties
注解有的是写给javac编译器看的,有的是写给java运行环境看的。
hibernate.cfg.xml中的show_sql,format_sql分别都是在输出信息中输出sql语句,后者是格式化后的效果,便于查看
以xml映射时,除了主键id,其他每个字段都要映射,而以annotation的方式时,只需指定主键,其他默认进行了映射(默认加了@Basic)。
当字段名和类的属性名不对应的时候,在相应的getter方法上添加collumn(name=""),注意都要选择javax.persistence下面的。
不想映射某个字段加入@Transient注解接口
映射日期的时候,如果只想映射精度到日期,可以加入@Temporal(TemporalType.DATE)默认是TemporalType.TIMESTAMP,完整的是(value=TemporalType.DATE),但只要是value=都可以不用写它,简写掉,这只针对类型为java.util.Date
大部分时候(95%)都不需要指定映射的类型,由hibernate默认自动类型映射
映射枚举类型采用@Enumerated方便而使用xml的时候就麻烦了,要定义相关的类型转换器,还需指定类型enumType
字段映射的位置放在field或者get方法,最好保持field和get set方法的一致。
放到field上有点破坏面向对象编程封装机制,因为field一般来说都是私有的,虽然hibernate可以利用反射机制访问到
如果使用sequence的注解来指定生成策略,在类名前加@SequenceGenerator(name="seqGenerator", sequenceName="student_seq")表示声明一个生成器,然后在主键字段上加入注解
@GeneratoValure(strategy=GeneratorType.AUTO, generator="seqGenerator")
key,value是mysql的关键字,需要避免使用
如果使用联合主键,需要使用<composite-id>指定,我们可以将这些联合的属性组合到一个新的类中,这个类需要实现序列化接口,同时最好还要重写equals和hashCode这个时候是为了保证在内存中唯一性
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法
如果是一个联合主键,三种方式:@Embeddable(主键类)@Id; @EmbeddedId; @Id@IdClass 第二种常用
核心开发接口:Configuration进行配置信息的管理,用来产生SessionFactory,可以在configure方法中指定hibernate配置文件(不是以默认的hibernate.cfg.xml保存它的配置文件时要制定),只需关注一个方法buildSessionFactory
sessionfactory里面保存着数据库的连接池,可以使用getCurrentSession方法获得session或openSession方法(3.2以后不建议使用)。
前一个方法如果当前上下文没有session会开启一个新的session,只要调用commit方法再用第一个方法获得的就是新的,用途是界定事务边界
注意getCurrentSession()方法和openSession()方法不能混用,如果混用出错的可能性很大,最常见出错是使用getCurrentSession方法的同时还使用了close方法
上下文依据hibernate.cfg.xml中定义的属性<property name="hibernate.current_session_context_class">thread</property>,还有一种比
较常用的值是jta
thread使用connection去管理,设置auto_commit为false,遇到异常就rollback,没有异常则提交;对于分布式的情况下,多个数据库位于不同的主机上,就涉及到多个connection,往往需要别人为你做一个专门的事务管理器(一般应用服务器提供),管理分布式事务
所以事务分为两种:一种叫做connection事务,依赖数据库本身的;还有一种叫做JTA 事务。
前者只针对一个数据库,后者是分布式环境
hibernate中对象的三种状态:transient;persistent;detached
transient:内存中有一个对象,没ID,缓存中也没有
persistent:内存中有,缓存中有,数据库有ID
detached:内存有,缓存没有,数据库有
session管理一个数据库的任务单元。
hibernate中涉及很多非常细节的区别,但在实际应用中用得很少,请大家先享受些项目的乐趣,再来探讨这些细节问题,比如save和persist的区别,merge和evict等方法,refresh和lock等
load返回的是代理对象,等到真正用到对象的内容时才发出sql语句;get直接从数据库加载,不会延迟;还有就是不存在对应记录时表现不一样,get会立即报错
生成的动态代理类将所获取的实体类作为父类,然后直接产生的是二进制码,使用了javassist
update用来更新detached对象,更新完成后转为persistent状态;更新transient对象会报错,但是更新自己设定好id的transient对象可以;p状态更改部分字段,会检查缓存中的和数据库中的是否一样,如果不一样会立即更新,这实际上是所有字段一起更新
更新部分更改的字段:1.xml设定property标签的update属性,annotation设定@column
的updatable属性,不过这种方式很少用,不灵活;2.使用xml中的dynamic-update,JPA1.0 Annotation没有对应的属性,hibernate扩展?i.同一个sesion可以。
跨session 不行,不过可以用merge(不重要)3.使用HQL
saveOrUpdate方法根据不同情况。
clear方法用于清除缓存(get和load都会先去查找缓存中有没有,没有才去数据库查找)。
Hibernate强的地方就在于,一个PO脱离Session之后,还能持久化状态,再进入一个新的Session之后,就恢复状态管理的能力,但此时状态管理需要使用session.update或者session.saveOrUpdate,简单的来说,update和saveOrUpdate是用来对跨Session的PO进行状态管理的。
clear方法,无论是load还是get,都会首先查找缓存(一级缓存),如果没有,才会去数据库查找,调用clear方法可以强制清除session缓存
flush方法可以强制进行从内存到数据库的同步!
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);这个可以利用注解生成相关的表
xml形式设置一对一外键关联使用many-to-one,设置unique=true,主键关联使用one-to-one。
单向外键关联,一个实体类持有对另一个类的引用,然后在其get方法上设置@one-to-one 只要有双向关联,mappedBy必设,告诉hibernate对方那是主导;在数据库里面单向和双向没区别,但是Java程序里有区别,编程模型的实现
一对一主键关联不重要@PrimaryKeyColumn。
一对一的关系在项目中用得很少
一对一联合主键的时候也实现serializable接口,重写equals和hashCode方法
@JoinColumns(
{
@JoinColumn(name="wifeId",
referencedColumnName="gender"),
@JoinColumn(name="wifeName",
referencedColumnName="name")
}
)
组件映射就是一个类的一部分,那个类具有它的引用,这样实际上是一对一,可以设计到一张表里面。
@Embedded就可以了,为了防止嵌入进来的类中有属性重名,可以对其用@Column(name="")
多对一单向关联数据库表设计是在多方加外键。
多方存有一个一方的对象引用,一方什么也不要做
一对多的单向关联数据库设计和多对一单向关联的表结构是一样的,只是这时候在对象上表现为一方对象存有一个属性保存所有的多方对象,最好用set,这样保证不重复@OneToMany要加上@JoinColmn不然会出现它为我们生成表时按照多对多处理,加了一张中间表
存在外键关联的表时,删除的时候一定要把存在外键的删除,其他表才能删除
老师和学生的关系可以看做多对多关系,需建立一张中间表。
如果需要对中间表改名,
使用@ManyToMany
@ManyToMany
@JoinTable(name="t_s",
joinColumns={@JoinColumn(name="teach_id")},
inverseJoinColumns={@JoinColumn(name="stud_id")} )
其中joinColumns代表的是所在类的id,inverseJoinColumns代表另一个类对应的id
多对多本身就比较少用
级联cascade的ALL,REMOVE和PERSIST用得多一些,它的作用就是使得编程方便,并不是必用不可的东西。
如果A可以导航到B,在A上设置级联,B也会进行级联操作。
很多时候我们需要操作的时候就操作根上的对象就可以影响到它能导航到的对象,简化了操作。
它的属性指明做什么操作时关联对象是绑在一起的
双向关联铁律:双向关系在程序在要设好双向关联;一方采用mappedBy
cascade就是CUD,create,update,delete;fetch负责设置load,get等读取操作,hibernate一对多默认是lazy,多对一默认是eager,就是为了防止两边都去取,提高效率;两边不要同时设eager
树状结构的设计:在同一个类中使用OneToMany和ManyToOne,表结构就是id,parent_id, name
@Entity
public class Org {
private int id;
private String name;
private Org parent;
private Set<Org> children = new HashSet<Org>();
@OneToMany(mappedBy="parent", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
public Set<Org> getChildren() {
return children;
}
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id;
}
public String getName() {
return name;
}
@ManyToOne
@JoinColumn(name="parent_id")
public Org getParent() {
return parent;
}
public void setChildren(Set<Org> children) {
this.children = children;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
= name;
}
public void setParent(Org parent) {
this.parent = parent;
}
}
@Test
public void testShow() {
testSave();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Org org = (Org)session.load(Org.class, 1);
print(org, 0);
session.getTransaction().commit();
}
private void print(Org org, int level) {
String preStr = "";
for (int i=0; i<level; i++) {
preStr += "----";
}
System.err.println(preStr+org.getName());
for (Org o: org.getChildren()) {
print(o, level+1);
}
}
在没有更改属性的时候,或者更改的和原来的一样,即使设置了cascade为all,也不会发出update操作
对多方设置fetch的时候要谨慎,结合具体应用,一般用Lazy不用eager,特殊情况是多方数量不多的时候可以考虑,提高效率的时候可以考虑
要想删除或者更新,先做load,除了精确知道ID之外
O/RMapping编程模型中映射模型有:jpa annotaion, hibernate annotation extension, hibernate xml, jpa xml;编程接口有如jpa的EntityManager, hibernate的Session,所以实际上cascade的类型是和jpa的比较对应的;数据查询语言:HQL,EJBQL
如果想消除关联关系,先设定关系为null,再删除对应的记录,如果不删记录,该记录就变成了垃圾数据
继承映射有三种:single_table;table_per_class;joined实际用得比较少
学生、课程、分数的设计
a)使用联合主键@Embedded,实现Serializable接口 b)不使用联合主键
设计:实体类(表);导航(编程方便);确定了编程方式
NativeSQL>HQL>EJBQL(JPQL1.0)>QBC(Query By Cretira)>QBE(Query By Example)功能由大到小,使用时应根据实际情况选择。
QL应该和导航关系结合,共同为查询提供服务,设了导航写起来就方便些
对于HQL,里面的查询对象都是一个一个的类以及对应的属性,而非数据库中的表和字段,我们可以通过查看其执行的sql语句,因为字面上看有点抽象
编写的hql中可以带有参数,以形如:param的形式,然后紧跟后面以链式操作的形式对其设置参数,这样书写起来比较简洁
Query q = session.createQuery("from Category c where c.id > :min and c.id < :max")
.setInteger("min", 2)
.setInteger("max", 8);
参数还可以以?作为占位符,之后对其进行设值,第一个?编号为0,依次类推:
Query q = session.createQuery("from Category c where c.id > ? and c.id < ?");
q.setParameter(0, 2)
.setParameter(1, 8);
如果只想查询某个对象中的部分属性,可以将其存放在List里面,每个List中为一个Object类型的数组。
也可以组装成一个vo,如
Query q = session.createQuery("select new com.bjsxt.hibernate.MsgInfo(m.id, m.cont, m.topic.title, ) from Msg");
其中MsgInfo就是VO,这种对象就是用来传值的,或者叫做DTO
Query q = session.createQuery("select t.title, from Topic t join t.category c "); //join Category c
上面这个不能写成join Category c的原因是有可能存在多个成员变量(同一个类),需要指明用哪个成员变量的连接条件来做连接
当很多时候如果确定查询出来的结果只有一条记录的时候,可以直接用query.uniqueResult(),而不需使用list()方法去做循环
“from Topic t where t.msgs is empty”其中empty表示测试集合是否为空,前提是在对象模型中加上一对多的导航
EJBQL中还可以使用一些函数,通配符*,_等
用in可以实习exists的功能,但是exists的执行效率要高得多
可以在映射类的时候加入一个@NamedQuery(name="", query=""),将针对这个类的常用查询以命名的形式写在里面,方便改以及使用.还可以将sql语句集中写在配置文件中
如果想使用数据库本身的查询语言,可以session.createSQLQuery("").addEntity(Categary.class),它能提供比较强大的功能
QBC查询就是通过使用Hibernate提供的Query By Criteria API来查询对象,这种API 封装了SQL语句的动态拼装,对查询提供了更加面向对象的功能接口
注意session clear的运用,尤其在不断分页循环的时候。
在一个大集合中进行遍历,遍历msg,取出其中的含有敏感字样的对象;另外一种形式的内存泄露
Java在语法级别上没有内存泄露,垃圾收集器自动帮你回收了。
但是在使用一些资源的时候不用的情况下一定要记得关闭,如打开了文件,获取了数据库连接
1+N问题:一个对象里面关联了另外一个对象,并且fetchType为EAGER,不如说ManyToOne,在查询的时候,由于N对1的一方默认的fetch=FetchType.EAGER,所以会把被关联的对象一起取出来,本来一条sql语句就可以搞定,但是却另外多发了n条sql;注意OneToMany也存在,如果将一方的fetchType为EAGER
解决方法一:设置fetch=ZY,需要时才发sql语句
解决方法二:用session.createCriteria()做查询,而不是用createQuery
或者用join fetch,实际上session.createCriteria()就是连接方式
解决方法三:用@BatchSize(size=5)类上面
list会取所有,iterate先取id,等用到的时候再根据id来取对象,session中list第二次发出,仍会到数据库查询,iterate第二次,首先找session级缓存。
一般情况下用list()
就可以了
缓存就是在内存中开辟一块空间,把本来在硬盘里的东西放到这块空间里,将来再读的时候直接从内存读
hibernate中有三种缓存,分别是一级缓存、二级缓存和查询缓存。
一级缓存就是session 级别的缓存;二级缓存是SessionFactory级别的缓存,可以跨越session存在,
打开二级缓存,使用EhCacheProvider
<property name="e_second_level_cache">true</property>
<property
name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property> 类上面加@Cache里面usage常设成READ_WRITE,name="sampleCache1"同时要注意加入相关的jar包,注意它用的log是commons loggings
经常被访问的,数据量并不大,改动非常少的对象适合放在二级缓存里,如组织机构,用户权限等
load默认使用二级缓存,iterate默认使用二级缓存。
list默认往二级缓存加数据,但是查询的时候不使用(主要是每次查询条件可能不同,没法利用)。
如果要query用二级缓存,需打开查询缓存
<property name="e_query_cache">true</property>
调用Query的setCachable(true)方法指明使用二级缓存
查询缓存指的是查询语句一样的,第二次查的时候可以利用上之前的缓存;它依赖于二级缓存
缓存算法就是指内存中对象存满了,新来的对象去替换掉内存中已有的哪个对象,策略问题,有LRU(Least Recently Used),LFU(Least Frequently Used命中率高低),FIFO (First In First Out)ehcache三种都支持,可以设置一个参数
memoryStoreEvictionPolicy="LRU"
实际工作中起初可以不用考虑,之后性能优化的时候就要详细去探讨了
事务特性包含:原子性(Atomicity)、一致性( Consistency)、隔离性(Isolation)、持久性(Durability)
事务并发可能出现的问题:第一类丢失更新(Lost Update),只要支持事务就不会出现;脏读(Dirty Read),别的事务没有提交的数据;不可重复读(non-repeatable read);第二类丢失更新(second update problem)不可重复读的特殊情况;幻读(phantom read),重点说的是插入和删除操作
多个事务并发引起的问题:
第一类丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。
脏读:一个事务读到另一个事务未提交的更新数据。
幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
第二类丢失更新:不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据。
事务隔离级别:
为了解决多个事务并发会引发的问题。
数据库系统提供了四种事务隔离级别供用户选择。
o Serializable:串行化。
隔离级别最高
o Repeatable Read:可重复读。
o Read Committed:读已提交数据。
o Read Uncommitted:读未提交数据。
隔离级别最差。
数据库系统采用不同的锁类型来实现以上四种隔离级别,具体的实现过程对用户是透明的。
用户应该关心的是如何选择合适的隔离级别。
对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读,而且具有较好的并发性能。
每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别。
JDBC 数据库连接使用数据库系统默认的隔离级别。
在Hibernate的配置文件中可以显示地设
置隔离级别。
每一种隔离级别对应着一个正整数
<property name="hibernate.connection.isolation">2</property>
当数据库系统采用Read Committed隔离级别时,会导致不可重复读和第二类丢失更新的并发问题,在可能出现这种问题的场合。
可以在应用程序中采用悲观锁或乐观锁来避免这类问题
只要数据库支持事务,就不可能出现第一类丢失更新;
hibernate中使用悲观锁的方式,是读取时加入一个参数:LockMode.UPGRADE,其他还有几种是hibernate自己会自动使用的,不用去管
乐观锁的实现是每条记录都增加一个version字段,默认值为0,更新一次会自动加1。
只要检查拿出来的值的version和更新时version的值进行比对,发生变化的话更新会失败。