In a database there are "pastries". A pastry has zero, one, or many allergens.
Hence I'm using a list in Pastry with #OneToMany relation. Now, with this way of managing the relations, we get redundant entries, as for each pastry a new entry with the same allergen is created:
Our current solution looks like this:
A mapping-table in between those two database tables.
How can I achieve a non-redundant solution with direct references to the allergen elements from a list in the Pastry class? I googled a lot but sadly couldn't find anything helpful.
Would look like this:
Regarding my comment on solution:
What you need is a called a JoinTable. Based on your schema definition this could work as a potential example:
#Data
#Entity(name = "Pastry")
#Table(name = "PASTRY")
public class Pastry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToMany
#JoinTable(
name = "PASTRY_ALLERGEN",
foreignKey = #ForeignKey(name = "FK_PASTRY_ALLERGEN_PASTRY"),
inverseForeignKey = #ForeignKey(name = "FK_PASTRY_ALLERGEN_ALLERGEN"),
joinColumns = #JoinColumn(name = "pastry_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "allergen_id", referencedColumnName = "id")
)
private Set<Allergen> allergens = new HashSet<>();
}
For the Pastry association and:
#Data
#Entity(name = "Allergen")
#Table(name = "ALLERGEN")
public class Allergen {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
}
For the Allergen association.
This mapping will in turn produce an intermediate table called PastryAllergens which will hold reference to the PK of the Pastry table and the PK of the Allergen table.
Related
I have two entities connected with many-to-many relationship. For example:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Game> games = new HashSet<>();
}
#Entity
public class Game {
#Id
private Long id;
#ManyToMany(mappedBy = "games", fetch = FetchType.LAZY)
List<Account> accounts = new ArrayList<>();
}
So, there is a table account_games(account_id, game_id) in mysql describing entities many-to-many relations.
I don't want to have Game entity anymore. Is there a way to get rid of Game and leave gameId relation only? So, I'd like to have code something like that:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Long> gameIds = new HashSet<>();
}
without making changes in database.
I've tried different configuration on javax.persistance annotations, but none worked
You can use #ElementCollection and #CollectionTable to achieve that.
#Entity
public class Account {
#Id
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "account_games", joinColumns = #JoinColumn(name = "account_id"))
#Column(name = "game_id", nullable = false)
private Set<Long> gameIds = new HashSet<>();
}
You may have to change the query on how to filter data using gameId. Element Collection Query
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;
I've faced with an issue, I've declared the bidirectional #ManyToMany relation between my entities, but when I try to perform the select by repository, the #ManyToMany collection is always empty.
There is an example of my entities:
#Data
#EqualsAndHashCode
#Entity
#Table(name = "provider", schema = "debt")
public class ProviderEntity {
#Id
#Column(name = "provider_id")
private String providerId;
#Column(name = "external_provider_id")
private String externalProviderId;
private String description;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "provider_supported_driver", schema = "debt",
joinColumns = #JoinColumn(name = "provider_id", foreignKey = #ForeignKey(name = "FK_PSD_TO_P")),
inverseJoinColumns = #JoinColumn(name = "driver_id", foreignKey = #ForeignKey(name = "FK_PSD_TO_D"))
)
private Set<DriverEntity> drivers = new HashSet<>();
}
#Data
#EqualsAndHashCode
#Entity
#Table(name = "driver", schema = "debt")
public class DriverEntity {
#Id
#Column(name = "driver_id")
private String driverId;
#Enumerated(EnumType.STRING)
private DriverType type;
private String description;
#ManyToMany(mappedBy = "drivers")
private Set<ProviderEntity> provider = new HashSet<>();
}
Also, I've enabled the SQL query logs, that hibernate generates, when I run it, the query works well.
I've tried different guides and answers for the same issues, but I've no result with them.
What's the reason for this behavior? Maybe has somebody any ideas?
I've found the reason for this issue and the problem is in the primary keys of my tables, they aren't generated by sequence or some different way, as I understand Hibernate doesn't understand how to map this columns
I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}
I just refactor a Project to use Hibernate (4.2.4.Final) with Inheritance. But I got trouble with ManyToMany annotation.
I have a base File Class like this:
#Entity
#Table(name = "file")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "descriminator", length = 25)
public abstract class File {
#Id
#Column(name = "id", unique = true, nullable = false, length = 256)
private String id;
}
and a special Inheritance class like this:
#Entity
#DiscriminatorValue("ISSUE_HISTORY_ATTACHMENT")
#Data
public class IssueHistoryAttachment extends File {
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true) }, inverseJoinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)})
private IssueHistory history;
}
This IssueHistoryAttachment Class is also referenced in my IssueHistory Class.
#Entity
#Table(name = "issue_history")
#TableGenerator(name="tg", table="hibernate_sequences",pkColumnName="sequence_name", valueColumnName="sequence_next_hi_value", allocationSize=1)
public class IssueHistory implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "tg")
#Column(name = "id", unique = true, nullable = false)
private int id;
// some other fields
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "issue_history_attachment", joinColumns = {
#JoinColumn(name = "issue_history_id", nullable = false)
}, inverseJoinColumns = {
#JoinColumn(name = "attachment_id", nullable = false, unique = true)
})
private Set<IssueHistoryAttachment> attachments = new HashSet<IssueHistoryAttachment>();
}
When i now store a IssueHistory Instance with two Attachments, all this fields are correctly saved in my database.
I got 2 new entries in the file table, one new entry in the *issue_history* table and two correct entries in the relation table *issue_history_attachment*.
So at this points all thinks are looking fine. But when I try to read the Values Attachment Set in the IssueHistory Instance only contains one element instead of two like stored in the database.
Any suggestions how to solve this?
I just found the source of the Problem.
It was a missing/wrong equals method. :-)
I can't comment yes so I have to make an answer.
I see one problem in your code (or maybe I don't understand it):
In IssueHistory you are using #ManyToMany to IssueHistoryAttachment but in IssueHistoryAttachment you are using #ManyToOne.
In my opinion it is the reason of your problem.