I'm trying to create a join table with #JoinTable from 3 different entities. Below is the code sample I'm using. While creating join table I'm getting below error. Please help to resolve.
There are 3 entities in my design. Credential, Category and Tenant.
Now I'm trying to make a join table that will contain the pk of these 3 tables using #ManyToMany annotation between them. Below is the relationship which I'm trying to make.
#Entity
#Table(name = "CREDENTIALS")
public class CredentialsEntity {
/** The credential id. */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int credentialId;
#ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(table="CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"),
#JoinColumn(table ="TENANT", name = "TENANT_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table="CREDENTIALS", name = "CREDENTIAL_ID", referencedColumnName = "ID"))
private List<TenantEntity> tenantEntities;
}
==========================
#Entity
#Table(name = "TENANT")
public class TenantEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int tenantId;
#ManyToMany(mappedBy = "tenantEntities", cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
==========================
#Entity
#Table(name = "CATEGORY")
public class NodeCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int categoryId;
#ManyToMany(cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
Caused by:
org.hibernate.AnnotationException: Cannot find the expected secondary
table: no TENANT available for
com.aricent.aricloud.entity.CredentialsEntity at
org.hibernate.cfg.Ejb3Column.getJoin(Ejb3Column.java:363) at
org.hibernate.cfg.Ejb3Column.getTable(Ejb3Column.java:342) at
org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:584)
at
org.hibernate.cfg.annotations.CollectionBinder.buildCollectionKey(CollectionBinder.java:1018)
at
org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1336)
EDIT
I'm able to do the jointable like below, as mentioned in link
is this correct approach or I'm doing something wrong?
#ManyToMany(cascade={CascadeType.ALL})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(name = "CREDENTIAL_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table = "CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"))
#MapKeyJoinColumn(name = "TENANT_ID", referencedColumnName ="ID")
#ElementCollection
private Map<TenantEntity, NodeCategory> tenantCategoryMap = new HashMap<TenantEntity, NodeCategory>();
Related
Initial Situation:
I have these two entities, GroceriesList and Product.
GroceriesList:
A GroceriesList can have several products.
#Entity
#Table(name = "lists")
public class GroceriesList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "list_id")
private Long listId;
#Column(name = "list_name")
private String listName;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "product_id", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "list_id", referencedColumnName = "product_id")
)
private Set<Product> products;
}
Products:
A Product can be allocated to several GroceriesLists
#Entity
#Table(name = "products")
public class Product {
public enum Category {
Dairy,
Fruit,
Vegetable,
Meat,
Grains
}
// Product ID Column, GenerationType.Identity refers to auto incr in Postgres
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private Long productId;
#Column(name = "product_name")
private String productName;
#Column(name = "product_vendor")
private String productVendor;
#Enumerated(EnumType.STRING)
#Column(name = "product_category")
private Category productCategory;
#Column(name = "product_storedquantity")
private Integer productStoredQuantity;
#JsonIgnore
#ManyToMany(mappedBy = "products")
private List<GroceriesList> groceriesLists;
}
The entities and relationships are stored in three different tables in Postgres(One for Lists, one for products, and one mapping table for the relationships).
The mapping table:
Mapping Table "lists_products"
A sample Product:
Sample Product with Id=4
The problem: I'm trying to create new lists, and that works. However, as you can spot from the Mapping Table Image, Hibernate inserts the IDs into the wrong columns. The list_id in the mapping table is currently getting the product_id, and the product_id in the mapping table is getting the list_id.
I have tried to change the order of column names & references in the #JoinTable annotation in the GroceriesList Entity. However, that throws a mapping Error. I assume my error lies somewhere in that annotation. Interestingly, Hibernate uses the correct SQL-Query. What am I missing here?
After consulting fladdimir's solution, it gave me an idea and I solved my issue. The problem lied within the #JoinColumn annotation, where I thought the "name = ..." property refers to the column name in the database.
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "product_id", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "list_id", referencedColumnName = "product_id")
)
private Set<Product> products;
However, that property refers to the declared variable inside the Entity in Spring Boot, i.e. listId
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "list_id")
private Long listId;
So, the working implementation of the #JoinTable Annotation should look like the following, where "name = ..." uses that variable name:
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "listId", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "productId", referencedColumnName = "product_id")
)
private Set<Product> products;
Consider following entities:
UserEntity:
#Entity(name = "user")
public class UserEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private List<RoleEntity> roles = new ArrayList<>();
}
RoleEntity:
#Entity(name = "role")
#NamedEntityGraph(
name = "role.entity_graph",
attributeNodes = {
#NamedAttributeNode(value = "permissions")
})
public class RoleEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
}
PermissionEntity:
#Entity(name = "permission")
public class PermissionEntity {
#Id
#Column(name = "id", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
}
roles of the UserEntity should be fetched lazy. Problem is when the roles are used (getter is called), hibernate fetches them with a query for each role and a query for each permission of each role, resulting in the n+1 issue.
Thus my question is: How can I lazy fetch the user roles with one query? Can I somehow utilize RoleEntity's EntityGraph?
Note: I already tried using #Fetch:
#Fetch(FetchMode.JOIN)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
This did not work. However together with #BatchSize it works as excpeted.
#Fetch(FetchMode.JOIN)
#BatchSize(size = 1000)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "role_permission", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "permission_id", referencedColumnName = "id"))
private List<PermissionEntity> permissions = new ArrayList<>();
I'm not happy with this solution though. Why do I need to add #BatchSize?
I think using Subgraphs might help you. Take a look at a description from Thorben Janssen on his Website: Entitygraph with multiple Subgraphs, quote:
#NamedEntityGraph(
name = "graph.AuthorBooksPublisherEmployee",
attributeNodes = #NamedAttributeNode(value = "books", subgraph = "subgraph.book"),
subgraphs = {
#NamedSubgraph(name = "subgraph.book",
attributeNodes = #NamedAttributeNode(value = "publisher", subgraph = "subgraph.publisher")),
#NamedSubgraph(name = "subgraph.publisher",
attributeNodes = #NamedAttributeNode(value = "employees")) })
I have an ClientUser entity with #OneToMany field customField:
#Entity
#Data
public class ClientUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "client_user_custom_field",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "field_id", referencedColumnName = "id"))
private Collection<ClientFieldValue> customFields;
}
And a ClientFieldValue entity:
#Entity
#Data
public class ClientFieldValue {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
private String value;
}
How can I create ClientFieldValue's for all existing ClientUser?
I know that I can do smth like:
final Iterable<ClientUser> clientUsers = userRepository.findAll();
clientUsers.forEach(clientUser ->
clientUser.setCustomFields(Collections.singleton(new ClientFieldValue())));
userRepository.saveAll(clientUsers);
But I don't want to query all and save them after set.
Is there another way to do this?
I am working on app that uses microservices architecture, i have project A which has this entity User with this code
#JoinTable(
name = "USER_AUTHORITY",
joinColumns = { #JoinColumn(
name = "USER_ID",
referencedColumnName = "id") },
inverseJoinColumns = { #JoinColumn(
name = "AUTHORITY_ID",
referencedColumnName = "id") })
private List<Authority> authorities;
and another entity Authority
#Entity
#Table(name = "AUTHORITY")
public class Authority {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "authority_seq")
#SequenceGenerator(name = "authority_seq",
sequenceName =
"authority_seq", allocationSize = 1)
private Long id;
#ManyToMany(mappedBy = "authorities", fetch = FetchType.LAZY)
private List<User> users;
}
and i have project B which has entity AAA with this code:
#Entity
public class subUser extends User
so when i run the the project B I get the following error:
org.hibernate.AnnotationException: Use of #OneToMany or #ManyToMany targeting an unmapped class: com.A.model.User.authorities[com.A.model.Authority
I get the solution from here the link
may be it help someone
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (ravermeister.artist_recordlabel, CONSTRAINT FK_9dgdyft45droyopxsqijwb1dx FOREIGN KEY (artist_id) REFERENCES artist (id))
Artist class
#Entity
#Repository
#Data
#NoArgsConstructor(force = true)
public class Artist {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "artist_firstname")
private String artist_firstname;
#Column(name = "artist_secondname")
private String artist_secondname;
#Column(name = "artist_nickname")
private String artist_nickname;
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "artist_recordlabel", joinColumns = #JoinColumn(name = "artist_id"),
inverseJoinColumns = #JoinColumn(name = "label_id"))
private Set<RecordLabel> recordLabels;
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable (name = "artist_musicrelease", joinColumns = #JoinColumn (name = "artist_id"),
inverseJoinColumns = #JoinColumn (name = "musicrelease_id"))
private Set <MusicRelease> musicReleaseSet;
and RecordLabel class
#Entity
#Repository
#Data
#NoArgsConstructor(force = true)
public class RecordLabel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "label_name")
private String label_name;
#Column(name = "label_country")
private String label_country;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "artist_recordlabel", joinColumns = #JoinColumn(name = "label_id"),
inverseJoinColumns = #JoinColumn(name = "artist_id"))
private Set<Artist> artistsList;
Please follow this guide for a many-to-many relation
http://www.baeldung.com/hibernate-many-to-many
I think this error means that you are trying to insert/update into RecordLabel table a artist_id value that does not exist in Artist table.