Delete Values from Join Table spring boot - spring

I have two tables students and subjects. A student can have more than one subject and vice versa. I have two model classes and vave joined using Many to Many relationship in spring boot and JPA.My problem is how I can delete values from my join table. But I can't figure out how I can do delete from join table. For Student and Subject Model I delete comfortably using deleteById() function.This is my code:
#ManyToMany
#JoinTable(
name = "student_subject",
joinColumns = #JoinColumn(name = "student_id"),
inverseJoinColumns = #JoinColumn(name = "subject_id"))
private Set<SubjectModel> subjects;
//and my repository Class
#Repository
public interface SubjectDao extends JpaRepository<SubjectModel, Integer> {}

You have to delete the corresponding objects form both sides of the link, and then save them.
myStudent.getSubjects().remove(mySubject);
mySubject.getStudents().remove(myStudent);
SubjectDao subjectDao = new SubjectDao();
subjectDao.save(mySubject);
Here another examle: Hibernate: delete many-to-many association

You have two table Student and Subject.
And I suppose you want is delete one of the subject from a student.
For that you should let jpa delete the row from subject and student-subject association table. And dont need to user SubjectRepository.
Take a look.
Student firstStudent=studentRepository.findById(1);
Set<SubjectModel> subs=firstStudent.getSubject();
subs.clear();
firstStudent.setSubject(subs);
studentRepository.save(firstStudent); // this method will delete the row from assiciation table as well as the subject table.

Related

How to obtain the bi-directional entity

Let's say we have two entities, EntityA and EntityB, and those entities are bidirectional. How we should obtain entityB? Does it make sense to add a new method to the repository like findAllByEntityA() or we may use getEntitiesA() getter?
You could add a new spring repo method, or simply use a #Getter annotation, or implement your own getter method.
You could do findAllByEntityA() as a way of fetching EntityB if you expect to have a List<EntityB> returned. On the other hand, I would expect based on the naming convention that getEntitiesA() would be for fetching a List<EntityA> & not for B's.
It really depends on your bi-directional relationship but, basically all of the jpa one-to-one, one-to-many, many-to-one, many-to-many mappings simply boil down to foreign key constraints.
as turbofood already said. There are differet kinds of mappings:
One-to-One mappings. Like a Driver and a Car: One car can only be driven by one Driver and one Driver can only drive one Car.
Many-To-One mappings: Like a father and a child, one Father can have multiple children but one children can only have one father.
Many-To-Many mappings: Like student and teacher. A student can have multiple teachers and one teacher can have multiple students.
For One-To-One-Mappings and for Many-To-One mappings you have exactly one foreign-key. But for Many-To-Many-Mappings you have two foreign-keys that are part of a db-relation-table (that must not have a jpa-entity).
Using JPA/Hibernate we differenciate the endpoints between relations into two kinds: The owning-Side (getter and setter) and the non-owning side (getter and setter).
For many-to-many-relations this is the owning-side:
#OrderBy
#ManyToMany
#JoinTable(name="`STUDENT_TO_TEACHER`", joinColumns = {
#JoinColumn(name="`student_id`", referencedColumnName="`id`", nullable=false),
}, inverseJoinColumns = {
#JoinColumn(name="`teacher_id`", referencedColumnName="`id`", nullable=false)
})
public Set<Student> getStudents() {
return this.students;
}
And the non-owning-side:
#ManyToMany(mappedBy="students")
public Set<Teacher> getTeachers() {
return teachers;
}
Now why owning-side and non-owning side is important:
Student student = ...
Teacher teacher = ...
// a bad example:
student.getTeachers().add(teacher); // STUDENT.TEACHERS IS NOT THE OWNING SIDE
entityManager.persist(student); // THIS IS NOT POSSIBLE!
// a good example
teacher.getStudents().add(student); // good
entityManager.persist(teacher); // possible, teacher have a new student.
There is another rare case of mapping beside this 1-1/n-m/1-n sides the Map<> association. The Map<> association let you use a map as a getter in hibernate. I never used it so I can not elaborate experience in this.

Error on JPA many-to-many relationship between entity table and join table

I want a many-to-many relationship between an entity table and a join table. Invoices consist of a set of subscriptions. A subscription can be a part of multiple invoices (e.g. monthly subscription). Subscriptions consist of a user and a service. I am already using the subscriptions table as a join table for the many-to-many relationship between users and services (e.g. a user can subscribe to multiple services and a single service has multiple users). I have a table, "invoice_subscription," with IDs for both the invoice and subscription. Every table has its own ID. Below is the relevant snippet in the invoice object:
#ManyToMany
#JoinTable(
name = "invoice_subscription",
joinColumns = #JoinColumn(name = "invoice_id"),
inverseJoinColumns = #JoinColumn(name = "subscription_id")
)
public Set<Subscription> getSubscriptions() {
return subscriptions;
}
I am using the following in the subscription object:
#ManyToMany(mappedBy = "subscriptions")
public Set<Invoice> getInvoices() {
return invoices;
}
For the above, I am receiving the error below:
org.hibernate.MappingException: Foreign key (FKfe5yu82iapjynsdyfitclkri4:invoice_subscription [subscription_id])) must have same number of columns as the referenced primary key (subscription [user_id,service_id])
When I switch to two "#JoinColumn" annotations with the IDs in the error, I get the following error:
org.hibernate.AnnotationException: A Foreign key refering com.example.model.Subscription from com.example.model.Invoice has the wrong number of column. should be 1
I agree with this, but it seems to contradict the previous error. I've read documentation, guides, and other questions, but I have not found any useful information. I'm clearly missing something obvious.

Hibernate OnetoMany with Fetch Lazy giving LazyInitializationException

I am a newbie to Java Persistence API and Hibernate and using Spring JPA repositories for querying in DB. Now I have two entities in Parent <-> Child relationship with Parent entity with #OneToMany and Child entity with #ManyToOne mapping.
Parent Entity:-
#Entity
#Table(name = "PERSONS")
public class Persons {
...
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<Cards> cards = new ArrayList<Cards>();
...
}
Child Entity:-
#Entity
#Table(name = "CARDS")
public class Cards {
...
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", nullable = false, insertable = false, updatable = false)
public Person person;
...
}
And I am using my PersonsRepository like below :-
#Repository
public interface PersonsRepository extends JpaRepository<Persons, String> {
....
}
Now the fetchType being used in the relationship is LAZY at both the ends. Now whenever I tried to loop over a List and tried to process the cards for each using person.getCards(), it gives me below error:-
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.abc.Persons.cards, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
Now I have found everyone saying that using LAZY is the best approach in Hibernate and it says lot more about the correct design of code as well. I agree the way I have used person.getCards() will not have any open session and that is the reason it is giving me LazyInitializationException but the intent behind this is to save a lot more DB calls.
Assuming I have 1000 persons list, that means I have to make 1000 separate calls to getCards() for each person. That's why if I use the FETCHTYPE.EAGER in Person #OneToMany, what is the performance impact since everything will be fetched eagerly.
Need suggestions about the best practices followed for such kind of problems. TIA.
Edit:-
I have a method in service class where I am using #transactional for that like below:-
#Transactional(readOnly = true)
public void fetchData(Integer param1, Timestamp param2, Timestamp param3, List<String> param4, NavigableMap<Long, List<Cards>> param5) {
List<Persons> validPersons = personRepo.getCardsPerPerson(param2, param3);
if(validPersons != null && !validPersons.isEmpty()) {
// store the cards on the basis of epoch timestamp
prepareTimestampVsCardsMap(validPersons, param4, param5);
}
}
private void prepareTimestampVsCardsMap(List<Persons> validPersons, List<String> uList, NavigableMap<Long, List<Cards>> timestampVsCardsList) {
for(Person person : validPersons) {
Long epoch = order.getOrderTime().getTime();
Set<Cards> cardsPerPerson = person.getCards();
}
}
Also, the query being used in repository for getting the cards associated to a person is using join fetch as below:-
#Query(value = "select p from Person p join fetch Cards c on p.id = c.id WHERE p.orderTime BETWEEN ?1 AND ?2 ORDER BY orderTime ASC")
public List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
I am still getting the same above mentioned LazyInitializationException. Can anyone please help.
First of all, it's always better to use FetchType.LAZY instead of FetchType.EAGER. Why? Because you might not need all the data every time. If you want to return a list of Persons and display them somehow, somewhere, do you need to fetch all of their cards as well? If not, then FetchType.LAZY would be the better option, and you would then control how much data you need.
LazyInitializationException usually indicates that you didn't fetch all the data you need while your Session was opened. There are many ways to fetch associated data (none of which is keeping the Session opened while processing request):
1. using join fetch in your JPQL/HQL
#Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
2. if you're using Spring Data, you could use #EntityGraph instead of join fetch
#EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();
That way, every time you call getPersons, it will fetch cards as well. Of course, you couldn't use this one if you have to write #Query.
If you're using Spring Data's naming conventions for some simple queries, then #EntityGraph would be an option for fetching associations.
3. using Criteria API
Again, if you're using Spring Data, this is just a fallback solution in case you end up with MultipleBagFetchException. I will not go into details for this one, but in case you encounter this exception, you'll find solution in Vlad Mihalcea's blog post The best way to fix the Hibernate MultipleBagFetchException.
You are under the misconception that EAGER loading means Hibernate will fetch all data with one statement, this is false. With EAGER as a strategy, the framework will just do every query required to fetch all data for every entity.
Example: If one entity has 2 EAGER relationships, fetching one will result in 3 statements, one to load the entity, one for each of its relationships. If you have 3 entities, you will have 7 statements, the initial statement loading the 3 objects, plus 2 per object.
When your treatment requires everything, there is no real performance impact at the moment. But most applications are not made of one treatment. This means every treatment in your application will load everything which is EAGER, even if not needed. This will effectively slow everything down. You also risk loading all your database in memory if everything is in EAGER.
This is why LAZY is the recommended approach.
As for your LazyInitializationException, it seems in your stack trace that you are using the stream API. It's a wild guess due to missing details, but JPA/Hibernate doesn't handle sharing a session between threads, so if you are using parrallelStream it could cause the problem.

Is it possible to use one-to-many and many-to-one in the same entity?

I would like to modify the spring's rest tutorial. Link here
The tutorial has two entity: User and bookmark ( many bookmark can belong to one user. )
I would like to modify it a bit. I would like to create a user, question, answer entity - a user can have many questions, and a question can have many answers.
Is this possible?
How should the entity definition look like for the question entity?
The logic would be that a user could create quizzes. The quiz can contain questions, and those questions may have possible answers.
Any ideas how should the entities look like?
I would appreciate every idea.
Is it possible to use one-to-many and many-to-one in the same entity?
I assume your question is, can "questions entity" have one-to-many relationship with Answers entity and many-to-one relationship with User entity at the same time.
Yes, it is possible. Just, be careful while using annotation to map your entities each other, otherwise performance of your application will be seriously degraded. Use eager/Lazy fetch wisely. Print out the sql queries that spring-data-jpa/hibernate fires under the hood and analyze.
It is definitely possible.
User
#Entity
public class User {
// id and other attributes ommited
// User and Quiz has OneToMany bidirectional relationship. OP hasn't specified that but I think it makes more sense because a quiz most likely will need to know the user who created it.
#OneToMany (mappedBy="user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Quiz> quizes;
// ...
}
Quiz
#Entity
public class Quiz {
// id ommitted
#OneToMany
private List<Question> questions;
#ManyToOne
#JoinColumn(name = "user_id") //quiz table will have a column `user_id` foreign key referring to user table's `id` column
private User user;
// ...
}
Question
#Entity
public class Question {
// id ommitted
#OneToMany
#JoinColumn(name="question_id") // Question and Answer has one-to-many unidirectional relationship. `answer` table has a foreign key `question_id` referring to `question.id` column
private List<Answer> answers;
// ...
}
Answer
#Entity
public class Answer {
// ..more attributes
}
Note that:
the entity relationships also depend on your business logic.
If the owner of the bidirectional relationship is different, then your client code needs to adjust. jpa-joincolumn-vs-mappedby
If you want to design your table to be "clean" such that one entity table does not have a foreign key referring to another associated entity. You can create a join table, make the OneToMany relationship "feel" like a ManyToMany and use unique index to enforce the OneToMany. It is up to you. This wikibook page explains pretty well
This is absolutely not the only solution.

HQL JoinTable not accessible 2

I have a code like that
#ManyToMany(cascade = { CascadeType.ALL }) #JoinTable(name = "AssignedBook_Person", joinColumns = { #JoinColumn(name = "person_Id") }, inverseJoinColumns = { #JoinColumn(name = "book_Id") })
As you see my JoinTable name is AssignedBook_Person and i want to take some data from it with that code
List borrowedBook = session createQuery(" from AssignedBook_Person ")
.setCacheable(true).list();
But it gives this error "AssignedBook_Person is not mapped "
Does anybody have any idea how i can solve JoinTable problem.
Thanks
You cannot query directly database tables via HQL , because it does not operate to database tables - it operates to entities. That is main idea of object relational mapping.
Join table contains data about relation between Book and Person, so data can be fetched by selecting instances of one of those and then examining related entities.
If only raw data in join_table is needed, native query can be used:
session.createSQLQuery(SELECT person_id, book_id FROM AssignedBook_Person);

Resources