Spring boot JPA many to many association problem - spring-data-jpa

I have many to many relationship between two entities User and forms.
and my classes are below.
User.java
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
.....
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "userformtable", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "form_id"))
private List<forms> userform = new ArrayList<forms>();
// CONSTRUCTOR AND GETTER SETTER
}
AND forms.class
public class forms {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String formname;
#ManyToMany(mappedBy = "userform", fetch = FetchType.LAZY)
private List<User> form = new ArrayList<User>();
}
I have database table for relations ship is like below
user_id | form_id
--------------------
1 | 4
--------------------
1 | 6
-------------------
1 | 3
------------------
1 | 7
===================
3 | 3
====================
3 | 7
===================
when I try to fetch all user using spring data jpa I do not get user object with id 3. I only get user id.
here is my json response.
0: {…}
1: 3 (this record is for user id 3)
2: {…}
can anyone help me with this?
Thanks in advance.

I have solved by using #JsonIgnore annotation in forms class.
#ManyToMany(mappedBy = "userform", fetch = FetchType.LAZY)
#JsonIgnore
private List<User> form = new ArrayList<User>();

Related

JPA Repository Query on additional table #ManytoMany

I want to do select like this in my jpa spring repository
SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC;
My enitity
#Entity
#Table(name = "symptomp")
public class Symptomp {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "symptomp_id")
private Long symptomp_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinTable(name = "symptomp_sicknes",joinColumns = #JoinColumn(name = "symptomp_id"),inverseJoinColumns = #JoinColumn(name = "sicknes_id"))
private Set<Sicknes> sicknes = new HashSet<>();
#Entity
#Table(name = "sicknes")
public class Sicknes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sicknes_id")
private Long sicknes_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinColumn(name = "speciesId")
private Species species;
My Symptomp repository:
public interface SymptompRepository extends JpaRepository<Symptomp, Long> {
#Query("select p from Symptomp p where name like ?1%")
public List<Symptomp> findAllBySymptompName(String symptomp);
public Symptomp findByName(String symptomp);
public List<Symptomp> findByNameIn(List<String> list);
Integer countDistinctSymptompByName(String id);
}
How I can create this select in my JPA repository?
I try get value like in select but i got error mapping bean.
You can get query result as List<Object[]> using nativeQuery=true parameter
#Query("SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC", nativeQuery=true)
List<Object[]> getQueryResult();
Other option is to create dto class with appropriate constructor
public class QueryResultDto {
Long sicknesId;
Long count;
public QueryResultDto(Long sicknesId, Long count) {
this.sicknesId = sicknesId;
this.count = count;
}
}
Then using JPQL
#Query("select new yourproject.dtopath.QueryResultDto(...")
List<QueryResultDto> getQueryResult(#Param("symptompIds") List<Long> symptompIds);
If you want to avoid a native Query the best way is to create an Entity for that JoinTable. Then you can query it easily. Additional benefit if this is that if in future a requirement will pop up that you have to store additional attributes in that relation you will have the Entity already there to do that easily.

How to fetch entities by objects value in JPA criteria api query

I am using JPA with JSF datatable with lazy loading.
Here One car can be owned by many users. So when i logged in to the application i want the cars which is owned by the user logged in(assume it as userId=1).
I have a mapping table "Cars_User" that contains carId and userId columns.
My Entities are like this
My Car Class
#Entity
#Table(name="car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#Transient
private boolean myCar;
#NotNull
#Size(min = 1, max = 50)
public String name;
#OneToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "Cars_User", joinColumns = #JoinColumn(name = "carId"), inverseJoinColumns = #JoinColumn(name = "userId"))
private List<User> carUsers = new ArrayList<User>();
getters ...
setters ...
}
User Class
#Entity(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
private String lastName;
}
I have found one answer for Lists of String collection in this link but how can be achieved in my case.
I wanted to do get all Cars entities in criteria api that contains the logged in user id "userId" in carUsers Lists. can anyone please help?
I found the solution. I have passed the logged in user Object "user" in isMember function. This may help for somebody.
CriteriaBuilder criteriaBuilder = em.getEntityManagerFactory().getCriteriaBuilder();
CriteriaQuery<Car> criteria = criteriaBuilder.createQuery(Car.class);
Root<Car> root = criteria.from(Car.class);
criteria.where(criteriaBuilder.isMember(user, root.get(Car_.carUsers)));
List<Car> cars = em.createQuery(criteria).getResultList();

Recursive compare - Hibernate Audit object + Javers

I have following simple data model:
TestUser:
#Entity
#Table(name = "TEST_USER")
#Audited
public class TestUser implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "testUser", fetch = FetchType.LAZY)
#JsonIgnore
#OrderBy
private Set<TestUserAddress> addresses = new HashSet<TestUserAddress>();
// ...
}
TestUserAddress:
#Entity
#Table(name = "TEST_USER_ADDRESS")
#Audited
public class TestUserAddress implements Serializable {
private static final long serialVersionUID = -2071806522133475539L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "CITY", nullable = false)
#NotEmpty
private String city;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "TEST_USER_ID")
#JsonBackReference
private TestUser testUser;
// ...
}
I have performed one operation to update TestUserAddress object - for example I changed TestUserAddress name:
[TestUser]:
- id = 1
- name = "testName"
- [TestUserAddress]:
- name = "testAddressName" ===> changed to "testAddressName2"
- city = "testAddressCity"
I developed some code to compare TestUser objects, like below:
// testUserId - ID of the object that I would like to compare
// startRevision/endRevision - revisions to compare - stored in Hibernate Envers audit tables
AuditReader testUserReader = AuditReaderFactory.get(em);
TestUser testUserStart = testUserReader.find(TestUser.class, testUserId, startRevision.intValue());
TestUser testUserEnd = testUserReader.find(TestUser.class, testUserId, endRevision.intValue());
Javers javers = JaversBuilder.javers().build();
Diff diff = javers.compare(testUserStart, testUserEnd);
but above code is not showing me change done for TestUserAddress.
If I run below code:
// testUserId - ID of the object that I would like to compare
// startRevision/endRevision - revisions to compare - stored in Hibernate Envers audit tables
AuditReader testUserReader = AuditReaderFactory.get(em);
TestUser testUserStart = testUserReader.find(TestUser.class, testUserId, startRevision.intValue());
Set<TestUserAddress> testUserAddressStart = testUserStart.getAddresses();
TestUser testUserEnd = testUserReader.find(TestUser.class, testUserId, endRevision.intValue());
Set<TestUserAddress> testUserAddressEnd = testUserEnd.getAddresses();
Javers javers = JaversBuilder.javers().build();
Diff diff = javers.compare(testUserStart, testUserEnd);
Diff diff2 = javers.compare(testUserAddressStart, testUserAddressEnd);
diff2 variable is showing me all changes done for TestUserAddress, however I would like to perform compare operation on root object to get the full lists done for object field and all child objects.
Do you know how can I do it?
I will appreciate any help. Thank you.

query entity with condition on ManyToMany relation

I have two Entites
#Entity
public Report()
#Id
#Column(name = "REPORT_ID")
private long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="reports_projects",
joinColumns={#JoinColumn(name="report_id", referencedColumnName="REPORT_ID")},
inverseJoinColumns={#JoinColumn(name="project", referencedColumnName="PROJECT_ID")})
private List<Project> projects;
second is:
#Entity(name = "projects")
public class Project
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PROJECT_ID")
// seems like spring's jpa has issue hanlde "_" between the words
private long id;
#Column(name = "CODE", nullable = false)
private String code;
#Column(name = "DESCRIPTION", nullable = false)
private String description;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
i'm tring to query reports by projects.code
tried few stuff like
#Query("select reports from org.jpp.domain.quicksearch.ReportQS reports inner join reports.projects p where p.code in :code")
And
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<QuickSearchResult> query = cb.createQuery(QuickSearchResult.class);
Metamodel m = em.getMetamodel();
EntityType<ReportQS> ReportQSMetaModel = m.entity(ReportQS.class);
Root<ReportQS> reportsQS = query.from(ReportQS.class);
Root<Project> projects = query.from(Project.class);
Join<ReportQS, Project> joinReportsProjects = reportsQS.join("projects");
Predicate condition = cb.equal(projects.get("code"),"gnrl");
query.select(reportsQS).where(condition);
TypedQuery<QuickSearchResult> q = em.createQuery(query);
I get empty result for both of the queries
Any idea how to get this to work ?
Thanks in advance,
Oak
Try following code:
String query = "select r from ReportQS r join r.projects p where p.code = :code";
List<ReportQS> reports = em.createQuery(query,ReportQS.class).setParameter("code","grnl").getResultList();
Make sure that ReportQS is name of entity class (in your sample code you have different class name and different entity name used in query).

JPA, Mixed surrogate key with foreign key and sequence number

I've got two tables:
DOCUMENT
--------
DOC_ID (PK)
.
.
.
SECTION
-------
DOC_ID (FK, PK)
SECTION_NUM (PK)
.
.
.
Entries in the database might look like this:
Document:
DOC_ID | . . .
--------------
1 | . . .
2 | . . .
Section:
DOC_ID | SECTION_NUM | . . .
---------------------------
1 | 1 | . . .
1 | 2 | . . .
1 | 3 | . . .
2 | 1 | . . .
Document has a generated Id on DOC_ID, while Section has a composite primary key over DOC_ID and SECTION_NUM.
SECTION_NUM is a locally(application) generated sequence number starting fresh for every document.
My entity classes look as follows:
#Entity
#Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
#Id
#Column(name = "DOC_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DocIdSeq")
#SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
}
#Entity
#Table(name = "SECTION")
#IdClass(SectionId.class)
public class Section implements java.io.Serializable {
#Id
#Column(name = "DOC_ID", nullable = false)
private Long docId;
#Id
#Column(name = "SECTION_NUM", nullable = false)
private Integer sectionNum;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DOC_ID")
private Document document;
}
public class SectionId implements java.io.Serializable {
private Long docId;
private Integer sectionNum;
}
When inserting a new Document and related Section, I do the following:
Document doc = new Document();
Section section = new Section();
section.setDocument(doc);
section.setSectionNum(1);
entityManager.persist(doc);
When persisting I get an exception stating that NULL is not allowed for column SECTION_NUM.
I'm using OpenEJB (which relies on OpenJPA behind the scenes for unit testing), and found when stepping through OpenJPA code that it successfully persists the Document object, but when it comes to the Section object it creates a new instance reflectively and sets all fields to null, so losing the sectionNum value, before linking it to the Document object persisted earlier.
Unfortunately I can't change the DB schema, as it's a legacy system.
Has anybody done something similar and got it working?
I've been meaning to update this for some time, but been too busy...
Ok, so it turns out this isn't really possible with JPA.
However, there is a workaround.
Previously I mentioned that the Document class looks like this.
#Entity
#Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
#Id
#Column(name = "DOC_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
#SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
}
That was just a shortened version to clarify the issue.
The real class has a collection of Sections too:
#Entity
#Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
#Id
#Column(name = "DOC_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
#SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
#OneToMany
private Set<Section> sections = new HashSet<Section>(0);
}
If Section had a simple primary key, JPA would easily handle the relationship, as it would accept an id from the application, or generate it from a sequence, but it won't do both with one id.
So, the solution is to manage the relationship yourself, and add a lifecycle function:
#Entity
#Table(name = "DOCUMENT")
public class Document implements java.io.Serializable {
#Id
#Column(name = "DOC_ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"DocIdSeq")
#SequenceGenerator(name = "DocIdSeq", sequenceName = "DOC_ID_SEQ", allocationSize = 1)
private Long docId;
#Transient
private Set<Section> sections = new HashSet<Section>(0);
#PostPersist
public void updateChildIds() {
for (Section section : this.sections) {
section.getId().setDocId(this.docId);
}
}
}
As you can see, the Section relationship is now Transient, meaning JPA won't manage it.
After persisting a Document, the framework will call the updateChildIds function, where you manually update the Section id's with the newly persisted Document id's.
This could be demonstrated in the following facade:
#Stateless
public void DocumentFacade implements DocumentFacadeLocal {
#PersistenceContext
private EntityManager entityManager;
public void save(Document entity) throws Exception {
this.entityManager.persist(entity);
this.entityManager.flush();
this.persistTransientEntities(entity);
this.entityManager.flush();
}
private void persistTransientEntities(CaseInstructionSheet entity) {
for (Section section : entity.getSections()) {
this.entityManager.persist(section);
}
}
}
Actually, JPA is perfectly able to handle this. The annotation you are looking for is MapsId.
In your case, in your Section, on the docId you simply need to add the following:
#MapsId("docId")
The value of the MapsId annotation is the attribute name of your compound primary key (which in this case is the same)