How do I select information from ManyToMany Table in JPA? - java

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

Related

How to convert collection of entities relation into collection of primitive ids in JPA/Hibernate?

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

Bidirectional #ManyToMany doesn't remove records from the join table xx_yy with CascadeType.ALL

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?

What hibernate / jpa annotation is required

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);
}

ManyToMany in javax.persistence allow duplicate entries

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;

Hibernate criteria many to many

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).

Categories