Hiberante_Annotation
Hibernate Tree映射
提取树,都很熟悉了,数据结构中的经典的二叉树,学过数据结构的人都知道那四种遍历的方法,在Hibernate中所谓的树状映射,其实就是在一张表中保存的数据中,数据之间都有关系,好比一所大学,就说我的学校长安大学了,长安大学下面有公路学院,汽车学院,材料学院,软件工程学院等等,那么每一个学院下面又有N多个系,依次类推,那么要是设计一张表,保存id和名字,将长安大学和她所有的学院和学院下所有的系保存起来,那么需要多添加一个字段,就是父Id比如说长安大学的id是1,材料学院id是2,那么材料学院的父id是1,如此等等,每一项数据都有一个父id,这样就类似一棵树了,但不一定是二叉树,那么树的根节点是长安大学,其父id为null.
在Hibernate中就可以用一个树状的ORM来编写实体了:
就拿上面的例子来说,有一个Node实体:
@Entity @Table(name= "treeNode") public class Node implements Serializable { private static finallong serialVersionUID = 2981633046554840135L; private int id; private Stringname; private Set<Node>children = new HashSet<Node>(); private Nodeparent; @Id @GeneratedValue public int getId() {returnid; } publicvoid setId(int id) {this.id = id; } public String getName() {returnname; } publicvoid setName(String name) {this.name = name; } //一对多,就是自己与自己的孩子的关系 @OneToMany(cascade=CascadeType.ALL,mappedBy = "parent",fetch=FetchType.EAGER) public Set<Node> getChildren() {returnchildren; } publicvoid setChildren(Set<Node> children) {this.children = children; } //多对一,就是自己和自己的父亲 @ManyToOne @JoinColumn(name = "parent_id")//指定一个名字 public Node getParent() {returnparent; } public void setParent(Node parent) {this.parent = parent; } }
这里只用了一个实体类,.可以这么理解,我们在上面的长安大学的例子中列举了
学校à学院à院系,三层结构的数,就是树的深度为三,用原来的一对多关系映射,我们可以设计三张表:
第一张表保存学校信息;
第二张表保存学院信息,多一个应用学校主键的外键
第三张表保存院系信息,多一个应用学院主键的外键
这么设计也是可以完成的,不过这么设计的表的数目增多,而且回到了一对多的关系映射了,这里可以改进设计为一个树状的结构,所有信息就可以保存在一张表中,简化了结构,就是在表中设计一个父id,相当于原来的一方外键引用另一方主键,到这里是自己的的外键引用自己的主键,有些拗口,呵呵!
上面红色高亮显示的注解不难理解了,只不过这里是在一个实体里面,这跟之前的多对一的配置相似了。
@OneToMany(cascade=CascadeType.ALL,mappedBy= "parent",fetch=FetchType.EAGER)
fetch=FetchType.EAGER这个默认是lazy,懒加载的,在这里这么配置,是方便待会测试时的效果,因为如果不这么设置,待会去数据的时候,会取一条发一条,看不出树状结构。
按照上面的注解生成的建表语句:
create table treeNode ( id integer not null auto_increment, name varchar(255), parent_id integer, primary key (id) ) alter table treeNode add index FK529C96C0D8DD21BF(parent_id), add constraint FK529C96C0D8DD21BF foreign key(parent_id) references treeNode (id)
自己的parent_id引用自己的主键id
再来一个保存数据的:
@Test public void testSave(){ Node n1 = new Node(); n1.setName("长安大学"); Node n2 = new Node(); n2.setName("软件工程学院"); Node n3 = new Node(); n3.setName("材料学院"); Node n4 = new Node(); n4.setName("软件设计"); Node n5 = new Node(); n5.setName("软件分析"); Node n6 = new Node(); n6.setName("材料成型机控制工程"); Node n7 = new Node(); n7.setName("金属材料"); n1.getChildren().add(n2); n2.setParent(n1); n1.getChildren().add(n3); n3.setParent(n1); n2.getChildren().add(n4); n4.setParent(n2); n2.getChildren().add(n5); n5.setParent(n2); n3.getChildren().add(n6); n6.setParent(n3); n3.getChildren().add(n7); n7.setParent(n3); Session session = sf.openSession(); Transaction tx = session.beginTransaction(); session.save(n1); tx.commit(); }
完了表中的数据是:
来一个取出数据的测试,这里只用取出一个长安大学的根节点(树中的术语)
@Test publicvoid testLoad(){ Session session = sf.openSession(); Node root = (Node)session.load(Node.class, 1); diGuiPrint(root,0); session.close(); } /** * 递归打印树状结构 * @param root * @param level */ privatevoid diGuiPrint(Node root,int level) { StringBuilder preStr = new StringBuilder(""); for (int i = 0; i < level; i++) { preStr.append("---"); } System.out.println(preStr+root.getName()); for (Node child : root.getChildren()) { //如果是子节点,就在加三个"---" diGuiPrint(child,level+1); } }
打印的效果:
长安大学
---材料学院
------金属材料
------材料成型机控制工程
---软件工程学院
------软件设计
------软件分析
总结:上面加fetch的类型设置为EAGER了,如果表中的数据很大的时候,这种效率是很低的,如果数据不大,就可以用,然而不设置这个却得不到想要的效果,控制台输出时是一条sql一个结果,当然在hibernate.cfg.xml中去掉show_sql的属性,就可以了,这样就看不到sql了,当然这不是一个着眼于问题的方法,如果实在需要,我们可以用Ajax等远程的异步访问,用到的时候才取出数据。