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;
}
Related
I'm trying to make an app that has users songs and two types of playlist(regular and for liked songs).
#MappedSuperclass
public abstract class BasePlaylist {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Type(type = "uuid-char")
private UUID id;
#Column(nullable = false)
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.DETACH)
private Set<Song> songs;
private String imageUrl;
private LocalDateTime creationTime;
}
#Entity
#Table(name = "songs")
public class Song {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Type(type = "uuid-char")
private UUID id;
#Column(nullable = false)
private String title;
#ManyToOne(optional = false)
private User creator;
#Column(unique = true, nullable = false)
private String songUrl;
private LocalDateTime creationTime;
}
the only difference between the two types of playlists is the relationship with the users (onetoone and manytomany). The problem I'm facing is when I try to delete a song I get Cannot delete or update a parent row: a foreign key constraint fails. I thought putting the cascade detach on the songs set would fix the problem but it didn't. I tried with cascade all but it didn't do anything either. I even tried making the relations bidirectional and putting cascadetype all on the playlists but I get the same error. My goal is to remove all song relations on delete.
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.
i am working on Spring Data JPA with Hibernate. I want to create a mapping table using hibernate automatically and want to retrieve the values based on the id's of each individual tables.
CustomerEntity.java:
#Getter
#Setter
#Table(name = "customers")
#Entity
public class CustomerEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "customerId", updatable = false, nullable = false)
private UUID customerId;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "customerEntities")
private List<ItemEntity> itemEntities;
//More customer fields
}
ItemEntity.java
#Getter
#Setter
#Table(name = "items")
#Entity
public class ItemEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "itemId", updatable = false, nullable = false)
private UUID itemId;
#ManyToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "customerId", nullable = false)
private List<CustomerEntity> customerEntities;
//More item fields
}
Now i want to create a mapping table with manyToMany for these two based on the id's
something like below:
MappingTable:
id // Primary Key
customerId // primary key from customer table
itemId // primary key from item table
and i want to use customerId or itemId (depends on which i have available) to find the records within the mapping table.
Can any one please provide any suggestions would be helpful here. TIA.
I have 2 entities that have Id's annotated but those Id's aren the primary keys in the tables. I am still mapping the PK's in to the entity for now to limit the initial impact of the change. But the association table that uses the PK's to associate the many to many relationship is throwing the following error:
java.lang.IllegalArgumentException: Provided id of the wrong type for class. Expected: class java.util.UUID, got class java.lang.Long
The entity #Id is the UUID but the table PK which is a Long is mapped as the #JoinColumn
The composite key for the association entity
#Embeddable
public class CustomerAccountId implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "user_id", nullable = false)
private Long customerId;
#Column(name = "account_id", nullable = false)
private Long accountId;
The association entity:
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "customerId", column = #Column(name = "user_id", nullable = false)),
#AttributeOverride(name = "accountId", column = #Column(name = "account_id", nullable = false))
})
private CustomerAccountId id;
#ManyToOne
#JoinColumn(name = "user_id", insertable = false, updatable = false, referencedColumnName = "user_id")
private Customer customer;
#ManyToOne
#JoinColumn(name = "account_id", insertable = false, updatable = false, referencedColumnName = "id")
private Account account;
The failing entity:
#Column(name = "user_id")
private Long serialId; // also the primary key in the table
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#org.hibernate.annotations.Type(type="pg-uuid")
#Column(name = "uid", updatable = false)
private UUID id;
Does anyone know if this is even possible? or am I going to be forced to update the content in the association table when I push this change?
put #Id and #GeneratedValue on the field that represent the table data then hibernate will map a long (sgbd) whit a long (table)
or
your (sgbd) table data type must be compatible with the (java) uuid type.
Why these 2 keys on your table?
I think it's not possible to have 2 PK for one entity. At most you can have a composite key base on your serialID and the UUID.
see How to map a composite key with Hibernate? for that
Or mark as #Id the real PK in the SGBD. Use the other in Java as a classic value in the table's point of view
The solution I decided to go with is the following:
#ManyToOne
#JoinColumns ( value = {
#JoinColumn(name = "user_id", insertable = false, updatable = false, referencedColumnName = "user_id"),
#JoinColumn(name = "user_uid", insertable = false, updatable = false, referencedColumnName = "uid")
})
private Customer customer;
In a nutshell I simply added the UUID to the association table and used both columns for the Customer Entity.
To address #Tokazio's question about using UUID and serial Id, the data warehouse conversion is impacted significantly so I need to slowly move from serial Id's to UUID's to minimize impacts and prevent outages.
I have an ER relationship from a legacy DB (MS SQL Server based as below
The way that I'm currently trying to convert this to the JPA 2.1 style is as below
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Orders implements Serializable {
#Id
#GeneratedValue
#Column(name = "OrderNumber", nullable = false)
private Integer orderNumber;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(
name = "OrderHistory",
joinColumns = {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false)
}
)
private List<OrderHistory> orderHistory;
----Other properties, Getters and Setters
}
#Entity
#PrimaryKeyJoinColumn(name = "OrderNumber")
public class SpecialOrders extends Orders implements Serializable {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false)
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.SELECT)
#OrderBy("sequenceNumber ASC")
private List<Items> items;
----Other properties, Getters and Setters
}
#Entity
#IdClass(ItemsPK.class)
public class Items implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(
name = "CustomOptions",
joinColumns = {
#JoinColumn(name = "OrderNumber", referencedColumnName = "OrderNumber", nullable = false),
#JoinColumn(name = "SequenceNumber", referencedColumnName = "SequenceNumber", nullable = false)
}
)
private List<CustomOptions> customOptions;
----Other properties, Getters and Setters
}
#Entity
public class ItemsPK implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
----Getters and Setters
}
#Entity
#IdClass(CustomOptionsPK.class)
public class CustomOptions implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#Id
#Column(name = "OptionNumber", nullable = false)
private Integer optionNumber;
----Other properties, Getters and Setters
}
public class CustomOptionsPK implements Serializable {
#Id
#Column(name = "OrderNumber", nullable = false, insertable = false, updatable = false)
private Integer orderNumber;
#Id
#Column(name = "SequenceNumber", nullable = false)
private Integer sequenceNumber;
#Id
#Column(name = "OptionNumber", nullable = false)
private Integer optionNumber;
----Getters and Setters
}
With the above code, I see that hibernate does below
INSERTs into Orders and gets the GeneratedId for OrderNumber
INSERTs into SpecialOrders, using the orderNumber retrieved above.
Attemps to INSERT into Items table, with a NULL value in the orderNumber and then fails because the orderNumber is a NOT NULL column.
Subsequently, If add a "Simple" primary key to Items table and make the orderNumber as a NULLable column, then the below happens:
INSERTs into Orders and gets the GeneratedId for OrderNumber
INSERTs into SpecialOrders, using the orderNumber retrieved above.
INSERTs into Items table with orderNumber as NULL value and gets the generated id of the Items table row.
Updates the row of Items table with the orderNumber from parent, using the retrieved id for Items.
Attemps to INSERT into CustomOptions table, with a NULL value in the orderNumber and then fails because the orderNumber is a NOT NULL column.
As per the above sequence, it seems that:
Composite Primary key doesnt seem to be working correctly or not supported.
Hibernate is handling the OneToMany relationship inefficiently by issuing an INSERT followed by an UPDATE, instead of just an insert.
Any idea if my understanding is correct? The only way of fixing this issue seems to be that I need to remove the composite primary key and replace it with a simple one.