如何使用复合键映射多对多

时间:2016-01-31 08:46:52

标签: java hibernate many-to-many

我有以下表格

Trainingplan
    TrainingplanID int(11) AI PK
    Trainer int(11)
    Client int(11)
    validFrom date
    validTo date
    type int(11)

TrainingplanExercises
    trainingplan int(11) PK
    exercise int(11) PK
    parameter int(11) PK
    value varchar(45)

不,我在使用Hibernate连接时遇到问题。我做了以下事情: 包豆;

@Entity
@Table(name = "Trainingplan")
public class Training {

    private IntegerProperty id;
    private ObjectProperty<Person> client;
    private ObjectProperty<Person> trainer;
    private ObjectProperty<Date> validFrom;
    private ObjectProperty<Date> validTo;
    private ObjectProperty<TrainingplanType> type;
    private List<TrainingplanExercise> exercises;

    public Training(int id, Person client, Person trainer, Date validFrom, Date validTo, TrainingplanType type) {
        this.id = new SimpleIntegerProperty(id);
        this.client = new SimpleObjectProperty<>(client);
        this.trainer = new SimpleObjectProperty<>(trainer);
        this.validFrom = new SimpleObjectProperty<>(validFrom);
        this.validTo = new SimpleObjectProperty<>(validTo);
        this.type = new SimpleObjectProperty<>(type);
        exercises = FXCollections.observableArrayList();
    }

    public Training(Person client, Person trainer, Date validFrom, Date validTo, TrainingplanType type){
        this(0, client, trainer, validFrom, validTo, type);
    }

    public Training(){
        this(0, null,null,null,null, null);
    }

    @OneToOne
    @JoinColumn(name = "client")
    public Person getClient() {
        return client.get();
    }

    public ObjectProperty<Person> clientProperty() {
        return client;
    }

    public void setClient(Person client) {
        this.client.set(client);
    }

    @OneToOne
    @JoinColumn(name = "trainer")
    public Person getTrainer() {
        return trainer.get();
    }

    public ObjectProperty<Person> trainerProperty() {
        return trainer;
    }

    public void setTrainer(Person trainer) {
        this.trainer.set(trainer);
    }

    @Column
    public Date getValidFrom() {
        return validFrom.get();
    }

    public ObjectProperty<Date> validFromProperty() {
        return validFrom;
    }

    public void setValidFrom(Date validFrom) {
        this.validFrom.set(validFrom);
    }

    @Column
    public Date getValidTo() {
        return validTo.get();
    }

    public ObjectProperty<Date> validTillProperty() {
        return validTo;
    }

    public void setValidTo(Date validTill) {
        this.validTo.set(validTill);
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "TrainingplanID")
    public int getId() {
        return id.get();
    }

    public IntegerProperty idProperty() {
        return id;
    }

    public void setId(int id) {
        this.id.set(id);
    }

    @OneToOne
    @JoinColumn(name = "type")
    public TrainingplanType getType() {
        return type.get();
    }

    public ObjectProperty<TrainingplanType> typeProperty() {
        return type;
    }

    public void setType(TrainingplanType type) {
        this.type.set(type);
    }

    @ManyToMany()
    @JoinTable(name="TrainingplanExercises",
            joinColumns={@JoinColumn(name="trainingplan")},
            inverseJoinColumns={@JoinColumn(name="trainingplan"), @JoinColumn(name="exercise"), @JoinColumn(name="parameter")})
    public List<TrainingplanExercise> getExercises() {
        return exercises;
    }

    public void setExercises(List<TrainingplanExercise> exercises) {
        this.exercises = exercises;
    }

    @Override
    public String toString() {
        return "Training{" +
                "id=" + getId() +
                ", client=" + getClient() +
                ", trainer=" + getTrainer() +
                ", validFrom=" + getValidFrom() +
                ", validTill=" + getValidTo() +
                ", type=" + getType() +
                '}';
    }

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

        Training training = (Training) o;

        return id != null ? id.equals(training.id) : training.id == null;

    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

TrainingplanExercise.java

@Entity
@Table(name = "TrainingplanExercises")
@IdClass(TrainingplanExerciseId.class)
public class TrainingplanExercise {

    private ObjectProperty<Exercise> exercise;
    private ObjectProperty<Training> training;
    private ObjectProperty<String> value;
    private ObjectProperty<Parameter> parameter;

    public TrainingplanExercise(Exercise exercise, Training training, String value, Parameter parameter){
        this.exercise = new SimpleObjectProperty<>(exercise);
        this.training = new SimpleObjectProperty<>(training);
        this.value = new SimpleObjectProperty<>(value);
        this.parameter = new SimpleObjectProperty<>(parameter);
    }

    public TrainingplanExercise(){
        this(null,null,null,null);
    }

    @Id
    @OneToOne
    @JoinColumn(name = "parameter")
    public Parameter getParameter() {
        return parameter.get();
    }

    public ObjectProperty<Parameter> parameterProperty() {
        return parameter;
    }

    public void setParameter(Parameter parameter) {
        this.parameter.set(parameter);
    }

    @Id
    @OneToOne
    @JoinColumn(name = "exercise")
    public Exercise getExercise() {
        return exercise.get();
    }

    public ObjectProperty<Exercise> exerciseProperty() {
        return exercise;
    }

    public void setExercise(Exercise exercise) {
        this.exercise.set(exercise);
    }

    @Id
    @OneToOne
    @JoinColumn(name = "trainingplan")
    public Training getTraining() {
        return training.get();
    }

    public ObjectProperty<Training> trainingProperty() {
        return training;
    }

    public void setTraining(Training training) {
        this.training.set(training);
    }

    @Column(name = "value")
    public String getValue(){
        return value.get();
    }

    public ObjectProperty<String> valueProperty() {
        return value;
    }

    public void setValue(String value) {
        this.value.set(value);
    }

    @Override
    public String toString() {
        return "TrainingplanExercise{" + "exercise=" + exercise + ", training=" + training + ", value=" + value + '}';
    }

}

 class TrainingplanExerciseId implements Serializable{
     protected ObjectProperty<Exercise> exercise;
     protected ObjectProperty<Training> training;
     protected ObjectProperty<Parameter> parameter;

     public TrainingplanExerciseId() {
         if(exercise == null)
             exercise = new SimpleObjectProperty<>(null);

         if(training == null)
             training = new SimpleObjectProperty<>(null);

         if(parameter == null)
             parameter = new SimpleObjectProperty<>(null);
     }

     public TrainingplanExerciseId(ObjectProperty<Exercise> exercise, ObjectProperty<Training> training, ObjectProperty<Parameter> parameter) {
         this.exercise = exercise;
         this.training = training;
         this.parameter = parameter;
     }

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

         TrainingplanExerciseId that = (TrainingplanExerciseId) o;

         if (exercise != null ? !exercise.equals(that.exercise) : that.exercise != null) return false;
         if (training != null ? !training.equals(that.training) : that.training != null) return false;
         return parameter != null ? parameter.equals(that.parameter) : that.parameter == null;

     }

     @Override
     public int hashCode() {
         int result = exercise != null ? exercise.hashCode() : 0;
         result = 31 * result + (training != null ? training.hashCode() : 0);
         result = 31 * result + (parameter != null ? parameter.hashCode() : 0);
         return result;
     }

     public Exercise getExercise() {
         return exercise.get();
     }

     public ObjectProperty<Exercise> exerciseProperty() {
         return exercise;
     }

     public void setExercise(Exercise exercise) {
         this.exercise.set(exercise);
     }

     public Training getTraining() {
         return training.get();
     }

     public ObjectProperty<Training> trainingProperty() {
         return training;
     }

     public void setTraining(Training training) {
         this.training.set(training);
     }

     public Parameter getParameter() {
         return parameter.get();
     }

     public ObjectProperty<Parameter> parameterProperty() {
         return parameter;
     }

     public void setParameter(Parameter parameter) {
         this.parameter.set(parameter);
     }
 }

现在,当我想要保存新培训时,我收到此错误:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'TrainingplanID' in 'field list'

因为这个SQL:

Hibernate: insert into TrainingplanExercises (TrainingplanID, trainingplan, exercise, parameter) values (?, ?, ?, ?)

我该如何解决这个问题? 如果我将joinColumn更改为“trainingplan”,我会收到有两个相同列的错误。如果我从反向列中删除“trainingplan”,我会收到一个错误,因为外部约束需要3列

编辑: 尝试评论中的内容。我确实尝试过OneToMany / ManyToOne:

@Id
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "trainingplan", nullable = false)
public Training getTraining() {
    return training.get();
}

@OneToMany(fetch = FetchType.EAGER, mappedBy = "training")
public List<TrainingplanExercise> getExercises() {
    return exercises;
}

如果我现在尝试将训练保存到数据库,它可以正常工作。 假设我想从数据库中获取Trainingplan,并添加新的TrainingplanExercises。我会使用这段代码:

Exercise ex = (Exercise) db.getAll(Exercise.class).get(1);


Training t = (Training) db.getAll(Training.class).get(0);


TrainingplanExercise te = new TrainingplanExercise(ex, t, "asdf", ex.getParameters().get(0));
TrainingplanExercise te1 = new TrainingplanExercise(ex, t, "asdf", ex.getParameters().get(1));
TrainingplanExercise te2 = new TrainingplanExercise(ex, t, "asdf", ex.getParameters().get(2));
TrainingplanExercise te3 = new TrainingplanExercise(ex, t, "asdf", ex.getParameters().get(3));



t.getExercises().clear();
t.getExercises().add(te);
t.getExercises().add(te1);
t.getExercises().add(te2);
t.getExercises().add(te3);

db.updateObj(t);

我得到了这个例外:

Exception in thread "main" org.hibernate.exception.LockTimeoutException: could not execute statement
    at org.hibernate.dialect.MySQLDialect$1.convert(MySQLDialect.java:447)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:126)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:112)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:211)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:62)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3581)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:104)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
    at db.Database.updateObj(Database.java:100)
    at db.Database.main(Database.java:171)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:998)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3835)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3771)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2582)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2535)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1911)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2145)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2081)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2066)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
    ... 19 more

2 个答案:

答案 0 :(得分:2)

好的,看。你所拥有的是一个设计问题,而不是一个普遍的问题。首先,据我所知,你想制作一套独特的TrainingplanExercise's。为此,您有Entity

@Entity
public class TrainingplanExercise implements Serializable {
    @EmbeddedId private TrainingplanExerciseId trainingplanExerciseId;

    public TrainingplanExercise() {}        
    public TrainingplanExercise(TrainingplanExerciseId trainingplanExerciseId) {
        this.trainingplanExerciseId = trainingplanExerciseId;
    }
    ... other fields ...   
}

上述Entity与原始Entity之间的区别在于我已将ID设为EmbeddableId。为了确保只在TrainingplanExercise's中添加了唯一的练习,您的compositeKey被定义为一个单独的类:

@Embeddable
public class TrainingplanExerciseId implements Serializable {
    private String exercise;
    private String parameter;

    public TrainingplanExerciseId() {}
    public TrainingplanExerciseId(String exercise, String parameter) {
        this.exercise = exercise;
        this.parameter = parameter;
    }

    ... getters, setters, hashCode, and equals
}

在这里,我创建了课程Embeddable,以便它可以用作ID。你试图宣布compositeKey的方式没有任何意义;您试图将TrainingplanExercise Entity中的每个字段声明为ID,但您只能拥有一个ID

model的不同之处在于TrainingplanExerciseId compositeKey不包含对TrainingPlan的引用。如果您要获取使用任何特定TrainingPlan's的{​​{1}}列表,那么您需要Bidirectional instead of a Unidirectional relationship,但这是一个不同的问题。否则,我不知道您为何要从TrainingplanExercise返回TrainingPlan。此外,您将TrainingplanExercise引用到TrainingPlan TrainingplanExerciseId,这需要compositeKey序列化,这实际上不会作为一个唯一身份证。

现在你可以将个别练习放入表格中:

TrainingPlan

之后,您希望使用可能public TrainingplanExercise createExercise(String exercise, String parameter) { TrainingplanExercise trainingplanExercise = new TrainingplanExercise(new TrainingplanExerciseId(exercise, parameter)); em.persist( trainingplanExercise ); return trainingplanExercise; } 的任意数量的TrainingPlan's,您使用此TrainingplanExercise's执行此操作:

Entity

您之间存在@Entity public class TrainingPlan implements Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToMany(fetch=FetchType.EAGER) private List<TrainingplanExercise> trainingplanExercises = new ArrayList<TrainingplanExercise>(); ... getters, setters, } 关系,因为ManyToMany引用了许多TrainingPlan,许多TrainingplanExercise's使用了TrainingplanExercise。除了TrainingPlan's之外,您不需要任何特殊注释,ManyToMany提供商会创建JPA,将每个link table的密钥放入一行,如下所示:

Entity

如果您将其声明为create table TrainingPlan_TrainingplanExercise ( TrainingPlan_id bigint not null, trainingplanExercises_exercise varchar(255) not null, trainingplanExercises_parameter varchar(255) not null ); 关系,则OneToMany提供商会在JPA上额外constraint,确保link table不能成为TrainingplanExercise链接到多个TrainingPlan,因此您不希望这样。仅举例来说,这就是约束的样子。

alter table TrainingPlan_TrainingplanExercise 
    add constraint UK_t0ku26ydvjkrme5ycrnlechgi  unique (trainingplanExercises_exercise, trainingplanExercises_parameter);

创建和更新TrainingPlans很简单:

public TrainingPlan createTrainingPlan() {
    TrainingPlan trainingPlan = new TrainingPlan();
    em.persist(trainingPlan);
    return trainingPlan;
}
public TrainingPlan updateTrainingPlan(TrainingPlan trainingPlan) {
    return em.merge(trainingPlan);
}

现在,您可以创建TrainingplanExercisesTrainingPlans,并将练习添加到培训计划中并进行更新。

TrainingplanExercise squats20 = trainingService.createExercise("Squats", "20");
TrainingplanExercise lifts10 = trainingService.createExercise("Lifts", "10");
TrainingplanExercise crunches50 = trainingService.createExercise("Crunches", "50");

TrainingPlan trainingPlan = trainingService.createTrainingPlan();
trainingPlan.getTrainingplanExercises().add( squats20 );
trainingPlan.getTrainingplanExercises().add( lifts10 );
trainingService.updateTrainingPlan(trainingPlan);

trainingPlan = trainingService.createTrainingPlan();
trainingPlan.getTrainingplanExercises().add( lifts10 );
trainingPlan.getTrainingplanExercises().add( crunches50 );
trainingService.updateTrainingPlan(trainingPlan);

另请注意,您的应用程序面临的挑战是确保用户只创建唯一的TrainingplanExercises。如果尝试创建具有重复TrainingplanExerciseexercise的{​​{1}},您将收到parameter异常,并且该事务将被回滚。

编辑:要阅读Unique index or primary key violation,可以使用以下内容:

TrainingPlans

请注意,由于public List<TrainingPlan> listTrainingPlans() { CriteriaQuery<TrainingPlan> criteria = em.getCriteriaBuilder().createQuery(TrainingPlan.class); criteria.select(criteria.from(TrainingPlan.class)); List<TrainingPlan> trainingPlans = em.createQuery(criteria).getResultList(); return trainingPlans; } 设置为List<TrainingplanExercise> trainingplanExercises,因此此特定查询将引入整个数据库。 FetchType.EAGER阅读单个FetchType.EAGER可能不是问题,但如果您只想获得TrainingPlan的列表而未获取所有详细信息,那么您需要解决如何实施TrainingPlan's

答案 1 :(得分:0)

您是否尝试使用多对一映射,因为无论如何它都是您使用外键所拥有的。然后你可以尝试类似的东西:

@Id
@ManyToOne( cascade = {CascadeType.PERSIST}, targetEntity=Trainingplan.class )
@JoinColumn(name = "trainingplan")
public Training getTraining() {}