使用额外列映射多对多关联表

Posted

技术标签:

【中文标题】使用额外列映射多对多关联表【英文标题】:Mapping many-to-many association table with extra column(s) 【发布时间】:2011-07-04 20:52:46 【问题描述】:

我的数据库包含 3 个表: User 和 Service 实体是多对多的关系,并与 SERVICE_USER 表连接如下:

USERS - SERVICE_USER - 服务

SERVICE_USER 表包含额外的 BLOCKED 列。

执行此类映射的最佳方法是什么? 这些是我的实体类

@Entity
@Table(name = "USERS")
public class User implements java.io.Serializable 

private String userid;
private String email;

@Id
@Column(name = "USERID", unique = true, nullable = false,)
public String getUserid() 
return this.userid;


.... some get/set methods


@Entity
@Table(name = "SERVICES")
public class CmsService implements java.io.Serializable 
private String serviceCode;

@Id
@Column(name = "SERVICE_CODE", unique = true, nullable = false, length = 100)
public String getServiceCode() 
return this.serviceCode;

.... some additional fields and get/set methods

我按照这个例子http://giannigar.wordpress.com/2009/09/04/m ... using-jpa/ 下面是一些测试代码:

User user = new User();
user.setEmail("e2");
user.setUserid("ui2");
user.setPassword("p2");

CmsService service= new CmsService("cd2","name2");

List<UserService> userServiceList = new ArrayList<UserService>();

UserService userService = new UserService();
userService.setService(service);
userService.setUser(user);
userService.setBlocked(true);
service.getUserServices().add(userService);

userDAO.save(user);

问题是hibernate坚持用户对象和用户服务之一。 CmsService 对象没有成功

我尝试使用 EAGER fetch - 没有进展

是否可以通过上面提供的映射实现我期望的行为?

也许有一些更优雅的方法可以将多对多连接表与附加列进行映射?

【问题讨论】:

【参考方案1】:

由于SERVICE_USER表不是纯连接表,而是有额外的功能字段(阻塞),所以必须将其映射为一个实体,并将User和Service之间的多对多关联分解为两个OneToMany关联:一个用户有很多 UserServices,一个 Service 有很多 UserServices。

您还没有向我们展示最重要的部分:实体之间关系的映射和初始化(即您遇到问题的部分)。所以我会告诉你它应该是什么样子。

如果你使关系是双向的,那么你应该有

class User 
    @OneToMany(mappedBy = "user")
    private Set<UserService> userServices = new HashSet<UserService>();


class UserService 
    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;

    @ManyToOne
    @JoinColumn(name = "service_code")
    private Service service;

    @Column(name = "blocked")
    private boolean blocked;


class Service 
    @OneToMany(mappedBy = "service")
    private Set<UserService> userServices = new HashSet<UserService>();

如果您不对您的关系进行任何级联,那么您必须保留/保存所有实体。虽然只有关系的拥有方(此处为 UserService 方)必须初始化,但确保双方保持一致也是一个好习惯。

User user = new User();
Service service = new Service();
UserService userService = new UserService();

user.addUserService(userService);
userService.setUser(user);

service.addUserService(userService);
userService.setService(service);

session.save(user);
session.save(service);
session.save(userService);

【讨论】:

只是补充.. 虽然在我看来这是最好的方式(出于性能原因,我总是更喜欢将拥有 FK 的事物映射为实体),但它实际上并不是唯一的方式。您还可以将 SERVICE_USER 表中的值映射为一个组件(JPA 将其称为可嵌入),并使用来自用户和服务实体之一(或两者)的@ElementCollection UserService表的主键呢?它应该是用户和服务外键的组合。那是映射的吗? 我不会那样做的。复合键是痛苦的、低效的,Hibernate 建议不要使用复合键。只需将自动生成的 ID 用于任何其他实体,生活就会简单得多。为确保[userFK, serviceFK] 的唯一性,请使用唯一约束。 @GaryKephart:用自己的代码和自己的映射提出自己的问题。 hibernate 4有更优雅的决定吗?【参考方案2】:

我搜索了一种方法来映射一个多对多关联表与额外的列与 xml 文件配置中的休眠。

假设有两个表“a”和“c”与名为“extra”的列的多对多关联。因为我没有找到任何完整的例子,这是我的代码。希望它会有所帮助:)。

首先是 Java 对象。

public class A implements Serializable  

    protected int id;
    // put some others fields if needed ...   
    private Set<AC> ac = new HashSet<AC>();

    public A(int id) 
        this.id = id;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public Set<AC> getAC() 
        return ac;
    

    public void setAC(Set<AC> ac) 
        this.ac = ac;
    

    /** @inheritDoc */
    @Override
    public int hashCode() 
        final int prime = 97;
        int result = 1;
        result = prime * result + id;
        return result;
    

    /** @inheritDoc */
    @Override
    public boolean equals(Object obj) 
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof A))
            return false;
        final A other = (A) obj;
        if (id != other.getId())
            return false;
        return true;
    



public class C implements Serializable

    protected int id;
    // put some others fields if needed ...    

    public C(int id) 
        this.id = id;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    /** @inheritDoc */
    @Override
    public int hashCode() 
        final int prime = 98;
        int result = 1;
        result = prime * result + id;
        return result;
    

    /** @inheritDoc */
    @Override
    public boolean equals(Object obj) 
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof C))
            return false;
        final C other = (C) obj;
        if (id != other.getId())
            return false;
        return true;
    


现在,我们必须创建关联表。第一步是创建一个表示复杂主键(a.id、c.id)的对象。

public class ACId implements Serializable

    private A a;
    private C c;

    public ACId() 
        super();
    

    public A getA() 
        return a;
    
    public void setA(A a) 
        this.a = a;
    
    public C getC() 
        return c;
    
    public void setC(C c) 
        this.c = c;
    
    @Override
    public int hashCode() 
        final int prime = 31;
        int result = 1;
        result = prime * result + ((a == null) ? 0 : a.hashCode());
        result = prime * result
                + ((c == null) ? 0 : c.hashCode());
        return result;
    
    @Override
    public boolean equals(Object obj) 
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ACId other = (ACId) obj;
        if (a == null) 
            if (other.a != null)
                return false;
         else if (!a.equals(other.a))
            return false;
        if (c == null) 
            if (other.c != null)
                return false;
         else if (!c.equals(other.c))
            return false;
        return true;
    

现在让我们自己创建关联对象。

public class AC implements java.io.Serializable

    private ACId id = new ACId();
    private String extra;

    public AC()

    

    public ACId getId() 
        return id;
    

    public void setId(ACId id) 
        this.id = id;
    

    public A getA()
        return getId().getA();
    

    public C getC()
        return getId().getC();
    

    public void setC(C C)
        getId().setC(C);
    

    public void setA(A A)
        getId().setA(A);
    

    public String getExtra() 
        return extra;
    

    public void setExtra(String extra) 
        this.extra = extra;
    

    public boolean equals(Object o) 
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        AC that = (AC) o;

        if (getId() != null ? !getId().equals(that.getId())
                : that.getId() != null)
            return false;

        return true;
    

    public int hashCode() 
        return (getId() != null ? getId().hashCode() : 0);
    

此时,是时候使用 hibernate xml 配置映射我们所有的类了。

A.hbm.xml 和 C.hxml.xml(完全一样)。

<class name="A" table="a">
        <id name="id" column="id_a" unsaved-value="0">
            <generator class="identity">
                <param name="sequence">a_id_seq</param>
            </generator>
        </id>
<!-- here you should map all others table columns -->
<!-- <property name="otherprop" column="otherprop" type="string" access="field" /> -->
    <set name="ac" table="a_c" lazy="true" access="field" fetch="select" cascade="all">
        <key>
            <column name="id_a" not-null="true" />
        </key>
        <one-to-many class="AC" />
    </set>
</class>

<class name="C" table="c">
        <id name="id" column="id_c" unsaved-value="0">
            <generator class="identity">
                <param name="sequence">c_id_seq</param>
            </generator>
        </id>
</class>

然后是关联映射文件,a_c.hbm.xml。

<class name="AC" table="a_c">
    <composite-id name="id" class="ACId">
        <key-many-to-one name="a" class="A" column="id_a" />
        <key-many-to-one name="c" class="C" column="id_c" />
    </composite-id>
    <property name="extra" type="string" column="extra" />
</class>

这是要测试的代码示例。

A = ADao.get(1);
C = CDao.get(1);

if(A != null && C != null)
    boolean exists = false;
            // just check if it's updated or not
    for(AC a : a.getAC())
        if(a.getC().equals(c))
            // update field
            a.setExtra("extra updated");
            exists = true;
            break;
        
    

    // add 
    if(!exists)
        ACId idAC = new ACId();
        idAC.setA(a);
        idAC.setC(c);

        AC AC = new AC();
        AC.setId(idAC);
        AC.setExtra("extra added"); 
        a.getAC().add(AC);
    

    ADao.save(A);

【讨论】:

【参考方案3】:

如前所述,使用 JPA,为了有机会拥有额外的列,您需要使用两个 OneToMany 关联,而不是单个 ManyToMany 关系。 您还可以添加具有自动生成值的列;这样,它可以作为表的主键,如果有用的话。

例如,额外类的实现代码应该是这样的:

@Entity
@Table(name = "USER_SERVICES")
public class UserService

    // example of auto-generated ID
    @Id
    @Column(name = "USER_SERVICES_ID", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long userServiceID;



    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "USER_ID")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SERVICE_ID")
    private Service service;



    // example of extra column
    @Column(name="VISIBILITY")    
    private boolean visibility;



    public long getUserServiceID() 
        return userServiceID;
    


    public User getUser() 
        return user;
    

    public void setUser(User user) 
        this.user = user;
    

    public Service getService() 
        return service;
    

    public void setService(Service service) 
        this.service = service;
    

    public boolean getVisibility() 
        return visibility;
    

    public void setVisibility(boolean visibility) 
        this.visibility = visibility;
    


【讨论】:

以上是关于使用额外列映射多对多关联表的主要内容,如果未能解决你的问题,请参考以下文章

具有额外多对多关系的 JPA 多对多

6django操作表多对多实战

一口一口吃掉Hibernate——多对多关联映射

(转)Hibernate框架基础——多对多关联关系映射

使用重复的额外列使多对多休眠

Mysql连表之多对多