Сопоставление таблицы ассоциации "многие ко многим" с дополнительными столбцами

131

Моя база данных содержит 3 таблицы: сущности User и Service имеют отношение «многие ко многим» и объединены с таблицей SERVICE_USER следующим образом:

ПОЛЬЗОВАТЕЛИ - 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);

Проблема в том, что в спящем режиме сохраняется объект User и объект UserService. Нет успеха с объектом CmsService

Я пробовал использовать EAGER fetch - без прогресса

Можно ли добиться ожидаемого поведения с помощью приведенного выше сопоставления?

Может быть, есть какой-нибудь более элегантный способ сопоставить таблицу соединения многие-многие с дополнительным столбцом?

archie_by
источник

Ответы:

192

Поскольку таблица SERVICE_USER не является чистой таблицей соединения, но имеет дополнительные функциональные поля (заблокированы), вы должны отобразить ее как объект и разложить связь многие-многие между пользователем и службой на две ассоциации OneToMany: один пользователь имеет много 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);
JB Nizet
источник
2
Просто добавлю ... Хотя это, на мой взгляд, лучший способ (я всегда предпочитаю отображать объект, владеющий FK, как объект из соображений производительности), на самом деле это не единственный способ. Вы также можете сопоставить значения из таблицы SERVICE_USER как компонент (то, что JPA называет встраиваемым) и использовать @ElementCollectionиз одного (или обоих) объектов User и Service.
Стив Эберсол
6
А как насчет первичного ключа таблицы UserService? Это должна быть комбинация внешних ключей пользователя и службы. Это нанесено на карту?
Йонас Грёгер
24
Я бы не стал так. Составные ключи болезненны, неэффективны, и Hibernate не рекомендует использовать составные ключи. Просто используйте автоматически сгенерированный идентификатор, как для любой другой сущности, и жизнь будет намного проще. Чтобы гарантировать уникальность [userFK, serviceFK], используйте уникальное ограничение.
JB Nizet
1
@GaryKephart: задайте свой вопрос, используя свой код и собственное отображение.
JB Nizet,
1
@gstackoverflow: Hibernate 4 ничего не меняет в этом отношении. Я действительно не понимаю, насколько это неэлегантно.
JB Nizet
5

Я ищу способ сопоставить таблицу ассоциации "многие ко многим" с дополнительными столбцами с гибернацией в конфигурации файлов 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);
}
Жар
источник
1

Как было сказано ранее, с 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;
    }

}
ingitaly
источник