I have two tables, "books" and "bookstores" that both have a #OneToMany reference to the "book_to_bookstore" table which hols the information how many books are in which bookstore.
If either a book or a bookstore is deleted, that information should vanish, too.
Therefore I use the standard JPA CascadeType.ALL and orphanRemoval=true. To make "proper" SQL schemas that also work when accessing it not with Java, I added the EclipseLink specific #CascadeOnDelete annotation, expecting the CASCADE ON DELETE to appear after both FOREIGN KEY statements.
Interestingly though, it only appears after one of them which I just can't explain!
I tried the DDL generation for MySQL and PostgreSQL but both are the same.
Why?
A compileable mini project is here:
svn+ssh://lathspell#svn.code.sf.net/p/lathspellsphp/code/java_test_eclipselink_cascade_bug
The important classes:
public class Bookstore implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "bookstoreId", orphanRemoval = true)
#CascadeOnDelete
private List<BookToBookstore> bookToBookstoreList;
...
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "bookId", orphanRemoval = true)
#CascadeOnDelete
private List<BookToBookstore> bookToBookstoreList;
...
public class BookToBookstore implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#JoinColumn(name = "bookstore_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Bookstore bookstoreId;
#JoinColumn(name = "book_id", referencedColumnName = "id")
#ManyToOne(optional = false)
private Book bookId;
...
The generated SQL for MySQL, note the missing CASCADE ON DELETE in the last line:
CREATE TABLE books (id INTEGER AUTO_INCREMENT NOT NULL, PRIMARY KEY (id))
CREATE TABLE book_to_bookstore (id INTEGER AUTO_INCREMENT NOT NULL, book_id INTEGER, bookstore_id INTEGER, PRIMARY KEY (id))
CREATE TABLE bookstores (id INTEGER AUTO_INCREMENT NOT NULL, PRIMARY KEY (id))
ALTER TABLE book_to_bookstore ADD CONSTRAINT FK_book_to_bookstore_book_id FOREIGN KEY (book_id) REFERENCES books (id) ON DELETE CASCADE
ALTER TABLE book_to_bookstore ADD CONSTRAINT FK_book_to_bookstore_bookstore_id FOREIGN KEY (bookstore_id) REFERENCES bookstores (id)
Related
I'm currently mapping a complex database schema wqith HIbernate and I have hit a wall with an entity which has a composite key with another composite key.
I have this table for roles with a composite key (site_id, id)
CREATE TABLE IF NOT EXISTS core.roles
(
id uuid NOT NULL DEFAULT gen_random_uuid(),
name character varying(100) NOT NULL,
is_system_role boolean NOT NULL DEFAULT FALSE,
site_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid NOT NULL,
updated_by uuid NOT NULL,
CONSTRAINT roles_pkey PRIMARY KEY (site_id, id),
CONSTRAINT roles_name_key UNIQUE (site_id, name),
CONSTRAINT roles_site_id_fkey FOREIGN KEY (site_id)
REFERENCES core.sites (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT roles_created_by_fkey FOREIGN KEY (created_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT,
CONSTRAINT roles_updated_by_fkey FOREIGN KEY (updated_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT
);
And I have this table with a composite key which also uses the previous one.
CREATE TABLE IF NOT EXISTS core.user_site_roles
(
user_id uuid NOT NULL,
site_id uuid NOT NULL,
role_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid NOT NULL,
CONSTRAINT user_site_roles_pkey PRIMARY KEY (site_id, user_id, role_id),
CONSTRAINT user_site_roles_site_id_fkey FOREIGN KEY (site_id)
REFERENCES core.sites (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_site_roles_role_id_fkey FOREIGN KEY (site_id, role_id)
REFERENCES core.roles (site_id, id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_site_roles_user_id_fkey FOREIGN KEY (user_id)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
CONSTRAINT user_site_roles_created_by_fkey FOREIGN KEY (created_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT
);
My current mapping for the roles one which is working is:
#Embeddable
public class CommonId implements Serializable {
#Type(type = "pg-id-uuid")
#Column(columnDefinition = "uuid", updatable = false)
private UUID id;
#Type(type = "pg-id-uuid")
#Column(name = "site_id", columnDefinition = "uuid", updatable = false)
private UUID siteId;
}
#Entity
#Table(name = "roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class Role extends AuditAtBy implements Serializable {
#EmbeddedId
private CommonId roleId;
#ManyToOne
#MapsId("siteId")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#Column(nullable = false, unique = true, length = 100)
private String name;
#Column(name = "is_system_role", nullable = false)
private boolean isSystemRole;
}
I was trying something similar with the composite Key for the UserSiteRole but Hibernate tells me that it needs to columns to map the roleId when in the table I have just the id but the PK is form by the two values as you can see in the script, not sure how to map it to be honest.
#Embeddable
public class UserSiteRoleId implements Serializable {
#Type(type = "pg-id-uuid")
#Column(columnDefinition = "uuid", updatable = false)
private UUID userId;
#Type(type = "pg-id-uuid")
#Column(name = "site_id", columnDefinition = "uuid", updatable = false)
private UUID siteId;
#Type(type = "pg-id-uuid")
#Column(name = "role_id", columnDefinition = "uuid", updatable = false)
private UUID roleId;
}
#Entity
#Table(name = "user_site_roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class UserSiteRole extends AuditCreated implements Serializable {
#EmbeddedId
private UserSiteRoleId userSiteRoleId;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "user_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne
#MapsId("siteId")
#JoinColumn(name = "site_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#ManyToOne
#MapsId("roleId")
#JoinColumn(name = "role_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Role role;
}
I would appreciate any ideas about how to map it, I had never had to map such a complex relationship so not sure how to proceed in this case.
Does this answer your question? jpa hibernate composite foreign key mapping
Actually that was useful as it made it clear that we could change the mapping from embeddedId to IdClass and make it work.
This is our new IdClass, pretty simple:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserSiteRoleId implements Serializable {
private User user;
private Site site;
private Role role;
}
And the entity itself working just inf e is as follows.
#Entity
#Table(name = "user_site_roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
#IdClass(UserSiteRoleId.class)
public class UserSiteRole extends AuditCreated implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#Id
#ManyToOne
#JoinColumn(name = "site_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#Id
#ManyToOne
#JoinColumns({
#JoinColumn(name = "role_id", referencedColumnName="id", columnDefinition = "uuid", insertable = false, updatable = false),
#JoinColumn(name = "site_id", referencedColumnName="site_id", columnDefinition = "uuid", insertable = false, updatable = false)
})
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Role role;
}
The error Error executing DDL "alter table if exists task.city add constraint FKtjrg7h2j3ehgycr3usqjgnc2u foreign key (id) references task.house" via JDBC Statement" I Don't understand how to solve it, I was already looking for a solution, but I check my database and Entity, everything is correct. I created the database from scratch myself. I work in Postgresql. Added the error log.
Properties
spring.datasource.url=jdbc:postgresql://localhost:5432/innotechnum
spring.datasource.username=***
spring.datasource.password=***
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQL10Dialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.open-in-view= true
spring.jpa.show-sql=true
City
#Data
#Entity
#Table(name = "city", schema = "task")
public class City {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "id_region", nullable = false)
private Integer id_region;
#Column(name = "name", nullable = false)
private String name;
}
house
#Data
#Entity
#Table (name = "house", schema = "task")
public class House {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH
})
#JoinColumn(name = "id")
private Set<City> city;
#OneToMany(mappedBy = "house")
private Set<Contract> contract;
#Column(name = "id_landlord", nullable = false)
private Long id_landlord;
#Column(name = "outside", nullable = false)
private String outside;
#Column(name = "rooms", nullable = false)
private Integer rooms;
#Column(name = "price", nullable = false)
private Double price;
#Column(name = "description", nullable = false)
private String description;
}
LOGS:
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table if exists task.city add constraint FKtjrg7h2j3ehgycr3usqjgnc2u foreign key (id) references task.house" via JDBC Statement
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:559) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]
at
...
This is the code that Postgresql gives me:
CREATE TABLE task.city
(
id integer NOT NULL DEFAULT nextval('task.city_id_seq'::regclass),
id_region integer NOT NULL,
name character varying(250) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT city_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE task.city
OWNER to root;
You should not use unidirectional #OneToMany association. Try to add the
#ManyToOne(fetchType = LAZY)
#JoinColumn(name = "house_id")
private House house;
to the City class.
P.S. Do you sure you need one-to-many association from house to city but not vice versa? City may have many houses, but house belongs to the particular city.
Hmm, try
public class House {
...
#ManyToOne(optional = false)
#JoinColumn(name = "city_id", referencedColumnName = "id")
private Set<City> city;
...
}
btw,
should not use #Data lombok in an entity class
spring.jpa.hibernate.ddl-auto=update can change update to none. You should manage schema on your own.
This is a shortened version of the entities where I only show the relevant parts.
#Entity
#Data
public class Wrapper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id
#OneToOne(mappedBy = "wrapper", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Application application;
public Wrapper(Application application) {
this.application = application;
application.setWrapper(this);
}
}
#Data
#Entity
#EqualsAndHashCode(exclude = "wrapper")
public class Application {
#Id
private Integer id;
#JsonIgnore
#OneToOne
#JoinColumn(name = "id")
#MapsId
private Wrapper wrapper;
#OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#SortNatural
private SortedSet<Apartement> ownedApartements = new TreeSet<>();
}
#Entity
#Data
public class Apartement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
}
#Repository
public interface WrapperRepository extends JpaRepository<Wrapper, Integer> {
}
The above entities generates the following create table statements:
create table Wrapper (
id int identity not null,
primary key (id)
)
create table Application (
id int not null,
primary key (id)
)
create table Apartement (
id int identity not null,
application_id int not null,
primary key (id)
)
alter table Apartement
add constraint FKsrweh1i1p29mdjfp03or318od
foreign key (application_id)
references Application
alter table Application
add constraint FKgn7j3pircupa2rbqn8yte6kyc
foreign key (id)
references Wrapper
Given the follow entities and the following code:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplication(application);
apartement2.setApplication(application);
WrapperRepository.saveAndFlush(wrapper);
I see three inserts in the log.
First wrapper, then application, and finally apartement. But for some reason application_id is null on the first save. But I know it has a bi-directional relationship.
The error I get is:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "APPLICATION_ID"; SQL statement:
insert into Apartement (id) values (null) [23502-197]
Why does this happen? Do I need to store everything in the correct order? Do I need to first store wrapper and application, then finally store the apartement once I have application ID?
Cannot hibernate store all three in one go? Or figure this out it self?
Sorry I fixed it.
The problem was
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
I removed insertable = false, updatable = false and added optional=false
That worked
#JoinColumn(name = "application_id", optional = false)
Try this:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplicationId(application.getId());
apartement2.setApplicationId(application.getId());
WrapperRepository.saveAndFlush(wrapper);
I have 2 tables in database side(oracle)
create table GROUPS
(
ID NUMBER not null,
GROUP_NAME VARCHAR2(30)
)alter table GROUPS
add constraint ID primary key (ID)
and
create table ITEM_GROUP
(
ITEM_ID VARCHAR2(30) not null,
GROUP_ID NUMBER not null
)
alter table ITEM_GROUP
add constraint ITEM_GROUPD_ID primary key (ITEM_ID, GROUP_ID)
alter table ITEM_GROUP
add constraint ITEM_GROUP_FK01 foreign key (GROUP_ID)
references GROUPS (ID);
Than I have mapping classes in Java side. I want to make thing, when I am selecting group to take all his items too, and I want to save item with hibernate it is all .
#Entity
#Table(name = "GROUPS")
public class Group {
#Id
#Column(name = "ID", nullable = false)
#javax.persistence.SequenceGenerator(name = "groupIdGenerator", sequenceName = "GROUP_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "groupIdGenerator")
private int id;
#Column(name = "GROUP_NAME")
private String groupName;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER, mappedBy="group",cascade = CascadeType.ALL)
private List<GroupItems> groupItems = new ArrayList<>();
// setters and getters
}
#SuppressWarnings("serial")
#Embeddable
public class GroupItemPK implements Serializable {
#Column(name = "ITEM_ID")
private String merchantId;
#Column(name = "GROUP_ID")
private int id;
// getters , setters , constructors , equals hashcode methods
}
#Entity
#Table(name = "ITEM_GROUP")
public class GroupITEM {
#EmbeddedId
private GroupITEMtPK id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID")
#JsonBackReference
private Group group;
}
I am interested in did i make any mistakes in build relationship ? If I did what is my mistakes , because I can not do my select and save queries without exceptions.
I am trying to do in my Code
List<Group> list = sessionFactory.getCurrentSession().createQuery("from Group a").list();
and here is my Exception
org.hibernate.engine.jdbc.spi.SqlExceptionHelper could not extract ResultSet [n/a]
java.sql.SQLSyntaxErrorException: ORA-00904: "GROUPITE0_"."ID": invalid identifier
I am using JPA (Hibernate) and trying to persist entire new entity with childs and composite keys, but when persisting childs i getting null in key. Table structure:
CREATE TABLE IDENTITY_DOCS
(
PERSON_ID BIGINT NOT NULL,
DOC_TYPE_ID BIGINT NOT NULL,
...
);
CREATE TABLE DOC_TYPES
(
DOC_TYPE_ID BIGINT PRIMARY KEY NOT NULL,
...
);
CREATE TABLE PERSONS
(
PERSON_ID BIGINT PRIMARY KEY NOT NULL,
...
);
Mappings:
#IdClass(...IdentityDocsPK.class)
#Table(name = "IDENTITY_DOCS")
#Entity
public class IdentityDocs {
#Id
#Column(name = "PERSON_ID", nullable = false)
private Long personId;
#Id
#Column(name = "DOC_TYPE_ID")
private Long docTypeId;
#ManyToOne
#MapsId("personId")
#JoinColumn(name = "PERSON_ID", referencedColumnName = "PERSON_ID")
private Employee employee;
...
}
public class IdentityDocsPK implements Serializable {
private Long personId;
private Long docTypeId;
...
}
#Table(name = "PERSONS")
#Entity
#Where(clause = "PERSON_TYPE_ID = 1")
public class Employee {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PERSON_ID")
#Id
private Long personId;
...
#OneToMany(mappedBy = "employee", fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<IdentityDocs> identityDocs;
}
#Table(name = "DOC_TYPES")
#Entity
public class DocTypes {
#Id
#Column(name = "DOC_TYPE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long docTypeId;
...
}
Test case:
Employee employee = new Employee();
employee.setFirstName("AAAA");
employee.setLastName("BBBB");
employee.setPersonNumber("1337");
IdentityDocs docs1 = new IdentityDocs();
docs1.setDocTypeId(1L); // already in database
docs1.setDocNumber("11111");
docs1.setDocSeries("11111");
docs1.setPlaceIssue("1111");
employee.setIdentityDocs(new ArrayList<IdentityDocs>());
employee.getIdentityDocs().add(docs1);
em.persist(employee);
Error code:
NULL not allowed for column "PERSON_ID"; SQL statement:
insert into IDENTITY_DOCS (DATE_EXPIRED, DATE_ISSUE, DOC_NUMBER, DOC_SERIES, PLACE_ISSUE, DOC_TYPE_ID, PERSON_ID) values (?, ?, ?, ?, ?, ?, ?)
I also tried without #MapsId (using insertable = false, updatable=false on relevant field), but with same result. Question #OneToMany and composite primary keys? is relevant, but i dont find answer there
Maybe should have an AUTO_INCREMENT clause on your person_id field in your DDL, or generate the key in some other way.