PlayFramework 2.x 技巧-@ManyToMany关联

转载请注明出处,保持署名

1. 简介    

    实体之间的关联关系是刚入门的同学比较头疼的问题,但是在日常开发中又是比较重要的技巧,熟练的使用实体关联,能够使代码清晰易懂,并且节省宝贵的开发时间。通常情况下,对于存在关联关系的两个实体,我们会明确指定其中一个实体为关系的维护端,而另一个实体为关系的查询端(反向端)。对于OneToMany或ManyToOne关系,JPA规范明确指出以Many一端为关系的维护端,One一端为关系的查询端;而对于ManyToMany的关联关系,双方都可以作为关系的维护端,因此在日常的开发工作中会出现一些误用。 本文主要从两个方面谈一下@ManyToMany关联,一方面是@ManyToMany的常用场景,另一方面是@ManyToMany的使用误区。

2. @ManyToMany的常用场景

1)单向@ManyToMany关联

    举例来说,User和Role是ManyToMany关联,假如我们只关心一个User有多少个roles,而不关心一个Role下有多少个users,这种情况就属于单向关联。在这种情况下,@ManyToMany注解只出现一次,并且是在关系的维护方,即User这一边,代码如下:

@Entity
public class User extends Model {
	
	@Id
	public long id;
	public String name;
	@ManyToMany(cascade=CascadeType.ALL)
	public List<Role> roles = new ArrayList<Role>();
	
	public static Finder<Long,User> find = new Finder<Long,User>(Long.class, User.class); 
}
@Entity
public class Role  extends Model {
	@Id
	public long id;
	public String name;
	
	public static Finder<Long,Role> find = new Finder<Long,Role>(Long.class, Role.class); 
}

注意,因为不需要根据Role查询User,所以Role实体没有users属性。

Code-1:

		User u1=new User();
		u1.name="u1";
		Role r1=new Role();
		r1.name="r1";
		u1.roles.add(r1);
		//r1,u1和r1之间的关系都会被保存进数据库,插入到数据库后,r1和u1的id都为1
		u1.save();


2)双@ManyToMany关联

    还是上面的User和Role的例子,这时不仅要从User查询关联的roles,而且要从Role查询关联的users,这种情况属于双向关联。在这种情况下,@ManyToMany注解在实体双方对会出现,但是需要注意的是,关系的查询端需要指定mappedBy属性(该属性在下文会有详细介绍)。理论上来讲,User和Role都可以作为关系的维护端,但是通常情况下,我们指定User为关系的维护端,Role为关系的查询端。代码如下:

@Entity
public class User extends Model {
	
	@Id
	public long id;
	public String name;
	@ManyToMany(cascade=CascadeType.ALL)
	public List<Role> roles = new ArrayList<Role>();
	
	public static Finder<Long,User> find = new Finder<Long,User>(Long.class, User.class); 
}
@Entity
public class Role  extends Model {
	@Id
	public long id;
	public String name;
	@ManyToMany(mappedBy="roles")
	public List<User> users = new ArrayList<User>();
	
	public static Finder<Long,Role> find = new Finder<Long,Role>(Long.class, Role.class); 
}
Code-2:在code-1的基础上,测试如下代码,
	  //通过Role查询关联的users,users列表中包含上面保存的u1,大小为1
	  Role.find.byId(1L).users.size();


3. @ManyToMany的使用误区

1)双向@ManyToMany关联时,实体双方都未指定mappedBy属性

    如果两边都未指定mappedBy属性,则EBean会认为双方都可以作为关系的维护端,为两个实体各自生成一张关系表,即USER_ROLE为User的关系维护表,ROLE_USER为Role的关系维护表。这样导致的结果是,双方的关系信息分别存在各自的关系表里,导致通过User保存的关系,Role实体无法查询到,反之亦然。测试代码如下:

Code-3:首先建立关系,

		User u1=new User();
		u1.name="u1";
		Role r1=new Role();
		r1.name="r1";
		u1.roles.add(r1);
		//r1,u1和r1之间的关系都会被保存进数据库,插入到数据库后,r1和u1的id都为1
		u1.save();
Code-4:查询关系:

	 //输出 users.size()为0
	 System.out.println(Role.find.setId(id).fetch("users").findUnique().users.size());

2)使用关系的查询端保存关系

你觉得下面的代码可以正确的保存关系吗?答案是否定的,有兴趣的同学可以自己试一下!

		User u1=new User();
		u1.name="u1";
		Role r1=new Role();
		r1.name="r1";
		r1.users.add(u1);
		r1.save(); //r1和u1间的关系并没有被保存,因为Role是关系的查询端,而不是维护端

3)mappedBy属性的使用

    mappedBy属性用于明确的指定该实体为关系的查询端,而另一个实体为关系的维护端。属性的值可以理解为另一个实体的外键,我们再来看一下Role实体的定义:

@Entity
public class Role  extends Model {
	@Id
	public long id;
	public String name;
	@ManyToMany(mappedBy="roles")
	public List<User> users = new ArrayList<User>();
	
	public static Finder<Long,Role> find = new Finder<Long,Role>(Long.class, Role.class); 
}

mappedBy的属性值为"roles",表示的意思是:根据关系表中的role.id来查询所有的roles。实际上在@OneToMany的注解中看起来更加直接一点。 

4) cascade注解属性的使用

    cascade注解属性一般用在关系的维护端,在上面的例子里就是User这一端。在ManyToMany关联中,级联删除只会删除关联关系,而不会删除关联实体。

	  //在删除u1时,u1和r1的关联关系会被删除,但是r1实体不会被删除
	  User.find.byId(1L).delete();


4. 小结

1)在双向关联关系中,明确使用mappedBy属性指定关系的查询端(反向端),另一端为关系的维护端;

2)关系的查询端(反向端)只能查询关系,而不能保存关系;

3)ManyToMany关系中的级联删除,只会删除关联关系,而不会删除关联实体。

    

你可能感兴趣的:(PlayFramework 2.x 技巧-@ManyToMany关联)