hibernate小结
1.lazy(true,false):是否延迟加载。通常设为true
1)由spring容器管理业务层方法a的事务,那么在a中也就是一个事务范围内(超过了事务范围就commit了),对象是可以lazyload的,若对象到了表现层由于出了事务的范围已经commit,session已经关闭了就不能lazyload,若需要在表现层lazyload使用opensessioninview解决。
2)压力不大的情况可以使用opensessioninview,压力教大的情况不要使用延迟加载,开始就初始化填充调用Hibernate.initialize(parent.getChildren())或者parent.getChildren().size()。
2.get()和load():最主要的区别在于返回的是否是代理对象。通常使用load。
1)load()取对象时若不使用对应的属性值,那么返回的是一个代理对象。当session关闭后再取属性值会报session关闭异常,因此使用此方法时,需要获取对象的数值时先使用Hibernate.initialize()初始化。此方法还会利用二级缓存。当使用opensessioninview时,不用Hibernate.initialize()初始化,session还未关闭,代理对象还能通过session获取数值。(跟延迟加载很像)
2)get()返回的是带有属性值的对象。
3.inverse(true,false):双向关联时由“谁”作为主控方来维护“外键”。
⊙ 1对多双向关联通常1控制反转,让多方来维护。
1(parent)对多(children)关系中:
1)1对应的集合设置inverse为false,表示不控制反转,也就是1自己作为主控方维护关系,表现在代码中: parent必须setChildren(),表示parent“知道了”children的信息(但children"不知道"parent信息),此时由1作为主控方维护之间的外键。
s.save(parent)时(已经设置了级联更新)sql操作:插入parent,插入不带外键的children,更新children外键为parent的id;
2)1对应的集合设置inverse为true,表示控制反转,也就是”多“作为主控方维护关系,表现在代码中: parent要setChildren(),每个children也要setParent(),表示children“知道了”parent的信息,即知道了parent的id信息并将其作为自己的外键。
s.save(parent)时(已经设置了级联更新)sql操作减少为:插入parent,插入带外键的children。
3)只要设置了inverse必然是双向关联,不可能是单向关联,因为双向关联才有互相设置对方的可能:parent.setChildren(),children.setParent()。双向关联的双方类中都必须有对方的对象或对象的集合作为属性:parent类中必须有children的集合,children类中必须有parent对象。
⊙ 多对多双向关联通常被动方控制反转,让主动方来维护外键。
多(user)对多(role)举例:
1)user为主控方,role为被动方。从业务上来分析角色(role)是用户(user)的一个属性,而角色相对于用户来说类似一个数据字典一样的基础数据。因此何为主动何为被动是从业务角度去分析。
2)从业务角度去分析。一般主动方有一个明显特征是会级联的更新操作被动方,例如修改用户信息时会去修改这个人具有的角色。而更新操作被动方时由于角色类似基础数据,更新的应该是角色其本身的信息,不会在修改角色时还要修改对应的用户。role——authority这两者多对多的业务中,role又是主动方,authority是被动方。
3)多对多双向必须主动方维护外键,否则若让被动方维护外键,在级联更新,级联删除时外键将不会得到更新和删除。
4.cascade(all-delete-orphan,all,save-update,delete,none):是否需要级联更新。
1)save-update:表示级联增改。
2)all:表示级联增删改。但是在没设置控制反转时,删除仅表示取消关联(update外键NULL,形成孤儿)而不是真正删除关联对象。若设置了控制反转,可以直接删除,不会形成孤儿。
3)all-delete-orphan:同all一样,但删表示真正删除。对于上面孤儿,设置此项可真正删除。
因此不同情况时孤儿问题的解决方法(外键NULL问题): 在双向关联时尽量使用inverse="true"来避免;在单向关联时使用all-delete-orphan来删除孤儿(外键已经设置为NULL的记录)。
通常:
①多对多关系应使用cascade="save-update",例如:user-role多对多,删除user时不会删除role,但会删除userrole关联表中的关联记录。
②1对多双向关联1这一方设置了<set inverse="true">时应使用cascade="all"。
③1对多单向关联,单向关联不能设置控制反转,应使用cascade="all-delete-orphan"。
5.fetch(select,join):获取一个对象时,如何获取非lazy的对象/集合。lazy和fetch是不能同时设置的他们是互斥的,即出现lazy=“true”就不能出现fetch=“xx”;出现fetch=“xx”就不能出现lazy=“true”
通常在应用中绝大部分的实体或集合,延迟方式抓取数据应该是最佳选择。对于需要一次性加载对象和关联集合的情况,一般使用连接抓取(join)或子查询抓取(subselect)而不使用查询抓取(select)。例如数据字典,权限,角色这种很少改动的数据,可以一次性将其和关联结合读到缓存中。
(1)fetch=“join”,主体对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。仅对get/load有效,对query无效。(1条sql获取主体与集合:select a,b from table1 left join table2 on table1.pid=table2.fid)
(2)fetch=“select”,先查询返回要查询的主体对象,再根据关联对象的id,为每一个对象发送一条select查询,获取关联的对象,形成n+1次查询。(1条sql获取主体,n条sql获取集合:select a,b from table where id=?)
(3)fetch=“subselect”,先查询返回要查询的主体对象,再根据关联外键id,为所有对象只发送一条select查询,获取关联的对象。(1条sql获取主体,1条sql获取集合:select a,b from table where id in (?,?,?...))
6.unsaved-value(null):1对多关联的集合中新增一个pojo,是否持久化到数据库。通常设为null
1是po对象,1对多的集合中新增一个pojo,保存1时此集合中的pojo根据其id是否等于null来判断是否插入db,若等于null表示此对象是transient自有状态,插入db,若不等于null表示此对象是persistent持久状态,由hibernate来管理他是否要更新。
7.dynamic-update,dynamic-insert(true,false):生成update,insert的sql语句时是否包含null字段。通常设为true
运行时比较属性动态生成sql,无法利用sql缓存的preparedStatement,所以适合大容量远程数据库,不适合小型数据库。但是1,2级缓存还是能够提高性能的。
8.cache usage:hibernate二级缓存的事务策略。通常使用下面的前3个
1)使用read-only:应用程序只需读取一个持久化类的实例,而无需对其修改。例如省份,城市等数据字典。
2)使用nonstrict-read-write:如果应用程序只偶尔需要更新数据,也就是说,两个事务同时更新同一记录的情况很不常见,也不需要十分严格的事务隔离。例如user—role—privilege—resource权限管理中的privilege,resource是仅仅会偶尔更改的。
3)使用read-write:如果应用程序需要更新数据但不频繁(频繁修改不应该缓存)。例如user—role—privilege—resource权限管理中的user,role是会较少更改的。
4)使用transactional:对事务要求苛刻的系统例如金融,这样的缓存只能用于JTA环境中,ehCache不支持此策略。
9.id的generator:通常使用native,identity。
(1)uuid.hex:按一定算法生成的数值,几乎不可能重复。性能好。但主键是字符型而不是数值型。
(2)native:hibernate根据db自动选择是identity还是sequence等等。方便db迁移,适合db多变的系统。
(3)sequence:数据库提供的sequence生成。app先select获取主键再insert。效率非常低,尤其是在大数据量插入时。
(4)identity:数据库提供的autoincrement生成。db内部先读主键+1再insert。
(5)increment:应用程序提供的autoincrement生成。多个app维护自己的主键,同时insert同一个db时可能主键重复。
(6)assigned:业务逻辑生成,而上述都有hibernate自动生成,id渗透业务。
10.fetch-size,batch-size:对较大数据量的数据进行操作,此参数影响性能明显。通常都设为50。
1)fetch-size:查时buffer缓冲区大小,每次查询到达缓冲区大小时才返回一次数据。读1w条记录,不设参数则读1w次,设参数为50则只读200次。每批传输的条数增大(1条变为50条),那么传输的次数减少(1w次变为200次),若是appServer和dbServer两台机器间网络传输,此参数对性能提高更明显。
2)batch-size:增删改时buffer缓冲区大小。每次增删改到达缓冲区大小时才提交一次数据。道理同上。
3)mysql不支持,mssql,oracle都支持。此参数并非越大越好,50以上性能可能还会下降,因为每批维护的buffer缓存区过大。
4)用spring配置hibernate的sessionFactory时集中配置hibernate.jdbc.fetch_size和hibernate.jdbc.batch_size,而不必在各个bean中配置。
11.set,list,bag:通常有重复记录用List,没有重复记录用Set。
1)set:a—b(1对多或多对多)业务断定b没有重复元素则使用set。不保持持久化的元素顺序。
2)list:a—b(1对多或多对多)业务断定b会有重复元素则使用list。保持持久化的元素顺序。但表中要增加一个字段用来表示顺序。
3)bag,是一个可重复的set,不保持持久化的元素顺序。本希望即可重复又不用添加一个字段,但是其性能低下不要使用。(插入集合数据到db时,先删除所有集合数据,再将现有数据逐条插入)
12.max_fetch_depth:为单向关联(一对一, 多对一)的外连接抓取(fetch=“join”)抓取最大深度。1对多不起作用。通常让其默认为0,不用管他。
1)对于应用中绝大部分的实体或集合,延迟方式抓取数据应该是最佳选择。也就是说通常对象或集合的加载方式都是lazy的,一次性fetch抓取对象和其关联的集合/对象只在特殊场合使用(例如:相互关联的数据需要缓存,则一次性读取),此参数使用范围很小。
2)若需要一次读取3层关联的所有数据,可以通过代码循环先读取一边。
13.save(),update(),saveOrUpdate(),merge():对象是持久态,那么对象值的所有改变都由容器管理保存到db,否则要通过调用函数显示告知容器保存到db
(1)对象由自由态pojo转成持久态po,用save(obj)。
(2)对象由持久态po转成游离态是pojo后,修改对象值后希望再持久化转成持久态po,用update(obj);此作用不是用来更新db,而是将obj纳入了容器。
(3)不知道对象是自由态pojo还是游离态pojo,都希望持久化转成持久态po,用saveOrUpdate(obj),例如表现层接到业务层传来的一个obj做修改并保存,但不知道是哪个状态的,保存时使用saveOrUpdate()。
(4)对象是po(持久态的),那么修改其值后,不用显示调用save,update,saveOrUpdate任何一个函数即可在事务提交时自动将改动保存到db,因为在commit的时候由容器管理并将改动保存到db中。
(5)merge与saveOrUpdate()一样,都能将自由对象和游离对象持久化,区别在于:在session中存在两个对象,但具有同一个标识(即主键),那么hibernate容器在持久化时不知道该持久化哪一个,一般会抛出那个经典的hibernate异常(NonUniqueObjectException)。merge字面意思是“合并”,其作用在于:将其中一个对象的属性合并到另外一个对象中,然后持久化后一个对象。
两个对象有同一个标识的场景(使用merge场景):①在save一个对象user1之前(游离态或持久态的,已经纳入hibernate容器的管理,session中已经缓存此对象),又根据user1的id去session.load一个新对象user2进行业务操作(例如待更新对象和db对象比较),结果导致session中存在两user对象具有相同id(save和load在一个事务中)。②session.load一个对象user1,对象传递时,将此对象的所有属性包括id赋值给另一个对象user2,session.update()对象user2,那么user2也纳入hibernate的容器管理即此时session中也存在两user对象具有相同id(load和update在一个事务中)。
既然了解了场景,我们完全可以自己解决(只不过hibernate提供merge统一解决):①中session.load一个新user2进行业务操作后,关闭session,并evit()session中的新user2,再update那个user1,此时session中只存在一个user1具有id,即不会发生异常。②中避免更新user2,可以将user2中修改的属性值设置到user1中,然后更新user1,因为user2只是一个pojo还没有纳入hibernate容器的管理,所以session中只存在user1。
14.Criteria和Query:一般在单对象(单表)多条件查询时使用Criteria,多对象(多表)多条件查询时用Query。
1)Criteria:单表多条件的查询,可以封装统一处理各种比较条件,不用对不同对象的查询都写一次比较条件,
例如Restrictions.eq,le,lt,ge,gt,like的封装统一处理。
2)Query:对于多表关联查询时,使用hql更方便。
15.query.list()和query.iterator():一般类似基础数据这样需要缓存的数据,在appServer启动时就读到二级缓存中,我们使用list读。一般在程序中读数据时若确定此数据是被缓存的,那么使用iterator,否则还是使用list读。
1)list:缺点在于不能利用二级缓存,但可填充二级缓存。优点在于查询sql语句就一条。
2)iterator:优点在于可以利用二级缓存,也能填充二级缓存。缺点在于当缓存中找不到时,先1条sql获取id,再根据id每条每条sql的查询,n+1查询。所以使用前要认定数据已缓存。
16.hibernate容器管理:
(1)持久态是po:若一个对象和关联的对象都纳入容器的管理即都是po,那么其值的所有改变都由容器管理保存到db,而不用通过配置来告诉容器如何保持到db。
(2)自由态,游离态是pojo:若一个对象或者其关联的集合中对象是pojo,那么我们就要通过一些配置来告诉容器怎么管理,因为是pojo纯对象没有纳入容器的管理范围,容器是不知道如何操作这个对象的,例如插入“1”时是否需要级联插入“多”集合中新增的对象,我们配置unsaved-value为null,那么容器将比较集合对象的id和null来决定是否插入。
17.clear,evit,delete。
①clear():清除所有缓存,不清除db,将持久态——>游离态。一般较少使用,通常用在批量操作时,清空session缓存,防止内存紧张。
②evit(entity):清除某个缓存,不清除db,将持久态——>游离态。一般较少使用,通常使用场合同上。还有就是对已经load到session中的对象,业务决定再次load希望强制更新,但是load会使用缓存,因此可以先evit()开始那个对象。
③delete(entity):清除某个缓存,清除db,将持久态——>自由态。
18.查询n+1
①非懒加载的load/get查询时(对query无效),fetch="select"会形成n+1查询(会利用二级缓存),使用fetch="join"一条语句解决(不利用二级缓存)
②query.iterator()时,会形成n+1查询(会利用二级缓存),使用query.list()一条语句解决(不利用二级缓存)
可以看出n+1次查询的好处是利用二级缓存,所以就是使用场景的问题:
当确定数据一定缓存时可以n+1次到ram中查(例如数据字典在系统启动时初始化到ram)。
当不确定数据一定缓存时应一条语句查询。