TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
11#
发表于 2015-06-02 12:46:12
|只看该作者
五、 Hibernate" i ~ f& P+ b9 W* f6 T9 m( _
1. Criteria查询方式
6 P9 `7 U2 x5 f6 d% K6 P7 D A) w D(1).Criteria查询方式(条件查询):8 |1 R/ b8 h0 Y& B
Criteriac=s.createCriteria(User.class);) \& x) Q4 w! w% ^
c.add(Restrictions.eq("name",name));//添加查询条件,User中的name属性的值是否等于"name"
; n9 o4 u6 ~- k* C2 X% ^' ~7 jList<User>list=c.list();
+ a6 Z# }: q. u5 R7 L* a: DUseru=(User)c.uniqueResult();
- y. O1 c3 E# h z7 z( X
% T- B- s& q) u& G O# P( y7 N2. hibernate的二级缓存配置与分析
5 u ~1 u0 l( e' }(1).二级缓存:SessionFactory级共享:% C3 P3 g, W; ^/ c/ k+ v
实现为可插拔,通过修改cache.provider_class参数来改变;hibernate内置了对EhCache.OSCache,TreeCaceh,SwarmCache的支持,可以通过实现CacheProvider和Cache接口来加入Hibernate不支持的缓存实现
6 S$ S- F8 d& E4 m在hibernate.cfg.xml中加入:% y, M9 j) U p F. \
<class-cacheclass="className" usage="read-only"/>或在映射文件的class元素加入子元素:<cache usage="read-write"/> H, ~( Z% p5 J5 n6 T, e7 y( f2 x
其中usage:read-only,read-write,nonstrict-read-write,transactional/ ^5 u& j7 N& v: Z& g
Session的save(这个方法不适合native生成方式的主键),update.saveOrUpdate,list,iterator,get,load,以及Query,Criteria都会填充二级缓存,但只有(没有打开查询缓存时)Session的iterator,get,load会从二级缓存中取数据(iterator可能存在N+1次查询)
) M% C1 v! a( e& u; x' e' A3 |Query,Criteria(查询缓存)由于命中率较低,所以hibernate缺省是关闭;修改cache,use_query_cache为true打开对查询的缓存,并且调用query.setCaheable(true)或criteria.setCacheable(true)5 U9 X. Q/ }7 M9 U. P# s
SessionFactory中提供了evictXXX()方法用来清除缓存中的内容0 z5 O3 q5 Z9 z/ P
统计消息打开generate_statistics,用sessionFactory.getSatistics()获取统计信息,获取统计信息成本是很高的,消耗资源.对程序的调试是很有帮助的,可以看到session的初始化时间,打开多少次,关闭多少次等信息.
$ R( I7 g0 o& u, Z+ j! T 2 `8 ]4 X7 L/ B& }+ D
(2).相对user对象进行缓存:8 H) Q' @5 l/ V
<class-cacheclass="cn.itcast.hibernate.domain.User"usage="read-only"/>只读方式,效率高,User类不会再改变了.能够保证并发.
- p( h1 u' ~. ^$ {3 ^ 2 `- s2 t" h2 g7 d3 s& ~: M& a
(3).先到一级缓存中查找,找不到在到二级缓存中查找
( ]9 A p0 w0 y& _
- h+ V% ~& }5 P6 d4 e: C3.Hibernate的拦截器和监听器* o; S; h/ Z4 p
(1).拦截器和事件
' \' \! c( y- X3 _& E- M8 p5 \拦截器与事件都是hibernate的扩展机制,Interceptor接口是老的实现机制,现在改成事件监听机制,他们都是hibernate的回调接口,hibernate在save,delete,update等会回调这些查询
2 N3 ^ G6 `( A + X( {% C- p9 c. ]
(2).拦截保存的的事件:9 f3 C* t8 w2 F/ s
实现SaveOrUpdateEventListener接口
) \' Z3 }4 u5 }5 @* ^ D: e$ k/ z2 Gpublic classSaveListener implements SaveOrUpdateEventListener{& m5 I1 x$ ?3 Y: \" |$ k
public voidonSaveOrUpdate(SaveOrUpdateEvent event){6 |0 Z+ f+ }% f
if(event.getObject()instantce of cn.itcast.hibernate.domain.User){" y% t- S1 \0 E0 g
User user =(User)event.getObject();
8 L: P1 R+ [; M @* J2 ?/ @. RSystem.out.println(user.getName().getFirstName());
1 K" \; R# \; I/ X5 I. Z}
7 k9 P% s7 [- s8 j}
4 c8 D6 `/ v, ^2 j0 a! r B" K T' i}; b3 d" o5 Q) L. y0 R
配置文件中:
$ J( w) j$ J6 X0 k2 e$ W1 _<eventtype="save">
5 A* L' [+ w/ l6 c+ I4 A& p<listenerclass="cn.itcast.hibernate.SaveListener"/>自己定义的监听器,不同监听器的注册顺序,输出的结果也是不同的.
% I' k2 I: v- D! {5 i<listenerclass="org.hibernate.evetn.def.DefaultSaveOrUpdateEventListenter"/>hibernate缺省的监听器,自己定义的监听器会覆盖缺省的,所以在这里还要把缺省的监听器注册一下.6 D7 ]$ E, g! ?, n: G3 U
</event>
& k( C) I; Q3 b0 U6 `" c9 k4 L- V当保存user时,会监听到., ~; {0 U4 D7 e- _; ]* J" o3 R( P' q
2 g H% w# x" Q0 e4 }+ q
4.hibernate的内部缓存分析# y U! }/ h. f) }: D1 I0 J
(1).第一级缓存是在session中,第二缓存是在sessionFactory
5 ?( O* ^% x0 j $ ?3 x8 @- S! e- D+ s
(2).Useruser=(User)s.get(userClass,id);
: Z8 x; g, U! ^- s ?System.out.println(user.getClass());
- `' K2 ^5 \% {7 h- Quser=(User)s.get(userClass,id);, a7 I3 [7 Y4 o5 d0 X' C
只有一条select语句0 _& M5 [9 T, e" f0 @ H
; X5 E2 b: U' H6 f% M0 ~(3).当session关闭时,缓存也就没有数据了.
* Q/ Q: ^" C! d3 I
! M; J' J" x J9 a/ I6 I(4).缓存的作用主要用来提高性能,可以简单的理解成一个Map,使用缓存涉及到三个操作:把数据放入缓存、从缓存中获取数据、删除缓存中的无效数据
$ ?, o; t" ?8 \2 V; a! ]2 n, `# t & z8 d Q5 Q' G$ T4 H) A% [
(5).一级缓存,Session级共享,save,update,saveOrUpdate,load,get,list,iterate,lock这些方法都会将对象放在一级缓存中,一级缓存不能控制缓存的数量,所以要注意大批量操作数据时可能造成内存溢出,可以用evict,clear方法清除缓存的内容.; [+ M; p: s2 z0 m5 S" ]9 g5 F
5 |) C0 T& B" } d. Q+ F5 A7 w
(6).只要有sql语句,就不会去缓存中拿数据,直接到数据库中拿数据2 b# m" M8 t# r3 R
7 G* A$ G/ I- J" i(7).手工的对缓存中的数据进行清除.清除一条记录:s.evict(user);清除所有的记录s.clear();定时的清除可以降低内存溢出的可能性.
) U; S. L1 ?- I1 R
( w3 d+ @6 y8 n(8).session的生命周期很短,只在一个web请求内! {. w3 l- _! Q( }
" G& `" z1 H7 L3 m/ K( u5.hibernate介绍与动手入门体验
( F# n# F* r( N- C! ?4 g(1).模型不匹配:Java面向对象语言,对象模型,其主要概念有:继承,关联,多态等,数据库是关系模型,其主要概念有:表,主键,外键等
$ w+ j q6 p, X" _/ C7 g% ] 4 l! s( q- Q9 W
(2).解决方法:第一种:使用JDBC手工转换,第二种使用ORM框架来解决,主流的ORM框架有Hibernate、TopLink、OJB
& P( p3 L( c" n2 i! Z% G, V
6 Z+ Y/ W! Y" p: T: E6 I(3).下载hibernate,将下载目录/hibernate3.jar和/lib下的hibernate运行时必须的包
' \4 I; [/ K5 a3 k
! X/ t U: o7 @+ K0 x! ?- Z(4).配置文件hibernate.cfg.xml和hibernate.properties,XML和properties两种,这两个文件的作用一样,提供一个即可,推荐XML格式,下载目录/etc下是示例配置文件$ }6 V" K4 C9 B
可以在配置文件制定:
. d1 {& l9 N! }7 ^7 _数据库的URL,用户名,密码,JDBC驱动类,方言等,启动时Hibernate会在CLASSPATH里找这个配置文件.映射文件(hbm.xml,对象模型和关系模型的映射),在/eg目录下有完整的hibernate示例
( w+ I7 h- ^# ?& [4 M : j& A; q/ F/ f
(5).首先建立一个对象:
; a5 N5 C, F0 v" v$ Hpublic class User{
, b h! M7 q3 z4 b$ b# s& Sprivate int id;
2 J5 I! q8 @2 a9 Uprivate String name;
$ o# s* L/ Y7 Hprivate Date birthday;
% E; F! C6 X% Y" X3 q$ ` x//省略了get/set方法
9 R4 ~6 L9 e, T8 a& m8 d- M}8 g4 C4 Z$ Q' s( q& m
编写映射文件:User.hbm.xml
! Y( i- ?& s8 {) y5 n1 O+ l, g3 `<hibernate-mappingpackage="cn.itcast.hibernate.domain">
. b9 ~2 R6 J5 i- @* y<class name="User">
' D6 w. F) A4 q1 F" z<id name="id">3 B( X3 z2 H" s/ U. |
<generator class="native"/>3 Z; B9 h8 [) M# F V
</id>
, w/ ?! _# y- c" `+ E& \<property name="name"/>
% @& o7 R& Q* @: Y' ?<property name="birthday"/>; L W$ X7 K. z( R6 E
</class>
; d3 c: N$ r! i. |5 ] F</hibernate-mapping>+ A- G. n8 l; E3 k" L( a1 u2 }
在配置文件中hibernate.cfg.xml:, F* v# B \( h: a& D
<hibernate-configuration>
/ D' c, @+ `/ H# k<session-factory>
( V5 M) A7 A" L7 k6 T% Q1 p<propertyname="connection.url">jdbc:mysql://localhost:3306/jdbc</property>
! s5 W. G# N1 B$ s<propertyname="connection.username">root</property>
, p9 ]# o" v+ w) C<propertyname="connection.password"></property>1 R! k: s$ E+ r4 K; z
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
- T- q- b. S* O5 }% u' [1 d3 O( M8 Z<propertyname="hbm2ddl.auto"></property>( l5 l2 H7 m- o2 i3 \% d
<mappingresource="org/hibernate/test/legacy/User.hbm.xml"/>
! k$ z9 R% ?) b* _! k<class-cacheclass="org.hibernate.test.legacy.Simple" region="Simple"usage="read-write"/>1 R+ `& w5 r/ K5 h# C( h& q
</session-factory>1 s/ K4 B3 M3 X9 h% X7 l, o: h2 o
</hibernate-configuration>
7 g! F0 S* ]8 W0 @$ a方言dialect就是哪种数据库.hibernate自己可以建立表(hbm2ddl.auto)
, S( z3 `( ]" _( y7 `7 p
# k- O: l `' `! a" O# m% X8 C(6).初始化:, G* s+ r" `; e7 J, N
Configuration cfg = new Configuration();
% A* G9 A0 C0 R# o1 [2 @' @cfg.configure();
! P3 t$ |$ L9 l" lSessionFactory sf=cfg.buildSessionFactory();//SessionFactory相当于JDBC中DriverManager* X7 F! S8 z' P3 o
Session s=sf.openSession();//工厂模式,生产connection9 H2 g! h$ u0 S1 ]) n
Transaction tx=s.beginTransaction();
. d9 L4 R* |( TUser user = new User();% n$ T3 u. ]0 I
user.setBirthday(new Date());
/ D w1 y5 ^0 a1 q2 \( tuser.setName("name");9 Y' {7 \/ @. F$ W
s.save(user);
6 C1 ]. S: V4 } P; Rts.commit();4 Y+ \) j" ?$ ` V) @
s.close();' n0 @, h$ w! ?" g
" K5 Q m" P( C# w" v# ^1 Q- S
(7).hibernate默认会把事务自动提交功能关闭了,所有自己要手动打开,查看表的结构命令:show create table user,看表的引擎是否支持事务,查看引擎命令:show engines
1 c2 `" t$ J6 m
$ S. A' \% @: c! g(8).开发流程:
$ w) `; b: f& R9 w& J, F" t方式一:由Domain object->mapping->db0 H a6 r' l7 w* S% ?: V
方式二:由DB开始,用工具生成mapping和Domain object
% c* E) z, G9 b9 s1 F+ Z方式三:由映射文件
% Q/ j/ Q: N" q
& Q# Q, [, f% e' u( T9 v. p. h, y(9).hibernate管理的Domain对象类定义必须要符合JavaBean的定义规则:默认的构造方法(必须的),有无意义的标示符id(主键),非final的,对懒加载有影响
, A! L0 r, k2 y# Lpublic class User{
; N" [8 @1 ?1 S9 }9 kprivate int id;* V) A! b. p# ?
private String name;
5 q5 F) F, I7 t. |8 m8 lprivate Date birthDay;2 z: l* ~# m- ^ G9 r+ R
//get/set方法
/ c" G/ q r, l8 g# ^}
3 R# E" a8 Z, g0 J$ a( d N 8 O" h! H# Y# Z+ Y
10.编写一个工具类进行初始化Hibernate
, y2 d0 s( n3 xpublic final class HibernateUtil{; c3 h' J* W: r: x6 V& Q* |
private static SessionFactorysessionFactory;4 g8 \0 L( E/ m: d! O5 n0 v1 { m
private HibernateUtil(){
$ d8 Q* u( {+ U% b& H# U}
! O7 v0 ?) f R% [) V( G* x5 Y/ tstatic{//静态代码块8 u$ r! o0 k# d; W& t
Configuration cfg = new Configuration();
# K2 m9 F) h U! e, I" \) Dcfg.configure();//默认的传入是hibernate.cfg.xml文件
. l5 b; W8 Z. { n1 a: DsessionFactory = cfg.buildSessionFactory();" d A/ |* l* H3 g9 ]6 c v# q
}/ z. N B" ^5 N% V6 K3 Z
public static SessionFactorygetSessionFactory(){+ S- a& }* g8 I1 m8 b+ O' O
return sessionFactory;$ o9 o! ]8 }; g+ B3 _3 K
}
+ a3 M- N5 k$ F$ b: |}
# c( d) Z( h' ]0 O4 {' [2 w . @& Y; N; r8 q: Q, B/ B, Y* B
11.static void addUser(User user){//标准规范代码3 ?) x; a) F# }+ `# E) E
Session s=null;
% H3 N; o1 I# P+ K- k1 ^" LTransaction tx=null;
( b6 N- m- M% H$ O% a: d5 Wtry{8 N0 }$ k1 j0 u" L1 K+ z( e
s=HibernateUtil.getSession();+ q# ~& X" k0 J* y0 }, R" e
tx.s.beginTransaction();
% X- P6 {( g8 B$ [4 W2 k& l9 Q& d% qs.save(user);% P2 H4 W7 G+ f, b3 n6 \/ I4 W
tx.commit();/ H, V, J2 s2 k/ t
}catch(HibernateException e){! ~2 k8 B! H% ^
if(tx!=null)
2 z, G: o1 |; o7 P" {5 ttx.rollback();//不仅要回滚,还有抛出异常0 Y1 i; |9 u9 T- I/ D
throw e;
) B/ D' Y8 C* N1 Y( t' t* a}finally{
( b( y: O4 O8 lif(s!=null)/ T" L7 P6 e/ a6 D& b8 R. P6 E I
s.close();
# r# `# X8 S4 z3 R1 f}
) s2 G, P; { R}
2 `/ e: [, b# p! H
+ v+ U. X3 ^' }5 }2 Q, i6.hibernate配置文件中的配置项& \- N& Z" a: H$ s* m# \3 m8 k
(1).hibernate.cfg.xml和hbm.xml内容解释:
$ M/ K; p, H2 H6 p" X4 R( y3 Y第一:数据类型:<property name="name"type="java.lang.String"/> type可以是hibernate、java类型或者你自己的类型(需要实现hibernate的一个接口)
4 w& t! W+ G, K# K! A% e第二:基本类型一般不需要在映射文件中说明,只有在一个java类型和多个数据库数据类型相对应时并且你想要的和hiberante缺省映射不一致时,需要在映射文件中指明类型(如:java.util.Date,数据库DATE,TIME,DATATIME,TIMESTAMP,hibernate缺省会把java.util.Date映射成DATATIME型),而如果你想映射成TIME,则你必须在映射文件中指定类型
2 v z! Q/ o2 O! n8 x, c第三:数据类型的对应关系: _1 {- W2 v9 g' Y# F
. R* J5 _1 }% `( V3 s
(2).Session是非线程安全的,生命周期短,代表一个和数据库的连接,在B/S系统中一般不会超过一个请求;内部维护以及缓存和数据库连接,如果session长时间打开,会长时间占用内存和数据库连接
7 I# _% h; e4 w ( o/ k4 R: p) w: u
(3).SessionFactory是线程安全的,一个数据库对应一个SessionFactory,生命周期长,一般在整个系统生命周期内有效;SessionFactory保存着和数据库连接的相关信息(user,password,url)和映射信息,以及Hibernate运行时要用到的一些信息.
3 g# p6 Y" P/ E$ R4 e 2 _" c: N U' g6 I* w
7. Hibernate映射类型
- M3 U7 M+ Y7 C& qserializable:序列化到数据库中.5 b* k$ w0 s, l& Y+ a: l$ B
* x r* F) R/ N& s+ }0 L
8.Hibernate中使用的集合类型
( ^; T7 }1 n* x' Y0 k(1).集合映射(set,list,array,bag,map)* {" D$ M6 g( c6 { G8 i8 i/ ^
List<Employee>emps = new ArrayList<Employee>();1 m+ S( G9 l2 ^* ]7 H' J
映射文件中:
, @, I! y' F0 i6 [5 s<listname="emps">' Z8 w; r: Q: [& z! h# {
</list>配置和set标签是相同的,只是区分List,Set的区别/ R' l* e) v7 `! j) X6 F
<list-indexcolumn="order_col"/>这一列是给hibernate使用的,需要记录该员工是第几个加进来的,即加入的顺序.
5 O" n/ U- J- g' D! E# c # G# A {. L9 u+ t
(2).由于懒加载的问题,Hibernate重写了java中的集合类,使其具有懒加载的功能.所以在定义的时候,必须要定义成接口类型即List,Set,Map
9 f! K3 s$ Y. u% m/ I+ P: ?' ] $ n+ I. z& t0 I5 I t0 ~
9.hql的命名参数与Query接口的分页查询# C* p2 g, G3 k* v5 N1 _* C1 `2 [
(1).匿名参数:不使用占位符了
# ~# b+ O# C: P; f1 @( VString hql ="from User as user where user.name=:n";
1 X4 j/ N* c% ]query.setString("n",name);
( g0 Q" z W3 H1 p2 f不会依赖参数的位置 c$ E" T6 P' B: w. V
7 q2 ]" J1 L8 ]2 {) X( S(2).Query接口中的方法7 B! }% x5 w, |
query.setFirstResult(0);( Y, K& k) ?9 m, b$ v2 c2 X
第一条记录从哪开始,参数为开始的位置
5 B% J3 H3 I. H) J6 tquery.setMaxResult(10);6 O0 [: s( A3 V) X
实现分页功能 |3 n& ~0 L' ]+ }* y
2 S: J, b1 P0 W$ e3 U K; H
10.Hql与Criteria查询的补充知识
# Y' X( Q8 F% L8 Z) }- ~) mHQL:查询多个对象select art,user from Article art,User user where art.author.id=user.idand art.id=:id这种方式返回的是Object[],Object[0]:article,Object[1]:user;
# J3 Y, b- n& f: j $ w; X) v% Q$ c1 x
11.Iterate查询与N+1次查询: @ O- T* ?4 n. A# D) K [7 s4 O
(1).假设已经加入到了10个用户) T: o+ K& _' K% [/ \+ f
static void iterator(){
1 r9 F& h! z7 F. d+ V! ASession s=HibernateUtils.getSession();
: f& ~" R5 y; k$ P9 ~4 f. @6 }Query q=s.createQuery("fromUser");
% E- B7 K! j# P* g/ VIterator<User> users =q.iterate();
. |# J+ X6 T% t9 H1 Wwhile(users.hasNext()){0 M, G8 Z. i6 B! h4 Y% n$ R
System.out.println(users.next().getName().getFirstName()); b, r2 t" k6 o. A! a, b: \& |
}# A8 i1 X T: x% ]9 f$ Z. O
}
0 Q/ M( U: `0 C3 T8 ]+ k( e首先把10个用户的id都查询出来,然后按照id去查询详细信息,这是会到一级缓存中查找,找不到在到二级缓存,找不到在到数据库中查找.假设都到数据库中查询,那么就进行了11次查询,第一次把所有的id都查询,然后再逐一按照id查询进行10次,总共进行了11次,所以在使用时一定要小心,是否确定一级缓存和二级缓存中有我们想要查询的数据,不然的话,性能就下降了
& [; g9 ? W# Z+ E: e: D+ [# Y # e6 w% z$ L, i/ w- Q6 D
(2).在懒加载的情况下,就会出现N+1次查询,比如一对一:7 \1 M4 p! p4 }' D( c$ c, b
首先查询IdCard得到id,然后再去访问Person% o% i0 p( t5 ^& p. {1 N/ `2 f
Session s=HibernateUtil.getSession();
/ g: O) m- a0 O$ c1 f$ jQuery q=s.createQuery("fromIdCard");. }: o0 |* D" w2 Q! ?
List<IdCard> ics=q.list();; E' x: g, Y' p: z7 W
for(IdCard> ic:ics){
' v2 H$ P7 i! ~: |System.out.println(ic.getPerson().getName());
2 A$ V% v$ K' A}
1 ~( I$ y) I: Y& e' Y4 ?' L7 ]因为懒加载,每次访问数据的时候,都进行查询数据库.6 i+ k( O6 L# S5 T
2 P* j- J* p; y3 s- c7 c
12.load方法的懒加载及原理分析. z; E, E, y. g, w; O. C7 ?
(1).Useruser=(User)s.load(userClass,id);
( H5 t1 y5 l7 R) ISystem.out.println(user.getClass());4 r2 a0 c3 L: a! u# @/ S. z, L
就是说s.load(userClass,id)返回的是User的一个代理对象.即是User的子类.在session没有关闭前,去访问数据库user.getName();但是这种方式不好,最好使用Hibernate.initialize(user);初始化懒加载.% O9 C9 m4 \ h5 ?) E4 F* t7 e
- q3 \* J8 N, d(2).懒加载是将与数据库的交互延迟,提高性能.load()方法,不会到数据库查询,只会返回一个User的一个子类.% G9 Q) p( S, L+ D8 V6 G7 \5 U
; v( `0 p/ a; I" ]2 @, D! ?8 \9 a4 r(3).asm.jar,cglib.jar这两个包实现懒加载,能够动态的修改内存中的字节码.即动态的生成一个User的子类.7 w9 y, D; a! g- W" M. h! o
|& `* f/ \5 Z6 O- c" i
(4).employee.setUser(user);这是就可以使用懒加载,建立employee和user之间个关联,但是不需要去访问数据库的时候8 X- B% ^# L( g L: M, i7 `, z
: O% J2 N+ [% i$ A0 U
(5).通过asm和cglib两个包实现的,Domain是非final的,session.load懒加载9 P+ X3 Q( h2 q4 g
one-to-one懒加载:必须满足三个条件才能实现懒加载:第一:主表不能有constrained=true,所以主表没有懒加载,第二:lazy!=false,第三:fetch=select;
" v% X3 i9 J- @3 s/ b- r( rone-to-many懒加载:第一:lazy!=false,第二:fetch=select
7 I$ E# N( S. j n7 K8 t5 kmany-to-one:第一:lazy!=false,第二:fetch=select1 `2 `* H: F, p+ w# e% `
many-to-many:第一:lazy!=false,第二:fetch=select
0 S3 Q$ E: C( M8 n( s
$ q1 V$ Q; K v5 t5 q6 p' d(6).能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外),hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象,当相关联的session关闭后,再访问懒加载的对象将出现异常.0 T# ?0 D, v r3 B8 l0 d
* ?- X6 t6 S8 d(7).方法getId和getClass不需要访问数据库也是知道的,所以不是出现懒加载的初始化异常.
" A$ s2 x- R7 O7 S % c5 Q j M5 C( m
(8).表中的属性也可以使用懒加载的,只是需要在编译后的内容进行处理,这种用途主要在字段是大文本类型时需要.# i" t& g- f8 `- G d2 }& Y
6 z& l- R7 [' U( W; L: ^3 S* t, x
13.OpenSessionInView模式的代码分析& _: S2 B( G1 e- {5 I- y
(1).ThreadLocal类# H& ]- ?! e1 N+ M
private static ThreadLocal session=newThreadLocal();
& J0 B( N: \2 O0 P线程级变量,作用域在一个线程内.
4 }' v! B' J3 F3 jSession s=(Session)session.get();
6 T- V; r* A1 p8 t3 B( X% tif(s==null)}5 ?( F9 A7 A, R2 C! i0 F2 w; M# x
s=getSession();
( J1 K+ }% n* A( T' Fsession.set(s);
, O& L' S+ s. `; z}0 ]( v a0 k5 G' P
当有一个web请求来时,服务器创建一个线程进行服务,将创建一个session,所以在这个线程内可以访问到session. P4 _" n+ b/ r$ k3 `
' Z2 v' g3 K8 k- h(2).sessioncontext和事务边界$ H) v9 |9 K- c# Z$ T/ H% Q
用current_session_context_class属性来定义context(用sessionFactory.getCurrentSession()来获得session),其值为:
3 e$ b: s8 U8 w: w. p+ u! ^第一:Thread:ThreadLocal来管理Session实现多个操作共享一个Session,避免反复获取Session,并控制事务边界,此时session不能调用close,当commit或rollback的时候session会自动关闭(connection.realease_mode:after_transaction).Opensession in view:在生成(渲染)页面时保持session打开,前面所说的懒加载时,可以保证session没有关闭,可以访问到数据.
) i3 X6 Z4 Y! L+ T第二:由JTA事务管理器来管理事务(connection.release_mode:after_statement)' W* z( O8 u% D2 D" H
. W% B; m& K! {1 @8 k/ p/ @5 ~(3).用户发送请求->web容器->doFilter(过滤器)->OpenSessionView->打开session,事务->ActionServlet(struts)的service方法->根据配置文件找到->Action(execute方法)->业务逻辑层(register方法)->Dao层(addUser方法)->返回,直到doFilter的commit,提交事务.在这个过程中session都没有关闭,可以解决事务的边界问题,解决懒加载的问题(即什么时候使用懒加载).缺点:延长事务,session的生命周期,session延迟关闭,那么一级缓存不会释放,长时间占用内存.客户端的网速比较慢,导致事务和session长时间不能关闭.即延迟关闭.会给服务器端造成很大的负载./ a7 T7 ]. Y# m7 |
' r2 ]2 k8 ~% E# d& p
14.Session接口及getloadpersist方法
, j$ w! M! P- X+ `(1).由于Session可以管理多个数据库表对应的多个实体对象,如果要查询id为1的实体对象,Session.get方法需要知道去哪个数据库表中查询id为1的记录,所以,除了给get方法传递所要查询的实体对象的id值外,还必须给get方法传递实体对象的类型,get方法才能知道去哪个数据库表中进行查询
2 l# d3 s F% `# b
5 A2 c3 W) C+ i9 O* x(2).通过类的类型可以去hibernate.cfg.xml文件中查找到对应的表5 b1 z+ ?& F3 W) D
0 u8 J5 T0 C; X# E: S& j# D. Z(3).在配置文件中添加标签<propertyname="show_sql">true</property>//可以打印sql语句
; O# O6 c# o4 N$ E, P + ~/ n" s& `: ]: k# `2 x8 p( v1 n) m
(4).Useruser=(User)s.get(userClass,id);与User user=(User)s.load(userClass,id);的区别,load不会去访问数据库,只有第一次访问时,才会访问数据库.增加一条打印出user1的类名的代码,就可以看到load方法所返回的User子类的名称了,该语句如下:2 w7 \5 B1 I) i
System.out.println(user1.getClass().getName());$ k" V9 h" |' @, l; m
U8 \# M1 n- t/ S; x v% h(5).s.save(user)和s.persist(user);都是存储数据,persist方法没有sql语句,没有开启事务,save会回滚,persist不会回滚* `0 H( G+ a* e. K
( y& @2 n7 i1 P6 U7 |
15.Session与SessionFactory的多线程问题( r& A" z3 r5 g
Session内部封装了一个connection对象,尽量迟的创建连接,尽量早的释放连接
, l7 Z$ G3 ]/ Q) L ) P- ], \! l. P1 B
16.本地sql查询与命名查询, g7 g! F! [2 M% \/ V/ _! U$ H W5 @
(1).使用Query接口$ h; t7 A% ?2 I" k* l
static list sql(){ Z- i8 ]( z* B- j3 X8 V. l
Session s=HibernateUtil.getSession(); |/ x5 }* R( X7 j- y
Query q = s.createSQLQuery("select * fromuser").addEntity(User.class);//查询的结果是User对象& J. u" a( m# {! O
List rs=q.list();. P* m' f1 O) N2 C$ g
for(User r:rs){' ?6 v h# j. T
System.out.println(r.getName());
9 h- q9 V& K8 a2 G; F/ F}# L7 V; i- U: ^
}
* L7 K/ b( P6 \5 i- m4 D& G . O# v' {9 _' g4 H6 j! k
(2).不同的数据库,本地的查询语句是不同的,所以这种本地的查询语句最好不要使用,兼容性和移植性不好.
8 \/ x5 t& {+ H; ? y, Q
/ l6 j+ L/ G F+ w" `6 y4 q(3).命名查询:将查询语句放在配置文件中,以后修改查询语句只修改配置文件中的查询语句就可以了.
2 g6 U* N3 L- Y# @7 k4 h<queryname="getUserByBirthday">- g7 z' m2 h. F+ S4 }
<![CDATA[from User wherebirthday=:birthday]]>& S( a5 F& D( |) N
</query>
8 }, G# i# Z( k3 H这个定义可以放到class标签内部,不需要使用全名,只需要getUserByBirthday即可,但是在这个范围内,不能出现重名,如果在外部,那就需要全名了,cn.itcast.hibernate.domain.User.getUserByBirthday
; ^" U+ G/ g( ~/ T, g7 g- }在配置文件中$ ?2 J, D1 B2 d& n5 u8 z4 E! T
static List namedQuery(){+ ] w# [, k5 p' }7 y
Session s=HibernateUtil.getSession();8 j4 E9 C" u6 s, t
Queryq=s.getNamedQuery("getUserByBirthday");' F" w2 d! H0 B) X0 B0 ~, o
q.setDate("birthday",new Date());3 X$ T$ s% c3 U6 c, O d
return q.list();
b& q, h5 N% e8 d' ]# j}
9 ? }+ n! E' _1 m
- |2 E$ s; c% M- e3 W0 J(4).hibernate可以做到用Map代替Domain对象,存入到数据库,但是这就符合ORM定义了,同时也可以将数据库中的内容转换XML1 Z/ j- N9 {" I$ C6 T* T+ P* U
# }( B* k6 U; @" G* w7 ^17.多对多关联关系的查询7 w- @2 q9 @% ]% T
使用表之间的关联join,效率低. K/ G$ j [9 E8 O& s' b: V( {! j
. w0 V/ b1 E# ^1 r18.多对多关联关系的映射与原理分析& l, m- n+ _8 K
(1).多对多(teacher-student):在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;Hibernate会为我们创建中间关联表,转换成两个一对多.. C; o2 d l) G" A& x
<set name="teacher"table="teacher_student">
1 {; {( r. S+ D$ ^1 L, S0 W<key column="teacher_id"/>
+ n$ f4 q3 P7 ~' z c4 O<many-to-many class="Student" column="student_id"/>5 J9 t0 A' | d; e7 k$ f
</set>& A7 O/ H8 V6 X: V1 C" @* `
ER图:teacher:id(PK);student:id(PK);teacher_student:teacher_id(PK,FK1),student_id(PK,FK2)
8 y8 p& u$ ] l; f+ `0 d
8 s8 n! J) r" m7 R(2).9 g* _- z- ~* d' j3 a5 q
public class Teacher{
& p/ J0 A$ m% {& W1 iprivate int id;
# w8 I J& T1 _* |2 [3 h) Wprivate String name;
- r$ Y4 o9 }% nprivate Set<Student> students;
, u0 v- D6 V/ F! k; I- P! Y: j//省略get/set方法- B2 |) |5 Q0 F0 w
}: V! G9 i9 n" L4 G @
public class Student{0 {; x; x4 ~- i4 B3 R w# _
private int id;) B8 H! K: B% @5 S7 Y
private String name;
& V9 x; n8 V( L1 S0 T h0 ~9 i0 Dprivate Set<Teacher> teachers;0 a& `, \2 q% \
//省略get/set方法9 J5 D$ `9 Y4 D2 W1 r' i1 i, Z7 [+ E
}# ]* |/ u, j J; Q2 r% ~' x9 S
teacher的映射文件:4 O) p* w5 X. N, q% z
<class name="Teacher">
2 S# l1 A. ] d; Z0 J5 K% A<id name="id">
% G. L/ f6 e; h# u1 ~' t& b! O! _<generator class="native"/>
+ x9 k5 I! T: `0 o v' M</id>. I, o* X0 f# }# z+ |) ?
<property name="name"/>
8 ^3 n2 J" g j<set name="students" table="teacher_student">
6 h6 v- t% k' ~$ n+ `9 k: A4 A<key cloumn="teacher_id"/>
2 G' J: n9 G' l+ ]+ c<many-to-many class="Student"column="student_id">根据student_id去查询学生的相关信息6 l( G3 f+ t6 `' u, M% ]
</set>
8 }# J+ f# x i3 S</class>
2 M9 P& @# r# W T同理student的映射文件相似.
3 A# v8 y+ ^0 F9 u 3 V- m5 _! d& G& v) `9 y) w
(3).测试类:
/ Z# b+ c, m4 ]% BSet<Teacher> ts=newHashSet<Teacher>();
3 a* T* p9 z2 j* ^! [7 a5 E! ITeacher t1=new Teacher();
2 Y1 y; T3 {' |+ b, Ft1.setName("t1 name");8 v! R, l( P" P! ], o
Teacher t2=new Teacher();$ G8 `0 ^8 u+ f
t2.setName("t2 name");; @# }$ `" R1 `4 j$ D0 M3 H* X
ts.add(t1);
" K& h8 W* T1 U, e' B1 B. Ots.add(t2);! f0 h4 ?8 l) A N
Set<Student> ss=newHashSet<Student>();: k" M* R; k, K+ u% R1 e
Student s1=new Student();& z0 \- l' ~* @
s1.setName("s1");
! E- b k# w% ]* j. b! O) M# H! UStudent s2=new Student();0 w9 f! T2 f# H# _( y3 r4 K
s2.setName("s2");
# e e) Y" E$ z6 S9 I# T: u it1.setStudents(ss);//建立关联关系+ T$ s l2 \1 v5 a7 ?3 n3 T
t2.setStudents(ss);
: o7 @% t: e6 o6 Q W7 fss.add(s1);; l" {4 j* g$ R& S
ss.add(s2);
( p8 _9 }1 [: u( M8 h+ Os.save(t1);
; g# b, p, e/ n! E2 Qs.save(t2);- X6 u. Y% Y1 d4 d; G; n
s.save(s1);
/ M G! n5 R1 D5 H9 H" Ps.save(s2);
/ H" T3 ]+ O$ [; L在中间表中插入数据& B8 o a* Y8 R. S8 n
8 ?7 {. T9 A( \. S% ~$ j
19.多对一的懒加载分析
t+ K2 {5 @, `+ a% h(1).查询员工的信息时,是否需要部门的信息,默认的情况下是懒加载的方式,怎样判断是否进行了懒加载,可以通过打印出的sql语句中的查询语句即可
6 m* u! |7 d/ H1 {: u3 F
|6 o2 f6 ?' v; Q r* M(2).当IdCard中的id是主键也是外键,当id有值时,一定有一个person与之对应,所以可以使用懒加载,先生成一个代理对象,当需要person的信息时,才去查询,反过来,因为person中的id只是个主键,知道person的id,IdCard中不一定有一个值与之对应,所以不使用懒加载的方式,而是直接去查询数据库,这就是查询主表时不使用懒加载,查询从表时使用懒加载.
: E: k" i: V+ ^- g# W 6 w) P7 {2 _: r; l
(3).但是多对一的部门和员工,直接就是用了代理,depart.getEmps()获取员工时,Hibernate中的集合把集合空对象和空集合是相同的概念.
" a1 _: B1 v$ r0 ~$ N$ |" ^
9 A( G/ ~/ S* ^: O! p/ c6 d20.多对一关联关系的检索与原理分析
/ E, Q" \' S; C R1 I7 P" N(1).查询操作(department表的查询和以前一样,只是employee表不一样):
+ G4 w' `) r6 e: Y9 Z- P, d) Vstatic Employee query(int empid){
( E$ B; X) W- }- Z/ u) uEmployee emp =(Employee)s.get(Employee.class,empid);
' s& _; D( `* C0 [& u4 \System.out.println("departname:"+emp.getDepart().getName());//得到department的名称.7 O7 `; f- m7 Q* F- \
return emp;
& ~. b4 N3 T) B, E. T7 L% u}! P$ O4 v: B) A$ J8 C# E
进行两次查询,首先根据id查询employee表,得到depart_id,在根据depart_id查询department表.
7 t6 ?6 Y$ s- A+ U1 b5 I1 A0 ^
9 M7 Y3 e; f- G. n21.多对一关联关系的映射与原理分析- T! A/ b# R$ p& E, l J z( t
(1).多对一:映射文件:<many-to-one name="depart"column="depart_id"/>
' X+ i' _7 J2 K& [ER图中定义Employee主键(PK):id和外键(FK):depart_id,Department的主键id;
) A0 w( R+ M8 K2 Q: e
# H2 i$ D. U/ {( T(2).建立Department类. L" N( q5 @0 V! a8 B2 r& ^
public class Department{
/ b3 ~ u1 V" T8 Iprivate int id;
2 W5 b3 o+ w' t0 p: T1 i* Rprivate String name;! B2 F, [1 M+ O
//省略get/set方法
& Q6 x5 \3 M& L8 h# u( G2 H}$ {3 [; k5 E) T! C2 O( |( \) O
建立Employee类+ w6 z! u* E5 d/ ^0 ~ _
public class Employee{
K s d% j4 ^/ \0 J. P( V6 }: jprivate int id;
5 g# P* b" H2 y3 A1 B, oprivate String name;
. |( b% F* o! Z2 H0 bprivate Department depart;, N: y" k* i' \( M& L3 `) q
//省略get/set方法
+ X2 z; R) E; d1 N0 `, }% J}2 ?; D. N n" n! Z* m9 V, g
* o4 k1 F4 w" s0 D6 y) r/ f(3).映射文件:
y& t1 t: K+ D4 }" J<hibernate-mappingpackage="cn.itcast.hibernate.domain">+ N, [7 |: i0 g4 t9 }* {
<class name="Emplyee">
* {) p9 x% H( w8 O- F6 a<id name="id">
& e I9 [' R/ w% O: p! O<generator class="native"/>
: E8 a. Y9 i$ ?* `</id>+ m. G0 O V9 N) d' W
<property name="name"/>
& Z& p& H2 x& f+ B5 T( |% f9 M8 Q<many-to-one name="depart"colum="depart_id"/>* N1 l8 x# o/ w' I
</class>
% T: ` |+ ?, N- _4 D$ A5 d; e; P( _</hibernate-mapping>
4 r9 B9 b5 q# D) i6 G2 u不在使用标签property,是对象类型depart,使用标签<many-to-one>
9 X, J ^6 t4 |) A" I3 n3 i' o通过反射可以找到depart对应的映射文件,当depart_id与depart映射文件中的id相同时,就查找到了.也可以使用属性not-null="true",设置colum="depart_id"这列不为空% @5 J) ]. H( a r( U# {! V6 u
$ E1 e. B( N5 @8 q& l2 [
(4).column="depart_id"不设置可以,默认就是column="depart"
; V1 [2 G! E$ h2 y' g6 y* \. O" K6 j
; y$ M$ |6 O n- J' ?8 n" d) i0 w(5).staticDepartemnt add(){
* a/ ]6 o) ~! m1 L1 a& E//模板代码,省略
* f" z) p, p) p: _* }Department depart = new Department();
+ k4 B1 Y; z- M1 c. m7 E* d/ P# {+ Mdepart.setName("depart name");
- U( v9 y7 h, Y. m+ sEmployee emp = new Employee();# _: Z4 }0 z2 [" f7 E
emp.setDepart(depart);//直接赋值就可以了,只要在对象建立关系,数据库中的表就建立关系了.
6 o: s8 x( r8 Vemp.setName("emp name");
/ I5 Q0 _$ H1 [s.save(depart);
0 l# ^9 f6 R) Q* C! N7 K1 q/ n( Ws.save(emp);! U2 C! `( X2 [1 f
return depart;" z$ x% h" @* L2 n
}
5 a5 q, c1 `" b7 O' ~# U O, J( H4 Y& ]3 u# m
(6).当s.save(depart);与s.save(emp)两条语句的顺序调换,会多出现一条更新语句,因为首先存储emp,当存储到depart时,因为employee中定义了department,所以hibernate检测到employee中的depart发生改变了,就进行了更新操作.此时是持久态
/ P3 G. J5 r. Z# o" O 9 {. x+ ?7 ?8 M. [; J$ Z
22. 分布式缓存的分析
8 `5 w6 K, ?+ U- u& ?' n大型网站有多个服务器,即就有多个cache,每个服务器对应一个cache,
/ |$ F% j1 E4 \0 ~# z# ` 8 o, s1 H% O% o9 Z$ L+ K
23.关联关系的级联操作& x3 t( A) s% o: M: v
(1).cascade和inverse:* }6 G4 l5 i9 X8 D. i; b
Casade用来说明当对主对象进行某种操作时是否对其关联的从对象也做类似的操作,常用的cascade:none,all,save-update,delete,lock,refresh,evict,replicate,persist,merge,delete-orphan(one-to-many),一般对many-to-one,many-to-many不设置级联,在one-to-one和one-to-many中设置级联
$ G! _% H4 w: q6 \Inverse表“是否放弃维护关联关系”(在Java里两个对象产生关联时,对数据库表的影响),在one-to-many和many-to-many的集合定义中使用,inverse="true"表示该对象不维护关联关系;该属性的值一般在使用有序集合时设置成false(注意hibernate的缺省值是false),one-to-many维护关联关系就是更新外键,many-to-many维护关联关系就是在中间表增减记录( v5 o3 I7 u) p- E( n% i3 R
注:配置成one-to-one的对象不维护关联关系.
6 J. n/ h8 q( F# q) ]
$ g. o. U; w ?! x. L, s, G24.缓存的原理与模拟分析
3 e- l, `+ P& i: `: P9 A(1).第一个人读的信息和后一个人读的信息可能相同,那第二个人读信息时能够加快速度了.
/ ]$ Z5 l3 o) D
# I4 ?9 F& ~/ [( D4 ^(2).第二人读取信息时,就不是到数据库中读取了,可以到缓存中读取数据.: B: W, U) @3 @: z8 J! K5 j. y9 k R
$ ^9 a: E3 S) m, j% ]; {(3).使用缓存cache存取数据.
2 ?7 G" P: y2 X' p
6 U7 @! i% i% J) s4 Q# ?; C25.继承_鉴别器与内连接器相结合$ O& B$ q, C6 J, ~1 j h
(1).子类的特有属性很多,就拿一张表进行对应,特有属性少的就和父类放在同一个表中,% K6 A' M f5 f3 Z- f
& F9 r7 C7 R$ |: r4 V8 G(2).employee:id(PK),name,depart_id,type,skill;sales:employee_id(PK,FK),sales;
. L6 Z( j1 i, Z( h6 m5 qSkiller子类和Employee父类放在一起,Sale类自己对应一张表.
% v$ x* G( N! X+ f0 ^
?6 F0 j& H' v# `# B(3).映射文件中只需按照前两中方式进行改变.8 h% K' ?5 R! i h# t6 Y' [: c4 W1 ~ u
2 `" K, W. D/ I; V. A26.继承_每个具体类映射一张独立表
6 \* O$ T) m5 f- C' ^(1).没有公共的属性,所有的属性都是自己特有的,在插入时候不需要涉及到多个表的关联了,效率高.如果employee不是抽象的,会有employee表% w0 P6 V7 V( N' [; z8 f. A2 k2 i$ x
+ K1 V2 N8 h7 U+ `$ J+ o/ ~(2).employee:id(PK),name,depart_id;skiller:id(PK),name,skill,depart_id;sales:id(PK),name,sell,depart_id;' c9 H9 ^9 T7 q9 J1 Q
6 g3 j' n3 [0 d4 y(3).映射文件:
* `4 J& m$ V( r# {# y2 T+ S. t6 \1 D<union-subclass name="Skiller"table="skiller">
. {+ ?% j: V7 C6 [<property name="skill"/>
6 @+ a* K: b" B, o8 _6 L</union-subclass>
I5 s! s8 `9 H9 u) Z3 Q6 S9 N<union-subclass name="Sales"table="sales">% _7 x2 ]7 c/ t5 M
<property name="sell"/>" q: B6 d' G9 A2 b3 C
</union-subclass>- @3 U9 d8 G Q6 B2 s
% j1 Z4 K$ n# t9 k3 [( t
(4).在查询的时候,多态查询时,还是要进行三种表的关联查询,但是插入只在一张表进行.: R6 `5 c; V4 p0 u# ^
5 w+ O, a1 d7 w4 [+ A
27.继承关系_每个类映射到一张表
, H7 T( d- W" N" o$ m/ X- Q(1).employee:id(PK),name,depart_id;sales:employee_id(PK,FK),sell;skiller:employee_id(PK,FK),skill;1 B" `8 b* y3 J- \; @
4 X# J/ Z. x1 ]% K1 [(2).此时不需要鉴别器了,每个子类对应一张表1 g7 d# F1 N4 f p
' s# |1 f3 U2 v! w4 ?(3).映射文件:
, p. l( |- R0 E$ G V x+ V<joined-subclassname="Skiller" table="skiller">
9 }- a/ \2 V2 i( a<key column="employee_id"/>; i3 y3 g, g- L9 A
<property name="skill"/>
6 \/ T9 D( V" o</joined-subclass>% V8 l6 V. h6 X" A
<joined-subclass name="Sales"table="sales">: H( h) K7 t8 d( l3 Q' {% P
<key column="employee_id"/>+ w* L1 T% q: @0 k) p
<property name="sell"/>* j4 b/ V% X" z$ `0 D- C
</joined-subclass>7 i) B& F. A, u$ j. H
9 G4 }+ k" V9 b; y! h" z(4).插入子类时,相同的属性插入到employee表中,自己特有的属性插入到自己表中,如果插入一个技术员Skiller(name,skill)时skill插入skiller表中,name插入employee表中,这时就插入了两张表.
# o9 M7 Z! S5 z& Q& H
7 P1 A" e8 S- D: q(5).当查询自己特有的属性时,会关联两张表,当查找相同的属性时,会关联三张表.所以查询时效率低.不要进行多态查询,最好查询具体的子类:
0 s7 D2 W/ x5 i& s具体查询:Employee emp = (Employee)s.getId(Skiller.class,id);
0 [6 m- C) u6 h4 w6 W多态查询:Employee emp = (Employee)s.getId(Skiller.class,id);
1 C0 L. ~5 h/ l2 @# V4 Y d0 p % C- ~: o+ {. j3 M/ Z$ U
28.继承关系_整个继承树映射到一张表! W8 {* N: O8 ?: r3 M: E
(1).public classSkiller extends Employee{. p1 y4 J& }) n+ E
private String skill;
$ c" t% Y6 z7 {* @$ R//省略get/set方法# ?' e6 Z Q" J! m( {6 E
}
! |: n* g" ] ^3 lpublic class Sales extends Employee{/ ~; O( u# b2 R6 g1 w
private int sell;
8 o" L x+ a/ @//省略get/set方法
% J! T' b+ {' @0 N# H7 k}$ M$ P4 b+ P* Q" Y# n. u
) p; Y& j3 n) b* z(2).employee表中的字段:id(PK),name,depart_id,type(区分不同类型的员工,又称鉴别器),skill,sell: p. `1 r) m% w/ i" Z+ J1 [4 i% S
- @0 \1 h& p* h1 g! a9 p
(3).这种方式当增加子类时,需要修改employee表结构.
+ F9 e4 F& {5 i# V! U
( X! |" v; Q9 n# t5 R. p; P(4).映射文件:6 z. l+ m8 }/ b6 _# [
<class name="Employee"discriminator-value="0">
6 E4 V9 R7 O) c1 b4 F. L<id name="id">
5 S! m# z# m+ g# \<generator class="native"/>' `1 y3 ~: q& T
</id>
# @' l6 A8 R- `$ R<discriminator column="type"type="int"/>鉴别器,hibernate用来区分不同的子类.3 U: b1 I4 c* T; D" [
<subclass name="Skiller"discriminator-value="1">
% u: Z7 j q1 R3 l; @: {% y<property name="skill"/>0 j6 S- i; m* }* ^2 Z" h
</subclass>$ P+ Q3 O) \% _5 T$ |7 B% K8 u
<subclass name="Sales"discriminator-value="2">2 _% y4 D4 `; D4 d0 {/ D
<property name="sell"/>
8 |3 ?, v0 T7 S3 _" S- Y</subclass>
2 E, h* Q& \" m: u</class>6 q9 ?* c6 q% C4 J8 z' }* n0 a
# V% {& z9 s) q3 b( W" C. o" W
(5).将一棵继承树映射到一张表中,所以在查询时,只对一张表进行操作,效率高,但是不灵活,当增加子类时,需要更改表结构,同时每个字段不能设置成非空约束.; H& f C/ Z4 r! I: @
7 T9 K, G! {. B& A3 p29.实体对象的三种状态与saveOrUpdate方法% \- ~* f- I5 a5 |* p
(1).Session的几个主要方法:
8 _+ o S6 R1 n' h8 B5 _第一:save,persist保存数据,persist在事务外不会产生insert语句; R5 B/ E5 `3 T: p# c1 w
第二:delete:删除对象' c$ d2 k7 c2 G
第三:update:更新对象,如果数据库中没有记录,会出现异常6 }* T+ M9 N9 d( ~6 ?
第四:get:根据ID查询数据,会立刻访问数据库( r7 J) n- b* ~
第五:load:根据ID查询,(返回的是代理,不会立即访问数据库)
, C+ L3 V! w2 V& w+ w B3 d! G! g第六:saveOrUpdate,merge(根据ID和version的值来确定是save或update),调用merge你的对象还是托管的
6 K2 a. }1 j) ?第七:lock(把对象编程持久对象,但不会同步对象的状态) H. l- ]/ U& ~) p6 R* ]
: p* _* F9 s" w' G/ F; I2 p+ ^
(2).瞬时(transient):数据库中没有数据与之对应,超过作用域会被JVM垃圾回收器回收,一般是new出来且与session没有关联的对象
1 a5 W/ M; X7 X5 y) }* q持久(persistent):数据库中有数据与之对应,当前与session有关联,并且相关联的session没有关闭,事务没有提交;持久对象状态发生改变,在事务提交时会影响到数据库(hibernate能检测到)
2 }: A) s2 g& ~6 }脱管(detached):数据库中有数据与之对应,但当前没有session与之关联;托管对象状态发生改变,hibernate不能检测到.
% d' D# H0 D; P( P6 y
, _* h) T y* C$ l8 q; w$ I(3).当关闭session时,持久态就变成了脱管状态了,区分这三种状态的两个标准:是否与数据库中记录相对应,是否在session中.
9 z3 ?' X1 S' c# t& O$ Y+ S
* h2 B% k: b9 O. `+ y(4).当在脱管的状态时,更新的时候需要执行update的更新语句,因为不在session中.0 n) C3 F3 {6 i" T8 G: {
; {( h7 F* X8 C4 Z4 g" m7 t6 c; J(5).对象new是瞬时的,get(),load(),find(),iterate()等是持久的,瞬时状态执行save(),saveOrUpdate()时变成持久的,当持久状态执行delete()时变成瞬时的,当脱管状态执行update(),saveOrUpdate(),lock()时变成持久状态,当持久状态执行evict(),close(),clear()时,持久状态变成脱管状态.
% G, x4 `: ^' j$ b ' }7 d, J# H; G8 R# R
(6).瞬时对象的id没有值,脱管对象的id是有值的.所以当没有值时执行save()方法,当有值时执行update()方法.) k- i; w# a) ~" V
1 D0 v; `0 V: \* U30.实体类或属性名与数据库关键字冲突问题" d( h/ W8 G) j% q' U
使用Oracle时,user是个关键字,可能出现问题,将表名添加反引号.2 Q+ e# u) j, @: v( c' c
& G& Q! [, p5 B. M31.使用Hibernate完成CRUD实验的步骤说明8 H6 f. C0 E# |& Q$ n4 m
(1).实验步骤:
6 z4 N, a/ S+ E6 Q( d第一步:设计domain对象User
- ~# I6 @& m- L$ r8 m( V7 ]第二步:设计UserDao接口+ y& A9 U9 r! H2 C
第三步:加入hibernate.jar和其依赖的包0 ^; j0 g4 e! }' a
第四步:编写User.hbm.xml映射文件,可以基于hibernate/eg目录下的org/hibernate/auction/User.hbm.xml修改# W/ W5 l4 F( o6 A# C9 X6 Y5 @2 a
第五步:编写hibernate.cfg.xml配置文件,可以基于hibernate/etc/hibernate.cfg.xml修改;必须提供的几个参数:connection.driver_class、connection.url、connection.username、connection.password、dialect、hbm2ddl.auto
/ N. \$ I5 K: U1 m! k第六步:编写HibernateUtils类,主要用来完成hibernate初始化和提供一个获得Session的方法( i A8 k0 P. y+ e- p5 v! M8 m0 [
第七步:实现UserDao接口
: P8 _% f1 D% ]; i. T8 ^
5 l# x n# q/ Y$ j32.事务的悲观锁和乐观锁. c8 |- r& o b. R
(1).悲观锁和乐观锁* w4 S+ T8 J% c# N
悲观锁由数据库来实现;乐观锁hibernate用version和timestamp来实现,悲观锁就相当于写锁,当自己在操作时,别人不能进行任何操作,
" K0 b- |9 Q8 K5 \0 t. ]5 E
M* J. Z B* W- f(2).可能多个人来读取同一个数据源,可能后一个人修改后的结果覆盖前一个人修改的结果,存在并发问题9 P: L; W( C/ w4 k
& ^+ N. ~0 m2 Y1 ?- A( A$ ]1 }0 w(3).悲观锁是不可取的,我们给每条记录添加一个版本号,当同时操作数据源时,判断版本号,如果版本号不符合,就不进行更新.假设刚开始版本号为0,同时来两个人进行操作,判断版本号是否为0,如果为0,就进行操作,操作完后版本号加一,那第二个人就发现版本号不等于0,就不会进行操作了,也不会覆盖前一个人进行的操作.. f0 v& g$ q- e8 c
0 B. N" h$ p) R$ r, y* F(4).在映射文件中:/ x3 `6 d* @- g% Z
<versionname="ver"/>该标签必须在id标签的下面,即是id的子标签.
/ r$ g& G# o @1 {7 `$ u% m3 E5 D ) W- l5 }2 D5 T0 J
(5).版本号的类型是整型的,也可以是日期型的4 y/ U( T' A7 X* W- v5 m
5 i4 c0 U/ |4 Y, g: g
(6).- P: b; n/ W0 o+ W7 O' |
Session s1=HibernateUtil.getSession();& Y( d* T2 ~: J E. ]. W
Transactiontx1=s1.beginTransaction();//第一个线程操作事务
+ E9 q% E4 ?. V+ kUser user1=(User)s1.get(User.class,id);
- i- X2 P$ T0 _7 RSession s2 =HibernateUtil.getSession();
3 W* K; g8 ~2 u3 g" Z! X/ a4 _/ t/ ?7 gTransactiontx2=s2.beginTransaction();//第二个线程操作事务) K5 b' n, X; f+ ~* Z5 Y8 g0 H( ]' I
User user2=(User)s2.get(User.class,id);1 D/ A1 ? ?( D
user1.getName().setFirstName("firstName1");% h, n% k/ o+ M+ N% Q! \
user2.getName().setFirstName("firstName2");( |" j8 d1 j1 v5 P
tx2.commit();//线程二先提交,成功了" m& q+ d8 Q+ Y1 |) B0 T) x8 x# Y
tx1.commit();//线程一提交不成功.因为版本号不一样.# o! A$ p$ L9 P
9 w9 z Y( K6 ~9 |4 K# j. S
33.事务与事务边界的相关知识
0 D% E! a% s/ C' o9 G(1).一个SessionFactory对应一个数据库,由JDBC实现
3 B! T2 m5 w0 C, i ) J/ E4 Z6 e" p8 X
(2).事务的控制应该在业务逻辑层实现.但是事务的对象是在DAO层,那么在业务逻辑层中调用事务的对象,就出现了耦合,所以要解决这个耦合,就需借助第三方架包了EJB,Spring
; V, u. [- R2 a: Q & H1 B3 B/ K) {
34.完善HibernateUtil类及hql查询入门
5 [% a3 e) ?" j0 v& Z4 B(1).HQL:面向对象的查询语言,与SQL不同,HQL中的对象名是区分大小写的(除了Java类和属性其他部分不区分大小写),HQL中查的是对象而不是和表,并且支持多态;HQL主要通过Query来操作,Query的创建方式:Query q=session.createQuery(hql);
2 K7 v# `) I' x: W( _$ Lfrom Person/ x$ [5 ]0 W; J
from User as userwhere user.name=:name//其中User是类不是表名,user是别名
1 J* m9 O4 [+ M, [3 g, E& a9 Yform User as userwhere user.name=:name and user.birthday<:birthday
( W+ H( X4 ?/ t* Y , r( a% u: v- a" C9 l" V- T3 |" W
(2).Criteria:是一种比HQL更面向对象的查询方式;Criteria的创建方式:Criteria crit=session.createCriteria(DomainClass.class);
/ V. y- u0 }$ ]9 _" i$ K简单属性条件如:
2 \& e; o6 q3 V8 ^0 Kcriteria.add(Restrictions.eq(propertyName,value)),criteria.add(Restrictions.eqProperty(propertyName,otherPropertyName))
5 y. U o9 z8 r9 g
# X$ j/ R, [+ a J' M! J$ J9 P(3).public staticvoid add(Object entity){//能够保存所有对象' t1 X. w$ H- |6 E7 P4 k, r: r
Session s=null;8 m* f! X7 C% h7 q6 O: C$ b
Transactiontx=null;' x" ]! A/ G* Y7 E+ P
try{
* _0 a7 B7 A9 Is=HibernateUtil.getSession();/ m3 u2 ?$ E4 a4 x
tx.s.beginTransaction();
6 M8 z; S0 Z6 E' Is.save(entity);9 Z) H2 y! q5 ^9 h$ x/ L( x: D
tx.commit();
8 H4 W. C- w- ~6 e}catch(HibernateExceptione){% K, d# t9 C ]0 t. t
if(tx!=null); F1 L& X9 F& D C# J. W
tx.rollback();//不仅要回滚,还有抛出异常
/ y. J0 b2 G6 ~ S8 T( u% [/ othrow e;9 i+ W- C2 M8 Q" p& I
}finally{# h0 S+ N* A0 f" Q! w# O. ]# j, E
if(s!=null)( ]. n/ b% }; E; a- `# g+ e# S# P
s.close();9 O- a9 Y/ Y: l7 [
}
9 M/ N9 H9 P7 U7 ~. V+ n2 ?# v}
3 Q) a5 Y( ^ j4 l' {同理更新,删除同时同样的道理9 S i: N$ \+ r+ w( R; T
1 c1 {5 ]4 ]7 m- d; [- `; s(4).执行HQL语句
0 r8 _/ q A/ R6 BSession s=null;
7 v. a: o. j. C8 qtry{1 [, J9 e' { W0 z
s=HibernateUtil.getSession();
8 F: x ~+ b- J% i( `3 NStringhql="from User as user where user.name=?";
& o) x3 }0 w1 [# K$ bQueryquery=s.createQuery(hql);, ~+ O" L: e, \# H0 a9 Y- i9 A
query.setString(0,name);//替换占位符# j, @* u0 s/ l" d. {3 ?1 J+ \
Listlist=query.list();//JDBC中的executQuery()类似
! _$ p2 l4 l/ b! ]8 d7 q7 x2 Dfor(Useruser:list{
' k' q$ {: o( i F3 \System.out.println(user.getName());
+ [4 _1 u/ V+ F4 z( }8 Q$ C1 T4 i}
& K4 L8 \1 B& u ^4 ^//Object obj=query.uniqueResult();当确定返回值只有一个的时候,使用这种方法.当查询有多个结果时,会出现异常
8 }6 C' q8 t2 p+ E, A}finally{# a {: T& H1 W* ^! V
if(s!=null)
, i" e; J, M; b* ws.close();
2 t8 j+ z$ _4 |+ H3 f}1 x X( P J# C9 F. S' z
支持多态,查询的话,子类对应数据库表也被查询,如果from Object的话,会把数据库中的表都查一遍,因为所有的类都是Object的子类., R |5 V6 c- z/ ]
( f. p; X! t* h6 Z) [! l( u
35.一对多关联关系的映射与原理分析- Y" d" [8 Z0 L E) t& V' s
(1).在Department的角度上是不是一对多了,在Department中定义:* l! {) B/ d7 c2 _. y# |
privateSet<Employee> emps;//一个department中有多个员工' h) Y4 u4 Q6 T- C- N, n- [8 g. z
& F+ T' V3 Y' h4 E1 _, t$ v
(2).映射文件:. o) `; K8 {5 M J5 Y) P- F* s* B
<classname="Department">6 T# E7 T* x6 q8 {" e' b' {( M
<idname="id"> [$ Y; ]: a, i9 C! z, E; L# y q
<generatorclass="native"/>
9 H: o1 E0 `0 k0 R3 U' s& r</id>$ y' t$ I) H# }
<propertyname="name"/>
8 t6 V' N/ b& G" N! a! o0 P<setname="emps">用set属性进行映射: B2 j) _, U2 L/ J* @
<keycoluem="depart_id"/>设置外键
- c- h& h) w( y' g' n! q* \<one-to-manyclass "Employee"/>( ]' \4 a6 [$ t: Z1 d/ B
</set>
9 J# i( E' c9 c+ r( W</class>
7 r4 K, h+ U# M
6 b; Q( u) t% J5 c(3).System.out.println("empsize:"+depart.getEmps().size());
! e: v4 G4 y7 ~ K打印出department中所有的employee人数.
9 P( F! V. N* K. ~$ @3 w
# H* H5 m8 K& X' S( R(4).首先添加employee,在添加到department,即告诉employee属于哪个department;多两条更新语句.9 T2 z: a1 S' t5 Q# u6 Z6 N, S
Set<Employee>emps = new HashSet<Employee>();
# q$ [0 |- H( y! _1 q7 w$ Uemps.add(emp1);
) n+ [ f0 ]; N( A7 nemps.add(emp2);
% |1 |( \6 W0 w5 N& U. U/ ]; Y ?# ~8 A2 cdepart.setEmps(emps);0 {" o3 W5 Y% M. t4 V, q6 T
告诉department有哪些employee
( a4 }2 T6 J" l# m0 o9 Kemp1.setDepart(depart);
" `% }6 W0 n0 m3 ^emp2.setDepart(depart);
& h' ~- d. v( P8 f7 g & d9 I/ f, F4 E
(5):ER图:Deparment:id(PK);Employee:id(PK),depart_id(FK1);
1 h! k4 s9 q$ A, c" u$ K+ N 4 ^2 N1 X, ?& m. u
36.一对多和多对多的懒加载分析
6 T( Z5 f/ v# Q/ z& M9 U(1).对于one-to-one懒加载方式体现出的效率不是很明显,查询身份证号时,把person的信息也查询出来,没有查询太多的信息,对效率的影响不是很大- N. m- T7 I+ m1 f) Y: I
: B4 O* `' A4 D- Z1 P7 {/ A(2).对于one-to-many懒加载方式就体现的很明显的了,当我们查询部门的详细信息时,可能把该部门的所有员工都查询出来,因为一个部门可能有很多员工,所以这时效率就明显降低了.
& u; s* g6 F# L6 ~% z5 ]" g / M) Z" n' L) X- q! ?' B
(3).缺省的是懒加载,当depart.getEmps()时,才会查询员工的信息,因为java中的set集合没有懒加载的功能,当我们的代码只是获取集合代理对象的引用,比没有调用该集合代理对象的方法,所以,hibernate在这里还用不着去查询数据库来填充集合代理,因此不会抛出"代理未初始化"的异常,如果将代码改为depart.getEmps().size(),就可以看到异常了.
5 y, j3 p: M9 ]0 J! k6 z* e& e . Z' n; M5 I9 {7 J7 A+ t
(4).对于many-to-many方式懒加载也很重要,因为涉及到三张表的查询.所以也需要懒加载的功能.
- U- _1 u/ @+ L9 W3 r% }9 M ) \$ q! j- j- Z
37.一对一的懒加载分析
7 Y+ c v, n$ F8 x3 Z(1).one-to-one在查询主对象的时候默认情况下不使用懒加载,使用一个关联查询.但是在查询从对象的时候使用了懒加载.
, V; |+ G1 _9 t) h
$ X9 M1 c! t3 k- I; v5 G(2).constrain=true是建立外键约束) S* N, ]7 [: u# E7 h# P
7 r; C+ R4 Q6 U- T6 }9 `(3).lazy="proxy",使用懒加载,默认的值也是proxy,还有false,true的取值3 c* ]1 M2 R, P. T
2 y" r9 b, C* a; I1 T# O( d9 u* {(4).fetch="join",使用什么方式去抓取,默认值为select,join是一次查询(表的连接),select是两次查询.当lazy="proxy"时,fetch="join"是无效的,它们俩之间的设置是互斥的.
( n4 L/ ]1 K5 d/ b! M f( U5 T3 R
4 e$ I, o' ^+ i5 a. M( C38.一对一外键关联关系的映射与原理分析
. r/ x* p$ d, p& g4 E(1).一对一:基于外键的one-to-one,可以描述为多对一,加上unique="true"约束<one-to-onename="idCard" property-ref="person"/><many-to-onename="person" column="person_id" unique="true"not-null="true"/>区别于多对一.只需将外键设置为唯一.
" p( l0 ^/ J- l# G1 x+ [ ; E8 e1 _4 j7 V
(2).对于IdCard的映射文件,其的id不是外部生成的,而是自增长的.
7 r8 w, x' d, U7 F4 L$ p6 E# c<generatorclass="native"/>对于Person的映射文件:<one-to-one name="idCard"property-ref="person"/>, L3 }5 l1 s! U: P7 H, ?
$ d9 }) w% `) [: g. H( W$ @+ A39.一对一主键关联关系的检索% k( I w7 L1 J8 d7 n- |' ?( l
(1).查询主对象:8 u4 h; K7 K T, N* m" d# P& O
Personp=(Person)get(Person.class,id);
& g1 M' B M! r8 g j0 _+ h- R; vSystem.out.println(p.getIdCard().getId());) R* h" x4 i' o1 V) i- y$ f& q o" w4 K
理论上是两次查询,但是实际只进行了一次查询,使用了表之间的关联join,效率上比两次查询高( g; ?, e# b& R. {- |" W( M
查询从对象:9 o& A" k" g( [/ P9 n
IdCardidCard=(IdCard)get(IdCard.class,id);
+ s9 y6 I1 E( y( YSystem.out.println(idCard.getPerson().getId());
! e2 m" v r* r* x' E: V5 o理论上和实际上都进行了两次查询
. ]+ {% @6 E S/ }% `! x # Z2 D! C3 M- U. r
40.一对一主键关联关系的映射与原理分析
* D6 G+ w% U- q& [& |(1).基于主键的one-to-one(person的映射文件)
1 e; {$ a8 S2 c* m! d<id name="id">
- G! ]0 J S3 \7 Z% e! Q<generatorclass="foregin"><paramname="property">idCard</param></generator> k' ]8 u8 n7 V/ A: b
</id>8 ~8 W* t; X: I8 K
<one-to-one name="idCard"constrained="true"/>
3 T* ]+ P8 t5 D; P% ]
4 x# D$ E- }; ^6 a$ X' a(2).对象模型:
- h/ X3 Q1 K0 {9 o& O9 Vpublic class Person{
2 E. w1 Z: \! O0 U# o$ Q7 w- q) T) E0 {private int id;
' h( v6 ]4 j0 Dprivate String name;
* _+ _' k3 J1 Q. Y+ ?private IdCard idCard;" p* D2 f7 Z) D3 T9 _- ]
//省略get/set方法$ P* `5 ?9 V" o
}) Y" W, {- B+ ]9 V
public class IdCard{/ n1 ?+ j+ z: F
private int id;
' I( r" h4 O k5 Q7 c& K Dprivate String name;
' G. f. Q& l7 i! I$ G. L0 T% `private Person person;
8 R1 ?$ }: Z( x W3 G) ]//省略get/set方法
2 a, r9 s6 @, }}( N# _9 X, T5 w/ a5 I2 r
; ]: C* W' ~9 h! `4 K9 } C2 l- g
(3).Person的映射文件:2 a; E; @6 o. O; L- Z3 \
<class name="Person">
! f1 y( \% |9 l+ W4 \! T<id name="id">
! T& ^! d1 P! L. h<generator class="native"/>" r: c! ^5 E; r3 i- F5 Y& h- L8 \
</id>
3 f+ d2 W L5 N<property name="name"/>, Z3 x* s: B* E* X
<one-to-one name="idCard"/>/ _" K I+ ]) H5 P2 [
</class>9 g7 n+ ?1 n! X% f1 i4 l
IdCard的映射文件:
/ V, N$ z$ J0 S9 b" T6 p' c<class name="IdCard">
; f7 s7 F5 S9 a6 i8 A<id name="id">
9 A3 r2 |6 N( Z" v. v7 p5 O* d<generator class="foregin">主键是由外部得到,不是自己得到的# h4 }. V; F4 G
<paramname="property">person</param>IdCard的id是由person得到的
5 W" Q: _2 V. P5 v+ _0 E0 c</generator>
* U9 ~3 V! W, H* T, f8 }2 L</id>
8 F$ I* K/ h8 J. b5 c<property name="name"/># [& c+ {" A; z5 h) v- ?
<one-to-one name="person" constrained="true"/>添加约束,配置外键.
! M' L0 N% T/ v</class>
. Q2 z9 m5 M( @! q7 q( I; eidcard中的主键是person中的外键8 g4 ~2 q7 |7 c
( ^% \% E6 w7 f8 g) k
(4).测试代码:
8 ~* i6 o( Q: S- S, D# n. }IdCard idCard = new IdCard();
$ E# g& j; {" C7 `# y. S# YPerson p=new Person();# K6 @7 k) q+ \4 K& E% \' `
p.setName("p1");
4 m- N+ B5 h8 N* X% c3 Vp.setIdCard(idCard);2 i" e1 A Y) m0 d# k1 K
idCard.setPerson(p);
8 O/ x- F" N4 F2 c' x$ ?7 y6 G9 rs.save(p);5 d! L5 S- {# j2 I* t+ F
s.save(idCard); E: @: T. U) _4 v4 ~
IdCard中的id是由Person得到的.只有主对象(person)存在,从对象(idcard)存在.. ]7 h' w3 U, r# t L ~
5 _2 B2 A4 d, T# Z- h4 l
(5).ER图:person:id(PK);IdCard:id(PK,FK)
" ]' m, F; W" S0 w: R' |! H; b
( |2 D$ @5 d: K9 E/ |/ e: M! v41.组件关联关系的映射与原理分析
3 m( g8 [0 A( Q: \& N(1).组件映射(User-Name):关联的属性是个复杂类型的持久化类,但不是实体即:数据库中没有表与该属性对应,但该类的属性要之久保存的
0 R3 ~# Y$ `5 s+ T8 ^<component name="name"class="com.test.hibernate.domain.Name">7 H" I* V# {6 S/ k- l( V
<property name="initial"/>. `! \. U8 y- E! ~( v- U! p
<property name="first"/>
" ]) F0 l- q2 O6 T1 k W" c<property name="last"/>
% h. L+ v- K, F, A# t* ?& s0 ?</component>. R2 f0 t1 U% U+ ?* G; T0 V
当组件的属性不能和表中的字段简单对应的时候可以选择实现:7 w3 d2 K* \6 t; D& B+ ~# W
org.hibernate.usertype.UserType或! j/ n9 U9 k4 D
org.hibernate.usertype.CompositeUserType+ e- V% V# u, {6 @# `. v, [3 @
. G$ U2 M3 W1 l0 {& S7 A) z+ m$ t(2).用户名name是个对象类型
7 E3 j) C& b$ e8 M2 Ipublic Class Name{! E2 m# E2 C: K& h$ n0 O3 D: _5 I
private String firstName;2 T2 a) K" [5 P# V0 `' {2 s) _
private String lastName;
0 q$ z4 C8 k2 m- }$ R! t' B4 w* a6 |6 M//get/set方法省略
+ { R: g7 h" ^2 |% L7 u}
4 A8 _! ~+ N6 R2 e; z想过使用一对一.一对多,多对一都可以,一个人只能有一个名字,一个人可以有多个名字.这是数据库中肯定有两张表:User和Name,但是现在Name的内容很小,不想设计成一个实体,不想在数据库中对应一张表,因为它太小了,此时就是用组件相关联,将用户user和名字name设计到同一张表中 |
|