一。什么时候会遇到1+N的问题?
前提:Hibernate默认表与表的关联方法是fetch="select",不是fetch="join",这都是为了懒加载而准备的。
1)一对多(<set><list>) ,在1的这方,通过1条sql查找得到了1个对象,由于关联的存在 ,那么又需要将这个对象关联的集合取出,所以合集数量是n还要发出n条sql,于是本来的1条sql查询变成了1 +n条 。
2)多对一<many-to-one> ,在多的这方,通过1条sql查询得到了n个对象,由于关联的存在,也会将这n个对象对应的1 方的对象取出, 于是本来的1条sql查询变成了1 +n条 。
3)iterator 查询时,一定先去缓存中找(1条sql查集合,只查出ID),在没命中时,会再按ID到库中逐一查找, 产生1+n条SQL
二。怎么解决1+N 问题?
1 )lazy=true, hibernate3开始已经默认是lazy=true了;lazy=true时不会立刻查询关联对象,只有当需要关联对象(访问其属性,非id字段)时才会发生查询动作。
2)使用二级缓存, 二级缓存的应用将不怕1+N 问题,因为即使第一次查询很慢(未命中),以后查询直接缓存命中也是很快的。刚好又利用了1+N 。
3) 当然你也可以设定fetch="join",一次关联表全查出来,但失去了懒加载的特性。
三。示例
1.两张表
create table t_group (
id integer not null auto_increment,
name varchar(255),
primary key (id)
)
create table t_user (
id integer not null auto_increment,
name varchar(255),
myGroup integer,
primary key (id)
)
alter table t_user
add index FKCB63CCB6D4A4BEC0 (myGroup),
add constraint FKCB63CCB6D4A4BEC0
foreign key (myGroup)
references t_group (id)
2.代码如下:
@Entity @Table(name="t_group") public class Group { private int id; private String name; private Set<User> users = new HashSet<User>(); public Group(){ } public Group(String name) { this.name = name; } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @OneToMany(mappedBy="group") public Set<User> getUsers() { return users; } public void setUsers(Set<User> users) { this.users = users; } }
@Entity @Table(name="t_user") public class User { private int id; private String name; private Group group; public User(){ } public User(String name) { this.name = name; } @ManyToOne @JoinColumn(name="myGroup") public Group getGroup() { return group; } public void setGroup(Group group) { this.group = group; } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
3.数据如下
4.默认情况下
@Test public void getAllUser(){ Session session = sf.openSession(); Transaction tx =session.beginTransaction(); List<User> groups = session.createQuery("from User").list(); //只要一条语句就可以取出全部内容。但hibernate默认会把关联的Group也取出来。 String string = "---------------------------- "; //由于每个user都不同group,所以会发出N条语句查询group.
//本例会发出1+10条 for(User g : groups){ string += g.getName(); } System.out.println(string); tx.commit(); session.close(); }
5.解决:
(1).在User类中标注group----fetch=FetchType.LAZY
@ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name="myGroup") public Group getGroup() { return group; }
(2).在Group类中写---------@BatchSize(size=10)
@Entity @Table(name="t_group") @BatchSize(size=10) public class Group {}
(3)使用连接查询
List<User> groups = session.createQuery("from User u left join fetch u.group g").list();
注意:group是属性名,而不是数据库字段名。 不然出错
6.实验花絮:
@Test public void saveGroupAndUser2(){ Session session = sf.openSession(); Transaction tx =session.beginTransaction(); for(int i=1; i<=10; i++) { Group g= new Group(); g.setName("g"+i); session.save(g); User u = new User(); u.setName("u"+i); u.setGroup(g); session.save(u); } tx.commit(); session.close(); }
参考下面的代码,上面红色字体的session.save(g)可以省略.不然org.hibernate.TransientObjectException: object references an unsaved transient instance
在User类中 @ManyToOne(cascade=CascadeType.ALL) @JoinColumn(name="myGroup") public Group getGroup() { return group; }