问题
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
回答1:
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.
回答2:
Did you tried using many-to-one mapping instead because it's what you have with a foreign key anyway. You could then try something like:
@Id
@ManyToOne( cascade = {CascadeType.PERSIST}, targetEntity=Trainingplan.class )
@JoinColumn(name = "trainingplan")
public Training getTraining() {}
来源:https://stackoverflow.com/questions/35111950/how-to-map-many-to-many-with-composite-key