Hibernate and composite key - java

I'm using Hibernate and Oracle database and just got stuck.
I'm trying to map this table:
CREATE TABLE passengers_on_the_flight
(
flight_id NUMERIC(10) REFERENCES flight(flight_id),
passenger_id NUMERIC(20) REFERENCES passenger(passenger_id),
seat NUMERIC(5) NOT NULL,
CONSTRAINT "not free" PRIMARY KEY (flight_id,passenger_id,seat)
);
So my mapping class looks like:
#Entity
#Table(name = "PASSENGERS_ON_THE_FLIGHT")
#NamedQueries({
#NamedQuery(name = "PassengersOnTheFlight.findAll", query = "SELECT p FROM PassengersOnTheFlight p")})
public class PassengersOnTheFlight implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected PassengersOnTheFlightPK passengersOnTheFlightPK;
public PassengersOnTheFlightPK getPassengersOnTheFlightPK() {
return passengersOnTheFlightPK;
}
public void setPassengersOnTheFlightPK(PassengersOnTheFlightPK passengersOnTheFlightPK) {
this.passengersOnTheFlightPK = passengersOnTheFlightPK;
}
#JoinColumn(name = "SEAT", referencedColumnName = "SEAT", insertable = false, updatable = false)
private int seat;
#JoinColumn(name = "FLIGHT_ID", referencedColumnName = "FLIGHT_ID", insertable = false, updatable = false)
#ManyToOne
private Flight flight;
#JoinColumn(name = "PASSENGER_ID",referencedColumnName = "PASSENGER_ID",insertable = false, updatable = false)
#ManyToOne
private Passenger passenger;
//Getters, setters for seat, flight and passanger
And primary key class:
#Embeddable
public class PassengersOnTheFlightPK implements Serializable {
#Column(name = "FLIGHT_ID",nullable=false)
private long flightId;
#Column(name = "SEAT",nullable=false)
private int seat;
#Column(name = "PASSENGER_ID", nullable=false)
private Long passengerId;
//Getters and setters for seat, flightId and passangerId
I tried to persist something and got
ORA-00957: duplicate column name
That because Hibernate generates such query:
insert into PASSENGERS_ON_THE_FLIGHT (seat, FLIGHT_ID, PASSENGER_ID, SEAT) values (?, ?, ?, ?)
I don't get why. Did I mis something in the mapping classes?

I get the same problem with a Coposite Key and solved it adding this params at the #JoinColumn hibernate annotation in the get methods of the external PK duplicate entities:
#JoinColumn(..., updatable=false, insertable=false)

Hibernate really REALLY wants you to have a unique identity key for each table; it has "issues" without it. Try putting an identity key on your table.

You get your error because you specified seat twice. Once you have it as property of your entity PassengersOnTheFlight and second time in the key. If it is part of your key, remove it from the main object. If it is an integer, you probably don't want #JoinColumn for it anyway.

Related

Override the Foreign Key Name pointing to Composite Key JPA/Hibernate

I'm using spring-boot 1.5.4 with spring-data-jpa and I'm trying to override the auto generated foreign key name during spring.jpa.hibernate.ddl-auto=create.
For simple id, I was able to override it: simple_fk
Hibernate: alter table my_entity add constraint simple_fk foreign key (simple_id) references simple
But not for foreign key with composite id: FKms12cl9ma3dk8egqok1dasnfq
Hibernate: alter table my_entity add constraint FKms12cl9ma3dk8egqok1dasnfq foreign key (composite_id1, composite_id2) references composite
What is wrong with my code? I also tried #PrimaryKeyJoinColumn.
Please see the class definitions below.
#Entity
public class Simple {
#Id
private long id;
}
#Entity
public class Composite {
#Id
private CompositeId id;
}
#Embeddable
public class CompositeId {
#Column
private long id1;
#Column
private long id2;
}
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"),
name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2")
})
private Composite composite;
}
This is a known issue with the Hibernate which was fix in version 5.2.8
So there are two ways to fix it: Either you update the Hibernate to the version 5.2.8 or up by adding
<hibernate.version>5.2.10.Final</hibernate.version>
to your pom.xml, which basically will update the Hibernate to the latest version.
or if Hibernate update is not possible or is just too risky you can add legacy/deprecated #org.hibernate.annotations.ForeignKey(name = "composite_fk") annotation on your
composite field which will make you code look like
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"), name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2") })
#org.hibernate.annotations.ForeignKey(name = "composite_fk")
private Composite composite;
}

JPA Composite Key part join

I have an existing database schema and I try to make one to many relationship in JPA when PK is a composite of multiple fields and just one of them is FK in the other entity:
DemandId: PK class that consist of two fields
#Embeddable
public class DemandId implements Serializable {
#Column(name = "\"ORDER\"", nullable = false)
private String order;
#Column(name = "SNRP", nullable = false)
private String number;
}
DemandEntity: The entity itself
#Entity
#Table(name = "DEMAND")
public class DemandEntity implements Serializable {
#EmbeddedId
private DemandId id;
#OneToMany(fetch = EAGER, cascade = ALL, mappedBy = "demand")
private Set<PartEntity> parts = new HashSet<>();
}
PartEntity:
#Entity
#Table(name = "PART")
public class PartEntity implements Serializable {
#Column(name = "SNRP")
private String number;
#ManyToOne
#JoinColumn(name = "SNRP", referencedColumnName = "SNRP", insertable = false, updatable = false)
private DemandEntity demand;
}
This approach leads to an exception:
Exception Description: The #JoinColumns on the annotated element
[field demand] from the entity class [class PartEntity] is incomplete.
When the source entity class uses a composite primary key, a
#JoinColumn must be specified for each join column using the
#JoinColumns. Both the name and the referencedColumnName elements must
be specified in each such #JoinColumn.
Unfortunatelly I cannot add another join column
#JoinColumn(name = "\"ORDER\"", referencedColumnName = "\"ORDER\"", insertable = false, updatable = false)
Because the PART table doesn't contain the ORDER field and the structure of the database cannot be changed.
Is there a way to perform such mapping?
Regards
If you have composite primary keys and you want to have one to many
mapping, I would suggest rather than keeping those keys as composite
primary keys, make them composite unique keys.
And make a auto-generated sequence as a primary key.
It is better and more convenient. By the way its my personnel opinion.
I even don't know, if that is possible or not which you are trying to do.

eclipselink manytoone not save

i have a persit problem with elcipselink
here is my model :
#Entity
#Table(name = "TBL_ASSOC_LANGUE_CODE_BE_GARE")
public class AssocLangueCodeBeGare {
#AttributeOverrides({ #AttributeOverride(name = "id_langue", column = #Column(name = "ID_LANGUE") ),
#AttributeOverride(name = "id_gare", column = #Column(name = "ID_GARE") ) })
#EmbeddedId
private AssocLangueCodeBeGareFK key;
#Column(name = "CODE_BE", length = 4)
private String codeBe;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "ID_GARE", referencedColumnName = "ID_GARE", insertable = false, updatable = false)
private StopPoint stopPoint;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_LANGUE", referencedColumnName = "ID_LANGUE", insertable = false, updatable = false)
private Langue langue;
in StopPoint i don t reference table AssocLangueCodeBeGare, i don t need it.
then when i do :
this.serviceStopPoint.save(currentStopPoint);
for (AssocLangueCodeBeGare assoc : listAssocLangueCodeBeGare) {
assoc.setStopPoint(currentStopPoint);
this.serviceAssocLangueCodeBeGare.save(assoc);
}
save is
#Override
public void save(T entityToSave) {
this.getEntityManager().persist(entityToSave);
}
I m using batch insert for writing and sometimes when i save another entity flush is done and i get :
Caused by: org.h2.jdbc.JdbcBatchUpdateException: NULL not allowed for column "ID_GARE"; SQL statement:
INSERT INTO TBL_ASSOC_LANGUE_CODE_BE_GARE (CODE_BE, ID_GARE, ID_LANGUE) VALUES (?, ?, ?) [23502-192]
at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1208)
at org.eclipse.persistence.internal.databaseaccess.DatabasePlatform.executeBatch(DatabasePlatform.java:2134)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeJDK12BatchStatement(DatabaseAccessor.java:871)
does i miss something in the mapping?
does i need to add something like this in stoppoint entity:
#OneToMany(mappedBy = "stopPoint", cascade = CascadeType.ALL)
List<AssocLangueCodeBeGare> assocLangueCodeBeGares;
#EDIT1
I think its caused by my EmbeddedId because it was null!! the mapping does'nt set correct value in the embedable object?
here is the embedable object :
#Embeddable
public class AssocLangueCodeBeGareFK implements Serializable {
private String id_langue;
private Long id_gare;
#Override
public int hashCode() {
thanks a lot!
The insertable = false and updatable = false prevent the value from the mapping from being written to the database. You need to have another mapping to that database column with the value set, or it won't get into the database. While making your OneToMany the ID works, so would the MapsId annotation in the current code as JPA will then set the value in your embeddable ID. Or you could have manually added the value from the referenced class into the appropriate AssocLangueCodeBeGare.key fields.
Finaly i find a solution :
I use #IdClass(AssocLangueCodeBeGareFK.class on AssocLangueCodeBeGare
And i put #id on
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "ID_GARE", referencedColumnName = "ID_GARE")
private StopPoint stopPoint;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_LANGUE", referencedColumnName = "ID_LANGUE")
private Langue langue;
I rename on my AssocLangueCodeBeGareFK class name of my property to be the same as the AssocLangueCodeBeGare class :
private Long stopPoint;
private String langue;
now all work like a charm.
if it helps...

JPA Relationship issue

I'm working on a project, and I encountered a problem with the JPA relationship. I've been advised on another thread to change a couple of things, however, I still can't get it to work properly.
I'm getting an exception and I know where the problem is but not sure how to solve it.
here is the User class:
#Entity
#Table(name = "user")
public class UserModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "password", length = 500, nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Car> cars;
}
here is the Car class:
#Entity
#Table(name = "car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(length = 11)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "make", nullable = false)
private String make;
#Column(name = "model", nullable = false)
private String model;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id") //here is where the exception throws (duplicated ID or com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'user_model_id' in 'field list' if I change that to user_model_id.
private UserModel userModel;
}
here is the service impl:
#Component
#Transactional(readOnly = true)
public class CarServiceImpl implements CarService {
#Inject
private CarRepository carRepository;
#Inject
private UserRepository userRepository;
#Override
#Transactional(readOnly = false)
public Car addCar(Long userId, Car car) {
User user = userRepository.findOne(userId);
user.getCars().add(car);
car.setUser(user);
carRepository.save(car);
return car;
}
Any help will be really much appreciated.
Thank you so much
I think mappedBy needs to point to the field name that owns the relation. In this case this is userModel so it should be
#OneToMany(mappedBy = "userModel", cascade = CascadeType.ALL)
If the relationship is bidirectional, the mappedBy element must be
used to specify the relationship field or property of the entity that
is the owner of the relationship. (from https://www.objectdb.com/api/java/jpa/OneToMany)
Also I think that your #JoinColumn annotation is wrong. It should specify the column used to join the related entity - so it cannot be ID but something like user_id
See https://www.objectdb.com/api/java/jpa/JoinColumn#
Also your ddl is wrong - this piece says that cars and users have the same id - which you do not want - your ddl is missing the actual column for the user foreign key
FOREIGN KEY (id)
REFERENCES game.user (id)
So if you changed JoinColumn to #JoinColumn(name = "user_id") your ddl for the foreign key must be.
CREATE TABLE game.car (
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '',
make VARCHAR(15) NOT NULL COMMENT '',
model VARCHAR(15) NOT NULL COMMENT '',
PRIMARY KEY (id) COMMENT '',
user_id INT(11),
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES game.user (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
If I can add my 2 cents to the mapping - try to avoid bidirectional relations wherever you can.

Using a JoinColumn Twice for CompositeKey getting Repeated Column Exception

I have the following situation:
I´m trying to build an application which is multi-tenant with the same tenants in one database
with the same tables. As by know Hibernate is not supporting this variant before 5.0 as I found.
I`m trying to solve this by adding a brandId field to every table.
As I build Many To Many relationships I also added this brandId to the ManyToMany Join Table and here (dont know if I can do this, mysql is not complaining) I made a foreign key to both tables while both include the brandid
So now for example I have a table Text(ID,name,brandId) and a Tag(ID,name,brandId) and a join table (text_id,tag_id,brand_id) where the foreign keys are
CONSTRAINT FK_TAG_TEXTS_TAG FOREIGN KEY (TAG_ID,BRAND_ID) REFERENCES TAG (ID,brand),
CONSTRAINT FK_TAG_TEXTS_TEXT FOREIGN KEY (TEXT_ID,BRAND_ID) REFERENCES TEXT (ID,brand)
As you can see Brand ID is used twice.
Then I generated my classes with Hibernate Tools, which created a Composite Primary Key Class as it should and the association in the Tag Class.
#ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE })
#JoinTable(name = "tag_texts", , joinColumns = {
#JoinColumn(name = "TAG_ID", nullable = false, insertable = false, updatable = false),
#JoinColumn(name = "BRAND_ID", nullable = false, insertable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "TEXT_ID", insertable = false, nullable = false, updatable = false),#JoinColumn( name = "BRAND_ID", insertable = false, nullable = false, updatable = false) })
public List<Text> getTexts() {
return this.texts;
}
The problem is now that I get the following exception:
org.hibernate.MappingException: Repeated column in mapping for collection: de.company.domain.Tag.texts column: brand_id
I looked into the Hibernate code in the Collection class which raises the exception.
Here a method 'checkColumnDupliation' is called which uses a Set and inserts the name,
what means that a second time inserting "BRAND_ID" as column leads to this behaviour.
As I found the most common solution for the Repeated column error is by inserting 'insertable = false and updateable = false' when using the same column in several references. This is described here:
Hibernate: Where do insertable = false, updatable = false belong in composite primary key constellations involving foreign keys?
But this seems to be not the same problem as mine.
So my question is: Is there a possibility to fix this with the JPA Annotations and use the Brand ID in both joinColumns and inverseJoinColumns?
The problem is that you want a JoinTable between 3 entities: Text, Tag and Brand.
Probably you will have to use an IdClass, something like :
public class AssociationId implements Serializable {
private long textId;
private long tagId;
private long brandId;
hash and equals function
...
}
Id Class Entity:
#Entity
#Table(name="tag_text_brand")
#IdClass(AssociationId.class)
public class TagTextBrandAssociation {
#Id
private long tagId;
#Id
private long textId;
#Id
private long textId;
#ManyToOne
#PrimaryKeyJoinColumn(name="TAG_ID", referencedColumnName="ID")
private Tag tag;
#ManyToOne
#PrimaryKeyJoinColumn(name="TEXT_ID", referencedColumnName="ID")
private Text text;
#ManyToOne
#PrimaryKeyJoinColumn(name="BRAND_ID", referencedColumnName="ID")
private Brand brand;
...
}
You can use this in your 3 entities like this:
#Entity
public class Text {
#Id
private long id;
...
#OneToMany(mappedBy="text")
private List<TagTextBrandAssociation> tagsAndBrands;
...
}
See here for more information.

Categories

Resources