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).
Related
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();
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;
I know the problem with N+1 select is well-known, but trying using different fetching strategies does not help to avoid it when I use native query.
I have 2 entities: A and B that correspond to the mysql tables A and B.
Several rows in table A could have the same b_id, that's why I use #ManyToOne annotation.
#Entity
#Table(name = "A")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(FetchMode.JOIN)
private B b;
}
I also create entity B.
#Entity
#Table(name = "B")
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String url;
}
I need to find all rows in A that are linked only for 1 row in table B. So I create native query in my ARepository
public interface ARepository extends JpaRepository<A, Integer> {
#Query(value = "SELECT a.id, a.b_id, a.name, b.url "
+ "FROM a "
+ "JOIN b ON a.b_id = b.id "
+ "GROUP BY a.b_id "
+ "HAVING count(a.b_id) = 1, nativeQuery = true)
List<A> getLinkedOnce();
But when when I run my restcontroller that just call getLinkedOnce method from repository, I see that in console, 1-st my query is called, then there are number of selects for each row in A that are end with
from B b0_ where b0_.id=?
I try to use different approaches, LAZY, EAGER and it does not work. I think because there is a usage of native query. But maybe the reason is another.
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.
I have an entity called Locality as:-
#Entity
#Table(name = "CMN_LOCALITY_MASTER")
public class Locality {
#Id
#Column(name = "LOCALITY_ID", unique = true, nullable = false,length = 11)
#GeneratedValue(strategy = GenerationType.IDENTITY)
int localityId;
#Column(name = "LOCALITY_DESCRIPTION",length=70)
String localityDescription;
#JsonProperty(access = Access.WRITE_ONLY)
#ManyToOne
#JoinColumn(name = "PINCODE_ID")
Pincode pinCode;
#JsonIgnore
#ManyToOne
City city;
}
which contains another entity called City and Pincode.
City is as below:-
#Entity
#Table(name = "CMN_CITY_MASTER")
public class City{
#Id
#Column(name = "CITY_ID", unique = true, nullable = false,length = 11)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int cityId;
#Column(name = "CITY",length = 150)
private String description;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "STATE_ID")
private State state;
}
I want to Get all data from Locality entity/table which has City ID = (e.g. 1)
I tried below queries:-
#Query("SELECT a FROM Locality a INNER JOIN a.city c WHERE c.cityId=?1")
List<Locality>getAllLocalityByCity(int cityId);
and also
#Query("SELECT a FROM Locality a WHERE a.city.cityId=?1")
List<Locality>getAllLocalityByCity(int cityId);
But these are not working.
Could you please suggest me something/way to query the data?
Also, is there an Eclipse Plug-In/Tool to test HQL queries in a faster way than restarting the server for every change in the query?
Could you also suggest reading documents/book for learning HQL?
Since you are not providing any logs or explanation I can suggest you try the following:
#Query("SELECT a FROM Locality a INNER JOIN a.city c WHERE c.cityId = :cityId")
List<Locality>getAllLocalityByCity(#Param("cityId") int cityId);
For learning the HQL I would start with Hibernate Docs. You can take a look at Criteria API as well.