如何用复合键映射多对多 [英] how to map Many to Many with composite key

查看:131
本文介绍了如何用复合键映射多对多的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下表格:

 训练计划
训练计划ID int(11)AI PK
Trainer int (11)
客户端int(11)
validFrom日期
validTo日期
类型int(11)

TrainingplanExercises
trainingplan int(11) )PK
练习int(11)PK
参数int(11)PK
varchar(45)

不,我在将它们与Hibernate连接时遇到问题。我做了以下事情:
package beans;

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

private IntegerProperty id;
private ObjectProperty< Person>客户;
private ObjectProperty< Person>培训师;
private ObjectProperty< Date> validFrom;
private ObjectProperty< Date>有效;
private ObjectProperty< TrainingplanType>类型;
私人列表< TrainingplanExercise>练习;
$ b $ 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();
}

公共培训(人员客户端,人员培训师,日期validFrom,日期validTo,TrainingplanType类型){
this(0,client,trainer,validFrom,validTo,type) ;


public Training(){
this(0,null,null,null,null,null);
$
$ b @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);
$
$ b @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);

$ b @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(){
返回类型;
}

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(){
返回练习;
}

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

@Override
public String toString(){
returnTraining {+
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;

培训培训=(培训)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< String>值;
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);

$ b @Id
@OneToOne
@JoinColumn(name =parameter)
public Parameter getParameter(){
return parameter 。得到();
}

public ObjectProperty< Parameter> parameterProperty(){
返回参数;
}

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

$ b $ @ @
@OneToOne
@JoinColumn(name =exercise)
public练习getExercise(){
return exercise 。得到();
}

public ObjectProperty< Exercise> exerciseProperty(){
返回练习;
}

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

@Id
@OneToOne
@JoinColumn(name =trainingplan)
公开培训getTraining(){
return training 。得到();
}

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

public void setTraining(训练训练){
this.training.set(训练);

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

public ObjectProperty< String> valueProperty(){
返回值;
}

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

$ b @Override
public String toString(){
returnTrainingplanExercise {+exercise =+ exercise +,training =+ training + ,value =+ value +'}';
}

}

class TrainingplanExerciseId implements Serializable {
protected ObjectProperty< Exercise>行使;
protected ObjectProperty< Training>训练;
protected ObjectProperty< Parameter>参数;
$ b $ public TrainingplanExerciseId(){
if(exercise == null)
exercise = new SimpleObjectProperty<>(null);

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

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

$ b $ 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;
返回参数!= 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);
返回结果;
}

public练习getExercise(){
return exercise.get();
}

public ObjectProperty< Exercise> exerciseProperty(){
返回练习;
}

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


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

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

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

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

public ObjectProperty< Parameter> parameterProperty(){
返回参数;
}

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


现在,当我想保存新的培训时,我得到这个错误:

 原因:com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:'TrainingplanID'字段列表'

因为这个SQL:

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

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



编辑:
尝试某些东西评论。我尝试了OneToMany / ManyToOne:

$ p code $ @ b $ b @ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name =trainingplan,nullable = false)
公开培训getTraining(){
return training.get();
}

@OneToMany(fetch = FetchType.EAGER,mappedBy =training)
public List< TrainingplanExercise> getExercises(){
返回练习;
}

如果我现在尝试将训练存储在数据库中,
比方说,我想从数据库中获得一个培训计划,并添加新的TrainingplanExercises。我会使用这段代码:

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


训练t =(训练)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);

我得到这个例外:

<$ p $ 线程main中的异常org.hibernate.exception.LockTimeoutException:无法执行语句
在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)
在org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3124)
在org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPer sister.java:3581)
在org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:104)
在org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:
org.hibernate.engine.engine.spi.ActionQueue.executeActions(ActionQueue.java:351) $ b 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)$ db $ b at db.Database.updateObj(Database.java:100)
at db.Database .main(Database.java:171)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)$ b $ 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)
导致:java.sql.SQLException:超出锁定等待超时;尝试重新启动事务
在com.mysql.jdbc.SQLError.createSQLException(SQLError.java:998)
在com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3835)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3771)
在com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435)
在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 
public class TrainingplanExercise implements Serializable {
@EmbeddedId private TrainingplanExerciseId trainingplanExerciseId;

public TrainingplanExercise(){}
public TrainingplanExercise(TrainingplanExerciseId trainingplanExerciseId){
this.trainingplanExerciseId = trainingplanExerciseId;
}
...其他字段...
}

上面的实体和原始的实体之间的区别在于我已经将 ID an EmbeddableId 。为了确保只有独特的练习放入 TrainingplanExercise's 中,您的 compositeKey 被定义为单独的class:

  @Embeddable 
public class TrainingplanExerciseId实现Serializable {
private String exercise;
私有字符串参数;

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

... getters,setters,hashCode和equals
}

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

这个模型的不同之处在于 TrainingplanExerciseId compositeKey 不包含回到 TrainingPlan 的引用。如果您正在尝试获取 TrainingPlan's 中使用任何特定 TrainingplanExercise 的列表,那么您需要一个双向而不是单向关系,但这是一个不同的问题。否则,我不知道为什么要从 TrainingplanExercise 中引用回< TrainingPlan 。此外,您将对 TrainingPlan 的引用添加到 TrainingplanExerciseId compositeKey ,这将需要序列化 TrainingPlan ,这实际上不会作为唯一的Id。



<现在你可以把个人练习放到表格中:

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

之后,您希望有任意数量的 TrainingPlan的使用可能的 TrainingplanExercise's ,您可以通过实体

  @Entity 
public class TrainingPlan实现Serializable {
@Id
@GeneratedValue(strategy = GenerationType。 AUTO)
私人长ID;

@ManyToMany(fetch = FetchType.EAGER)
private List< TrainingplanExercise> trainingplanExercises = new ArrayList< TrainingplanExercise>();

... getters,setters,
}

您有一个 ManyToMany 关系,因为 TrainingPlan 指的是许多 TrainingplanExercise's TrainingplanExercise 被许多 TrainingPlan's 使用。除 ManyToMany 外,您不需要任何特殊注释, JPA 提供程序将创建一个链接table ,将每个 Entity 中的键放入一行,如下所示:

<$ p $ b $ trainingPlan_TrainingplanExercise(
)TrainingPlan_id bigint不为null,
trainingplanExercises_exercise varchar(255)非空,
trainingplanExercises_parameter varchar(255)非空
) ;

如果您将其声明为 OneToMany 关系,那么 JPA 提供程序将在链接表上添加一个额外的约束 确保 TrainingplanExercise 不能链接到多个 TrainingPlan ,所以你不需要这个。

  alter table TrainingPlan_TrainingplanExercise 
添加约束UK_t0ku26ydvjkrme5ycrnlechgi unique(仅限于例子的缘故,这是限制的样子) trainingplanExercises_exercise,trainingplanExercises_parameter);

创建并更新 TrainingPlans 非常简单:

  public TrainingPlan createTrainingPlan(){
TrainingPlan trainingPlan = new TrainingPlan();
em.persist(trainingPlan);
返回培训计划;
}
public TrainingPlan updateTrainingPlan(TrainingPlan trainingPlan){
return em.merge(trainingPlan);

$ / code>

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

  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 由用户创建。如果尝试使用重复练习参数 TrainingplanExercise 要创建,你将得到一个唯一索引或主键违例异常,并且事务将被回滚。



编辑:阅读 TrainingPlans ,可以这样使用:

 公开列表< TrainingPlan> listTrainingPlans(){
CriteriaQuery< TrainingPlan> criteria = em.getCriteriaBuilder()。createQuery(TrainingPlan.class);
criteria.select(criteria.from(TrainingPlan.class));
列表< TrainingPlan> trainingPlans = em.createQuery(criteria).getResultList();
返回trainingPlans;
}

请注意,自列表< TrainingplanExercise> trainingplanExercises 被设置为 FetchType.EAGER 这个特定的查询将引入整个数据库。 FetchType.EAGER 读取单个 TrainingPlan 可能不是问题,但如果您只想要 TrainingPlan's 没有得到所有的细节,那么你需要弄清楚应该如何执行 FetchType.LAZY 。 / p>

I have the following tables

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)

No I have problems connecting them with Hibernate. I did the following: package beans;

@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);
     }
 }

Now when I want to save a new Training, I get this error:

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

Because of this SQL:

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

How do I fix this? If I change the joinColumn to "trainingplan" I get the error that there are two same columns. If I remove "trainingplan" from the reversed columns, I get an error that one is missing because the foreign constraint requires 3 columns

EDIT: Try something from the comments. I did try 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;
}

If I try saving a training to the DB now, it works. Let's say I want to get a Trainingplan from the database, and add new TrainingplanExercises. I would use this code:

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);

I get this exception:

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

解决方案

Okay, look. What you have is a design problem, not really a general problem. First, as I understand it, you want to make a set of unique TrainingplanExercise's. For that, you have this Entity:

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

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

The difference between the above Entity and your original Entity is that I have made the ID an EmbeddableId. In order to insure that only unique exercises are put into the TrainingplanExercise's, you have a compositeKey that was defined as a separate class:

@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
}

Here, I have made the class Embeddable so that it can be used as an ID. The way you were trying to declare a compositeKey didn't make any sense; you were trying to declare each individual field in the TrainingplanExercise Entity as an ID, but you can only have one ID.

What is different in this model is that the TrainingplanExerciseId compositeKey does not include a reference back to a TrainingPlan. If you are trying to get a list of TrainingPlan's that use any specific TrainingplanExercise, then you would need a Bidirectional instead of a Unidirectional relationship, but that's a different issue. Otherwise, I don't know why you want to refer back to a TrainingPlan from a TrainingplanExercise. Further, you were putting a reference to the TrainingPlan into the TrainingplanExerciseId compositeKey, which would require the TrainingPlan to be serialized, which really wouldn't work as a unique Id.

Now you can put individual exercises into the table:

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

After that, you want to have any number of TrainingPlan's that use the possible TrainingplanExercise's, which you do with this 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, 
}

You have a ManyToMany relationship because a TrainingPlan refers to many TrainingplanExercise's and a TrainingplanExercise is used by many TrainingPlan's. You don't need any special annotation besides ManyToMany, the JPA provider will create a link table, putting the key from each Entity into a row, like this:

create table TrainingPlan_TrainingplanExercise (
    TrainingPlan_id bigint not null,
    trainingplanExercises_exercise varchar(255) not null,
    trainingplanExercises_parameter varchar(255) not null
);

If you declare it as a OneToMany relationship, then the JPA provider will put an additional constraint on the link table insuring that a TrainingplanExercise cannot be linked to more than one TrainingPlan, so you don't want that. Just for example's sake, this is what the constraint would look like.

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

Creating and updating TrainingPlans is straight forward:

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

Now, you can create TrainingplanExercises and TrainingPlans, and add the exercises to the training plans and update them.

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);

Also note that your application has the challenge of insuring that only unique TrainingplanExercises are created by users. If a TrainingplanExercise with a duplicate exercise and parameter is attempted to be created you will get a Unique index or primary key violation exception and the transaction will be rolled back.

EDIT: For reading the TrainingPlans, something like this can be used:

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;
}

Note that since the List<TrainingplanExercise> trainingplanExercises is set to FetchType.EAGER this particular query will pull in the entire database. FetchType.EAGER probably isn't a problem for reading a single TrainingPlan, but if you only wanted a list of the TrainingPlan's without getting all of the details, then you would need to work out how FetchType.LAZY should be implemented.

这篇关于如何用复合键映射多对多的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆