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

我有以下表格

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 client; private ObjectProperty trainer; private ObjectProperty validFrom; private ObjectProperty validTo; private ObjectProperty type; private List 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 clientProperty() { return client; } public void setClient(Person client) { this.client.set(client); } @OneToOne @JoinColumn(name = "trainer") public Person getTrainer() { return trainer.get(); } public ObjectProperty trainerProperty() { return trainer; } public void setTrainer(Person trainer) { this.trainer.set(trainer); } @Column public Date getValidFrom() { return validFrom.get(); } public ObjectProperty validFromProperty() { return validFrom; } public void setValidFrom(Date validFrom) { this.validFrom.set(validFrom); } @Column public Date getValidTo() { return validTo.get(); } public ObjectProperty 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 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 getExercises() { return exercises; } public void setExercises(List 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; private ObjectProperty training; private ObjectProperty value; private ObjectProperty 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 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 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 trainingProperty() { return training; } public void setTraining(Training training) { this.training.set(training); } @Column(name = "value") public String getValue(){ return value.get(); } public ObjectProperty 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; protected ObjectProperty training; protected ObjectProperty 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, ObjectProperty training, ObjectProperty 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 exerciseProperty() { return exercise; } public void setExercise(Exercise exercise) { this.exercise.set(exercise); } public Training getTraining() { return training.get(); } public ObjectProperty trainingProperty() { return training; } public void setTraining(Training training) { this.training.set(training); } public Parameter getParameter() { return parameter.get(); } public ObjectProperty 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 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 

好的,看。 你所拥有的是一个设计问题,而不是一个普遍的问题。 首先,据我所知,你想制作一套独特的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的引用。 如果您正在尝试获取使用任何特定TrainingplanExercise ,那么您将需要双向而不是单向关系 ,但这是一个不同的问题。 否则,我不知道您为什么要从TrainingplanExercise返回TrainingplanExercise 。 此外,您将TrainingPlan的引用添加到TrainingplanExerciseId compositeKey ,这将要求TrainingPlan被序列化,这实际上不能作为唯一ID。

现在您可以将单独的练习放入表中:

 public TrainingplanExercise createExercise(String exercise, String parameter) { TrainingplanExercise trainingplanExercise = new TrainingplanExercise(new TrainingplanExerciseId(exercise, parameter)); em.persist( trainingplanExercise ); return trainingplanExercise; } 

之后,您希望使用可能的TrainingplanExercise's进行任意数量的TrainingPlan's ,您可以使用此Entity

 @Entity public class TrainingPlan implements Serializable { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @ManyToMany(fetch=FetchType.EAGER) private List trainingplanExercises = new ArrayList(); ... getters, setters, } 

您有一个ManyToMany关系,因为TrainingPlan引用了许多TrainingplanExercise'sTrainingplanExercise被许多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提供程序将在link table上添加一个额外constraint ,以确保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 。 如果尝试创建具有重复exerciseparameterTrainingplanExercise ,您将获得Unique index or primary key violationexception,并且将回滚该事务。

编辑:为了阅读TrainingPlans ,可以使用以下内容:

 public List listTrainingPlans() { CriteriaQuery criteria = em.getCriteriaBuilder().createQuery(TrainingPlan.class); criteria.select(criteria.from(TrainingPlan.class)); List trainingPlans = em.createQuery(criteria).getResultList(); return trainingPlans; } 

请注意,由于List trainingplanExercises设置为FetchType.EAGER因此此特定查询将FetchType.EAGER整个数据库。 FetchType.EAGER可能不是读取单个TrainingPlan的问题,但是如果您只想要一个TrainingPlan's列表而没有获得所有细节,那么您需要FetchType.LAZY应该如何实现FetchType.LAZY

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

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