I have a table called Order which has many Items, so I need a map to link the Items to the Order.
At the moment I have this:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(name = "order_map_item", joinColumns = #JoinColumn(name = "orderId"), inverseJoinColumns = #JoinColumn(name = "itemId"))
List<Item> items = new ArrayList<Item>();
but it doesn't allow duplicates if I want to add an Item twice.
When I edit the MySql database by hand and remove the index, I can add duplicates. But they are getting more if I call em.refresh(order);
Please tell me, what is best practice for my case? Can't find anything...
Looks like your problem is that the join table has obviously a composite primary key with each column as foregn key to both the tables.
However your use case dictates that there may be repetition. In this case, an alternate strategy could be a mapping entity
#Entity
public class OrderItemMapping {
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name="order")
private Order order;
#ManyToOne
#JoinColumn(name="item")
private Item item;
Order Class:
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Item> items;
Items Class:
#ManyToOne(cascade = {CascadeType.ALL} )
#JoinColumn(name = "orderId")
private Order order;
Related
I have two entities mapped Board and Tag by #ManyToMany to a join table board_tag_table.
How would I return the top 5 most common tag_id in the board_tag_table?
enter image description here
public class Board {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinTable(name = "board_tag_table",
joinColumns = {
//primary key of Board
#JoinColumn(name = "id", referencedColumnName = "id")
},
inverseJoinColumns = {
//primary key of Tag
#JoinColumn(name = "tag_id", referencedColumnName = "tag_id")
})
private Set<Tag> tags = new HashSet<>();
}
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer tag_id;
private String tagname;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "tags")
private Set<Board> boards = new HashSet<>();
}
Unable to find how to query within a many to many table
you can pass through foreach and write your query in Tag repository, but I think you can't write query, because they are have list from two sides
Consider using a native query instead.
If you want to use the JPA, you can add a field (Eg. usedCount) in the Tag entity and follow the instructions here https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.limit-query-result.
The query should look like this:
List<Tag> findByUsedCount(Sort sort, Pageable pageable);
Don't look at it as trying to access the board_tag_table, and instead look at how you would do this with the java entities themselves. This would be just selecting the 5 top Tags based on the number of boards they have. "select t.tag_id, count(b) as boardCount from Tag t join t.boards b group by t.tag_id order by boardCount", then use maxResults to limit the returned values to 5
I am attempting to remove entries from a many to many relationship using Spring Data JPA. One of the models is the owner of the relationship and I need to remove entries of the non-owner entity. These are the models:
Workflow entity
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "dataUploads")
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
DataUpload repository
#Repository
public interface DataUploadsRepository extends JpaRepository<DataUpload, UUID> {
#Transactional
void delete(DataUpload dataUpload);
Optional<DataUpload> findByDataUploadId(UUID dataUploadId);
}
To delete data uploads, I am trying to execute a couple of query methods of the repository:
First version
dataUploadsRepository.deleteAll(workflow.getDataUploads());
Second version
workflow.getDataUploads().stream()
.map(DataUpload::getDataUploadId)
.map(dataUploadsRepository::findByDataUploadId)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(dataUploadsRepository::delete);
Problem is that Spring Data JPA is not removing DataUploads nor entries of the association table workflow_data.
How can I tell Spring Data to remove from both data_uploads and workflow_data (association table)?
I would appreciate any help.
I found the solution for this problem. Basically, both entities (in my case) need to be the owner of the relationship and the data from the association table must be deleted first.
Workflow entity (relationship owner)
#Entity(name = "workflows")
public class Workflow {
#Id
#Column(name = "workflow_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID workflowId;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"))
private Set<DataUpload> dataUploads = new HashSet<>();
// Setters and getters...
}
DataUpload entity (relationship owner)
#Entity(name = "data_uploads")
public class DataUpload {
#Id
#Column(name = "data_upload_id")
private UUID dataUploadId;
#ManyToMany
#JoinTable(name = "workflow_data",
joinColumns = #JoinColumn(name = "data_upload_id", referencedColumnName = "data_upload_id"),
inverseJoinColumns = #JoinColumn(name = "workflow_id", referencedColumnName = "workflow_id"))
private Set<Workflow> workflows = new HashSet<>();
// Setters and getters...
}
Notice that Workflow has ALL as cascade type, since (based on the logic I need), I want Spring Data JPA to remove, merge, refresh, persist and detach DataUploads when modifying workflows. On the other hand, DataUpload does not have cascade type, as I do not want Workflow instances (and records) to be affected due to DataUploads deletions.
In order to successfully delete DataUploads, the associate data should be deleted first:
public void deleteDataUploads(Workflow workflow) {
for (Iterator<DataUpload> dataUploadIterator = workflow.getDataUploads().iterator(); dataUploadIterator.hasNext();) {
DataUpload dataUploadEntry = dataUploadIterator.next();
dataUploadIterator.remove();
dataUploadsRepository.delete(dataUploadEntry);
}
}
dataUploadIterator.remove() deletes records from the association table (workflow_data) and then the DataUpload is deleted with dataUploadRepository.delete(dataUploadEntry);.
It has been a while since I have to fix these kind of mappings so I'm not going to give you a code fix, instead maybe give you another perspective.
First some questions like, do you really need a many to many? does it make sense that any of those entities exist without the other one? Can a DataUpload exist by itself?
In these mappings you are supposed to unassign the relationships on both sides, and keep in mind that you could always execute a query to remove the actual values (a query against the entity and the intermediate as well)
A couple of links that I hope can be useful to you, they explain the mappings best practices and different ways to do the deletion.
Delete Many, Delete Many to Many, Best way to use many to many.
I'm implementing categorisation system where a category will usually have several subcategories, and a subcategory will have at least one parent, but there will certainly be cases when a subcategory will have more than one parent.
That's why I chose ManyToMany approach.
So, the Category:
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "cat_id", nullable = false)
private Integer catId;
....
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "cats_subcats",
joinColumns = #JoinColumn(name = "cat_id"),
inverseJoinColumns = #JoinColumn(name = "subcat_id")
)
private Set<Subcategory> subcats;
....
The Subcategory:
public class SubCategory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "subcat_id", nullable = false)
private Integer subcatId;
....
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "subcats")
private Set<Category> cats;
....
This setup works, it creates the join table, inserts my two dummy subcats, and also creates the two joining records in the join table.
I then proceeded with testing how it would behave in different scenarios.
First, I wanted to remove one subcategory from an existing category with three subcategories.
My managed bean:
....
#PostConstruct
public void init() {
category = new Category();
category.setName("Programmatically added ctg");
category.setSlug("programmatically-added-crg");
Set<Subcategory> subcats = new HashSet<>(2);
Subcategory subcat = new Subcategory();
subcat.setName("Subcat one");
subcats.add(subcat);
Subcategory subcat2 = new Subcategory();
subcat2.setName("Subcat to be removed");
subcats.add(subcat2);
Subcategory subcat3 = new Subcategory();
subcat3.setName("The most recent subcat");
subcats.add(subcat3);
category.setSubcats(subcats);
// this initially saves both the cat and the subcats
ctgService.save(category);
categories = ctgService.getAll();
// now I remove one of the three subcats
category.getSubcats().remove(subcat2);
// this is a method belonging to my service (EJB)
ctgService.update(category);
// upon re-fetching, I can see in my DB that the subcat has not been removed
categories = ctgService.getAll();
}
....
I got it to work by changing (in Category entity) #ManyToMany(cascade = CascadeType.ALL) to #ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}).
Indeed, it removes the subcat as desired but... When I take a look at my categories (there's only one in this scenario) - I can see that it somehow has been re-inserted because it now has the cat_id of 2 instead of 1.
Could anyone shed some light on any/both of the issues I'm experiencing?
I think you want 'orpahnremoval' but it's not available on #ManyToMany
How do I delete orphan entities using hibernate and JPA on a many-to-many relationship?
I have two classes.
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id", unique = true)
private int invId;
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
#Column(name = "invoice_amt", nullable = false)
private Double invAmt;
}
And,
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", unique = true)
private int itemId;
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name="invoice_id")
private List<Invoice> invoiceList;
}
I am new to JPA. So my understanding may not be accurate.
My understanding is that, if I save Invoice, the invoice_id of that instant should cascade down to invoice_id of all the items.
However, I see Item being saved but get null in place of invoice_id of the Item.
What am I missing?
UPDATE!!! UPDATE!!!
Ok so I changed the #ManyToOne to be a singular attribute and did objItem.setInvoice(objInvoice) and saved it. However, I still get NULL on invoice_id.
You are annotating a many-to-one relation, but use collections on both sides. This will not work. The one-side has to map the relation to a singular attribute. In your case, it would be
#ManyToOne
private Invoice invoice
Perhaps you rather need a many-to-many relation. In this case, you will need to change the annotations to #ManyToMany and get rid of the cascades (they tend not to work as expected from a many-side).
targetEntity attribute and the #JoinColumn annotation are redundant on the invoice attribute of Item.
In order for the Item to save the id of the related invoice, you first need to set the invoice attribute of the Item since item is the owning side (the one where the relation information is stored).
I'm not sure this is your only problem, but a 1:n relationship shouldn't have a List both ways. If you turn List<Invoice> into a simple Invoice object, you'll at least be closer to a solution. We can go from there if your code still fails.
public class Item {
#ManyToOne(optional = false, targetEntity = Invoice.class)
#JoinColumn(name = "invoice_id")
private Invoice invoice;
}
public class Invoice {
#OneToMany(mappedBy = "invoiceList", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Item> itemList;
}
I have two entities with many to many relationship:
#Entity
#Table(name = "items")
public class Item implements Comparable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private Integer itemId;
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinTable(name = "items_criteria",
joinColumns = #JoinColumn(name = "item_id"),
inverseJoinColumns = #JoinColumn(name = "filter_criterion_id"))
private List<FilterCriterion> filterCriteria;
}
and
#Entity
#Table(name = "filter_criteria")
public class FilterCriterion {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "filter_criterion_id")
private Integer filterCriterionId;
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinTable(name = "items_criteria",
joinColumns = #JoinColumn(name = "filter_criterion_id"),
inverseJoinColumns = #JoinColumn(name = "item_id"))
private List<Item> items;
}
I need to write function in ItemDao class that returns List of Items which have all elements of the collection given as an argument. In the example below I use Restrictions.in so the result contains even those Items which contain for example only one FilterCriterion from List given as argument. I need to have in the result only those Items, which contain all of the elements in argument List.
public List<Item> getItems(List<FilterCriterion> currentFilterCriteria) {
Criteria criteria = ht.getSessionFactory().getCurrentSession().createCriteria(Item.class);
List<Integer>currentFilterCriteriaId = new ArrayList<Integer>();
for(FilterCriterion criterion : currentFilterCriteria){
currentFilterCriteriaId.add(criterion.getFilterCriterionId());
}
if(!currentFilterCriteriaId.isEmpty()){
criteria.createAlias("filterCriteria", "f");
criteria.add(Restrictions.in("f.filterCriterionId", currentFilterCriteriaId));
}
return criteria.list();
}
First of all, you'll have to fix your mapping. You don't have a bidirectional ManyToMany association here, but two, unrelated, unidirectional ManyToMany associations. One side must be the inverse side by using the mappedBy attribute:
#ManyToMany(mappedBy = "filterCriteria")
private List<Item> items;
Now to your question, one way of doing this is to use such a query. I'll let you translate it to Criteria if you really want to. I'd use HQL instead, since it's so much easier and maintainable:
select i from Item i
where :criteriaIdSetSize = (select count(c.id) from Item i2
inner join i2.filterCriteria c
where c.id in :criteriaIdSet
and i2 = i)
You should use a Set to hold your criteria IDs rather than a list though, to make sure it doesn't contain duplicates (which would make the result incorrect).