I have the following setup in my Spring Boot JPA application:
Embeddable
#Embeddable
public class LogSearchHistoryAttrPK {
#Column(name = "SEARCH_HISTORY_ID")
private Integer searchHistoryId;
#Column(name = "ATTR", length = 50)
private String attr;
#ManyToOne
#JoinColumn(name = "ID")
private LogSearchHistory logSearchHistory;
...
}
EmbeddedId
#Repository
#Transactional
#Entity
#Table(name = "LOG_SEARCH_HISTORY_ATTR")
public class LogSearchHistoryAttr implements Serializable {
#EmbeddedId
private LogSearchHistoryAttrPK primaryKey;
#Column(name = "VALUE", length = 100)
private String value;
...
}
OneToMany
#Repository
#Transactional
#Entity
#Table(name = "LOG_SEARCH_HISTORY")
public class LogSearchHistory implements Serializable {
#Id
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
#OneToMany(mappedBy = "logSearchHistory", fetch = FetchType.EAGER)
private List<LogSearchHistoryAttr> logSearchHistoryAttrs;
...
}
Database DDLs
CREATE TABLE log_search_history (
id serial NOT NULL,
...
CONSTRAINT log_search_history_pk PRIMARY KEY (id)
);
CREATE TABLE log_search_history_attr (
search_history_id INTEGER NOT NULL,
attr CHARACTER VARYING(50) NOT NULL,
value CHARACTER VARYING(100),
CONSTRAINT log_search_history_attr_pk PRIMARY KEY (search_history_id, attr),
CONSTRAINT log_search_history_attr_fk1 FOREIGN KEY (search_history_id) REFERENCES
log_search_history (id)
);
When I go to start the application, I get the following error:
Caused by: org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: com.foobar.entity.LogSearchHistoryAttr.logSearchHistory in com.foobar.entity.LogSearchHistory.logSearchHistoryAttrs
I am not sure why I am getting this error - the mapping looks correct (to me). What is wrong with this mapping that I have? Thanks!
You moved the mappedBy attribute into an embeddable primary key, so the field is no longer named logSearchHistory but rather primaryKey.logSearchHistory. Change your mapped by entry:
#OneToMany(mappedBy = "primaryKey.logSearchHistory", fetch = FetchType.EAGER)
private List<LogSearchHistoryAttr> logSearchHistoryAttrs;
Reference: JPA / Hibernate OneToMany Mapping, using a composite PrimaryKey.
You also need to make the primary key class LogSearchHistoryAttrPK serializable.
At OneToMany part:
#OneToMany(mappedBy = "primaryKey.logSearchHistory", fetch = FetchType.EAGER)
private List<LogSearchHistoryAttr> logSearchHistoryAttrs;
Related
I have a Hibernate entity.
#AllArgsConstructor #NoArgsConstructor #Getter
#Entity
#Table(name = "app_category_link", schema = "mariott_application")
public class AppCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_category_link_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "app_id")
private App app;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id")
private Category category;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id_who_added")
private User userWhoAdded;
#Column(name = "date_add")
private ZonedDateTime dateAdded;
}
And I have a #DataJpaTest that generated such DDL. Surprisingly it produdes an unexpected primary key.
create table mariott_application.app_category_link
(
app_category_link_id int8 not null,
date_add timestamp,
app_id bigserial not null,
category_id int8 not null,
user_id_who_added int8,
primary key (app_id, category_id) -- wrong
)
Why does Hibernate generate wrong primary key?
This can happen when you use the table name multiple times e.g. also in a #JoinTable/#CollectionTable. Always make use of an existing entity as inverse #OneToMany rather than defining a #ManyToMany association to avoid this issue.
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;
}
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.
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.
Here's the DB design (DDL):
CREATE TABLE Countries
(
iso_code CHAR(2) NOT NULL,
name VARCHAR(50) NOT NULL,
PRIMARY KEY (iso_code)
);
CREATE TABLE Zips
(
country_code CHAR(2) NOT NULL,
code VARCHAR(10) NOT NULL,
PRIMARY KEY (country_code, code),
FOREIGN KEY (country_code) REFERENCES Countries (iso_code)
);
Here's the Zip class + composite primary key class:
#Entity
#Table(name = "Zips")
public class Zip implements Serializable
{
#EmbeddedId
private ZipId embeddedId;
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country = null;
...
}
#Embeddable
public class ZipId implements Serializable
{
#Column(name = "country_code", insertable = false, updatable = false)
private String countryCode;
#Column(name = "code")
private String code;
...
}
Hibernate stack trace:
Exception in thread "main" javax.persistence.PersistenceException: [PersistenceUnit: zips] Unable to build EntityManagerFactory
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:911)
at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:57)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)
at tld.zips.Main.main(Main.java:27)
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: tld.zips.model.Zip column: country_code (should be mapped with insert="false" update="false")
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:675)
at org.hibernate.mapping.PersistentClass.checkPropertyColumnDuplication(PersistentClass.java:697)
at org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:719)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:473)
at org.hibernate.mapping.RootClass.validate(RootClass.java:235)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1332)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1835)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902)
... 4 more
What's this? country_code is mapped as read-only (insertable = false, updatable = false) in the composite primary key class. This works perfectly with EclipseLink! IIRC #Embeddable classes allow #Basic, #Column, #Enumerated, #Temporal, #Lob, and #Embedded on its columns, so this should work. Note the code is JPA 1.0 compatible.
The exception vanishes when putting the insertable = false, updatable = false on the #JoinColumn, but this is not what I want. I prefer my associations to be writable...
Is this a Hibernate bug? I'm using Hibernate 3.6 stable.
Looks like a bug. As a workaround you can place country into ZipId instead of countryCode:
#Entity
#Table(name = "Zips")
public class Zip implements Serializable
{
#EmbeddedId
private ZipId embeddedId;
...
}
#Embeddable
public class ZipId implements Serializable
{
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country = null;
#Column(name = "code")
private String code;
...
}
Yeah, seems like a bug. Anyway, you can do it like this, I suppose. Haven't tried it myself, though.
#Entity
#Table(name = "Zips")
public class Zip implements Serializable
{
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="countryCode", column=#Column(name="country_code", insertable = false, updatable = false))
#AttributeOverride(name="code", column=#Column("code"))
})
private ZipId embeddedId;
#ManyToOne
#JoinColumn(name = "country_code", referencedColumnName = "iso_code")
private Country country;
...
}
#Embeddable
public class ZipId implements Serializable
{
private String countryCode;
private String code;
...
}