Many to many Jpa Query Spring boot - mysql

I have three mysql tables with a many to many relationship and I am trying to make a Jpa Query on spring boot. The tables are
product with fields id, name
extra with fields id, name
product_extra with fields id, product_id, extra_id
Product table has a many to many relationship with extra table, as a product can have many extras hence the need for product_extra table
Here is the query i would like to include in my project
SELECT extra.name
FROM extra
INNER JOIN product_extra ON extra_id = extra.id
WHERE product_id = ?;
Should i have like a #ManyToMany annotation and where should i have it

Yes you should. Use #ManyToMany to map between Product and Extra.Make sure to use Set instead of List for the mapping in order to have better performance. It looks like:
#Entity
#Table(name="product")
public class Product{
#ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE})
#JoinTable(name = "product_extra",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "extra_id")
)
private Set<Extra> extras = new HashSet<>();
}
#Entity
#Table(name="extra")
public class Extra{
#ManyToMany(mappedBy = "extras")
private Set<Product> products = new HashSet<>();
}
After mapping them , you can then use JPQL to get a product by id together with its extra by :
select p from Product p left join fetch p.extras where p.id = :productId;

Related

Spring data jpa - Aggregate list of values in many-to-many relationship

I have the following (simplified) entities:
#Table(name = "groups")
public class Group {
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#ManyToMany(fetch = FetchType.LAZY)
private Set<User> users;
...
}
#Table(name = "users")
public class StoredUser extends StoredBase {
#Column(name = "id")
private long id;
#Column(nullable = false, unique = true)
private String username;
#ManyToMany(mappedBy = "users")
private Set<Group> groups;
...
}
So I wanted to get something like list of username group by groupId:
group_id group_name username
--- --- ----
1 gr1 1, 2, 3
2 gr2 4, 5
3 gr3 1, 4
At first I was just using groupRepository.findAll() and convert the response based on that. But the amount of unrelated data coming with Group and User is big and is slowing down the response. So I want to fetch just the related values only.
So I wonder what is the best way to achieve this?
Many thanks
group_concat and a 'group by' should get you there.
Something like (untested, but someone will be along to tell me if I'm being fick):
select g.group_id, g.group_name, group_concat(u.username) as all_users
from groups as g join users as u on g.users = u.id
group by u.id;
You can use projections from Spring Data JPA (doc). In your case, create interface:
interface GroupAndUsers{
String getId();
String getName();
Set<Integer> getUsersId();
}
and add following method to your repository:
List<GroupAndUsers> findAllGroupAndUsers();

Selecting where an entity contains a list which elements have another list

I am writing a JPQL query in spring JPA and i have the following scenario. I have a entity Margin which contains a list of PerPeriodMargin and each element of PerPeriodMargin contains a list of MarginFactor.
code:
#Entity
public class Margin {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "margin", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PerPeriodMargin> perPeriodMargins;
}
#Entity
public class PerPeriodMargin{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Margin margin;
#OneToMany(mappedBy = "perPeriodMargin", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MarginFactor> marginFactors;
}
#Entity
public class MarginFactor{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Underlying underlying;
#ManyToOne
private PerPeriodMargin perPeriodMargin;
}
I would like to select all Margin where MarginFactor underlying.id is passed as a parameter in a single jpql query?
Any suggestions?
i activated hibernate logging by this line in application.properties "logging.level.org.hibernate.SQL=DEBUG" and i have been confused about the generated SQL queries. its seems there is something wrong about that multiple join. can any one explain this.
select * from margin m inner join per_period_margin ppm on m.id = ppm.margin_id join margin_factor mf on ppm.id = mf.per_period_margin_id where mf.underlying_id = ? and m.id = (select margin_id from trading_account ta where ta.id = ?)
and on for perPeriodMargin
select perperiodm0_.margin_id as margin_i5_12_0_, perperiodm0_.id as id1_12_0_, perperiodm0_.id as id1_12_1_, perperiodm0_.end_time as end_time2_12_1_, perperiodm0_.margin_id as margin_i5_12_1_, perperiodm0_.name as name3_12_1_, perperiodm0_.start_time as start_ti4_12_1_ from per_period_margin perperiodm0_ where perperiodm0_.margin_id=?
until now all is seams good.
finally ther are two other queries that try to get marginFactors.
select marginfact0_.per_period_margin_id as per_peri6_9_0_, marginfact0_.id as id1_9_0_, marginfact0_.id as id1_9_1_, marginfact0_.bid as bid2_9_1_, marginfact0_.notional as notional3_9_1_, marginfact0_.offer as offer4_9_1_, marginfact0_.per_period_margin_id as per_peri6_9_1_, marginfact0_.settlement as settleme5_9_1_, marginfact0_.underlying_id as underlyi7_9_1_, underlying1_.id as id1_24_2_, underlying1_.digits as digits2_24_2_, underlying1_.display as display3_24_2_, underlying1_.enable as enable4_24_2_, underlying1_.enable_buy as enable_b5_24_2_, underlying1_.enable_sell as enable_s6_24_2_, underlying1_.focus_digits as focus_di7_24_2_, underlying1_.focus_position as focus_po8_24_2_, underlying1_.left_currency_id as left_cu11_24_2_, underlying1_.name as name9_24_2_, underlying1_.right_currency_id as right_c12_24_2_, underlying1_.temporary_disable as tempora10_24_2_, currency2_.id as id1_3_3_, currency2_.digits as digits2_3_3_, currency2_.enable_buy as enable_b3_3_3_, currency2_.enable_sell as enable_s4_3_3_, currency2_.name as name5_3_3_, currency2_.symbol as symbol6_3_3_, currency2_.temporary_disable as temporar7_3_3_, currency3_.id as id1_3_4_, currency3_.digits as digits2_3_4_, currency3_.enable_buy as enable_b3_3_4_, currency3_.enable_sell as enable_s4_3_4_, currency3_.name as name5_3_4_, currency3_.symbol as symbol6_3_4_, currency3_.temporary_disable as temporar7_3_4_ from margin_factor marginfact0_ left outer join underlying underlying1_ on marginfact0_.underlying_id=underlying1_.id left outer join currency currency2_ on underlying1_.left_currency_id=currency2_.id left outer join currency currency3_ on underlying1_.right_currency_id=currency3_.id where marginfact0_.per_period_margin_id=?
and as we see here in the last query there are only one where condition on perPeriodmarginId. as I think it must also contain underlying condition, because this query is the responsible of fetching marginFactors where we must extract the data that who have a specific underlyingId.
I'm really serious about this question please can someone explain that or is it a bug in hibernate!
This is the query:
select m
FROM Margin m
JOIN m.perPeriodMargins ppm
JOIN ppm.marginFactors mf
JOIN mf.underlying und
WHERE und.id = :id

How to perform a cascaded merge against child of new entity with JPA and Hibernate

This question relates to managing id numbers to ensure that I don't end up with duplicate identities on my person's table. It is a springboot project with MySQL database.
Some background:
I have an HTML form for submitting an "episode". Each episode contains "persons" and has a relationship to "persons" of ManyToMany.
"episodes" are entered and submitted into the database (db1) by field staff. A few hours later the episode is manually entered into a second database (db2) by BackOffice staff.
On my spring attached database (db1) I have a persons table which has a native auto generated id field. db1 also has a id2 field - which records the unique id for the person from db2.
Field staff do not always have access to id2 when they enter a episode, but BackOffice staff do.
When I save a new "episode" I need the save method to check if person id2 exists in the database and perform an update on person (not create new).
Then delete the duplicate person.
Episode entity:
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#Column(name="start_date")
#DateTimeFormat (pattern="dd/MM/yyyy HH:mm")
private Date start_date;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "episode_person", joinColumns = #JoinColumn(name = "episode_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"))
private List<Person> persons;
#OneToOne(cascade=CascadeType.ALL)
//#JoinColumn(name = "id")
private Address address;
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long id2;
private String surname;
private String firstname;
private String phoneHome;
#DateTimeFormat(pattern = "dd/mm/yyyy")
private Date dob;
#ManyToMany(mappedBy = "persons", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Episode> episodes;
EpisodeServiceImpl
#Override
#Transactional
public Episode saveEpisode(Episode episode) {
List mergedPeople = personService.mergeDetachedWithAttached( episode.getPersons() );
episode.setPersons( mergedPeople );
return episodeRepository.save(episode);
}
PersonServiceImpl
#Transactional
#Override
public List mergeDetachedWithAttached(List<Person> people) {
final List<Person> results = new ArrayList<>();
if ( people != null && !people.isEmpty() ) {
// loop over every person
for (Person person : people) {
// for current person try to retrieve from db via Id2
Person db2Person = personRepository.findById2( person.getId2() );
// if a person was retrieved use them instead of submitted person
if (db2Person != null ) {
System.out.println("A matching person was found in the db using id2 - this is the person that will be added");
results.add(db2Person);
} else {
results.add(person);
}
}
}
return results;
The way this is written at the moment when ever a new episode is submitted I create new person(s) even if I successfully looked them up from db1 using id2 and added them to the episode.
How can I handle this so that:
I can merge duplicate identities based on comparing id2. The joining table that holds episode_id and person_id will also need to be updated where a id is deleted after a merge.
It's much easier if you replace the #ManyToMany association with 2 bidirectional #OneToMany associations, meaning that you map the association table as well.
This way, considering that you have those duplicated Episode or Person entities, you can easily move the joined association by simply adjusting the #ManyToOne associations on the association entity. As for the duplicated Episode or Person you can either use UPSERT as explained below, or do a manual merging after the entries are added to the DB by the batch process.
Typically, when you have multiple nodes that running concurrently, you can use the database UPSERT or MERGE operation.
You could combine the UPSERT with Hibernate #SqlInsert annotation.
To handle the FK updates, you'd need to use the FK ON DELETE CASCADE strategy.

JPA Hibernate Nested Objects

Basically I have 3 tables: COUNTRY, STATE and CITY.
in Country.java:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "COUNTRY_ID")
private List<State> state = new Vector<State>();
in State.java
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "STATE_ID")
private List<City> city = new Vector<City>();
JPA query looks like:
caEntityManager.createQuery("SELECT C FROM COUNTRY C
JOIN fetch C.STATE S JOIN fetch S.CITY").getResultList();
When I try to execute the query I get:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
I am sure my query is wrong, i am new to this, please appoint me to the right direction. I
Thanks!
Problem is Hibernate can not fetch two bags EAGERly. The quick solution would be to change the Lists to Sets.
To read more:
Article 1
Article 2
Also this question suggests a couple of other solutions.

Hibernate criteria example query gets multiple records

I have a generic criteria query and it returns same records. I think there is something wrong with my student save method. Here is my save method;
Student student = new Student();
student.setId(Utility.generateUUID());
student.setClassroom(selectedClassroom);
student.setUrl(urlAddress);
genericService.save(student);
When I try to get all Classrooms from datatable it returns 3 Classroom object which are same but there is only one record in Classroom table. The Problem is there are 3 student records which Classrooms are referencing to this classroom record.
My criteria query;
#Transactional(readOnly = true)
public <T> List<T> getByTemplate(T templateEntity) {
Criteria criteria = getCurrentSession().createCriteria(templateEntity.getClass());
criteria.add(Example.create(templateEntity));
return criteria.list();
}
Entities;
public class Classroom{
....
#OneToMany(mappedBy = "classroom", fetch = FetchType.EAGER)
private List<Student> studentList;
}
public class Student{
#JoinColumn(name = "classroom", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Classroom classroom;
}
Try to add the following to your criteria:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This will retrieve distinct entities for Classroom even tought the inner join select will retrieve the three lines (one per user).