Hibernate #ManyToMany org.hibernate.LazyInitializationException - spring

I want to deal with common error, but I cannot find the right answer.
the error is:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: newsProject.post.entity.Tag.posts, could not initialize proxy - no Session
How to avoid use of #Transactional?
I am trying to do the following actions in my SpringBootTest:
#SpringBootTest
#ActiveProfiles("test")
class PostRepositoryTest {
#Autowired
private PostRepository postRepository;
#Autowired
private TagRepository tagRepository;
#AfterEach
void deleteFromDB(){
postRepository.deleteAll();
tagRepository.deleteAll();
}
#Test
void deletePostWithMultipleTags(){
Post post = new Post();
Tag tag = new Tag(1L,"tag1", null);
Tag tag2 = new Tag(2L,"tag2", null);
Tag tag3 = new Tag(3L,"tag3", null);
tagRepository.save(tag);
tagRepository.save(tag2);
tagRepository.save(tag3);
post.setTitle("testTitle");
Tag tagNew = tagRepository.findByTagName(tag.getTagName());
post.addTag(tagNew);
post.addTag(tagRepository.findByTagName(tag2.getTagName()));
post.addTag(tagRepository.findByTagName(tag3.getTagName()));
System.out.println(post);
postRepository.save(post);
}
}
The error is in this part:
public void addTag(Tag tag) {
tags.add(tag);
tag.getPosts().add(this);
}
I am open for any additional questions that related to this problem
I do not want to use following answers:
- Fetch.EAGER (or something related with turning down Fetch.LAZY)
- hibernate.enable_lazy_load_no_trans=True
The code:
Post.java
#Entity
#Data
#Table
#NoArgsConstructor
#AllArgsConstructor
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "post_generator")
#SequenceGenerator(name="post_generator", sequenceName = "seq_post", allocationSize = 1)
private Long id;
#Enumerated(EnumType.STRING)
private Status status;
//Some code
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(
name = "post_tag",
joinColumns = {#JoinColumn(name = "post_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
private List<Tag> tags = new ArrayList<>();
public void addTag(Tag tag) {
tags.add(tag);
tag.getPosts().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getPosts().remove(this);
}
}
Tag.java
#Entity
#Data
#Table
#NoArgsConstructor
#AllArgsConstructor
#ToString(exclude = "posts")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_generator")
#SequenceGenerator(name="id_generator", sequenceName = "seq_tag", allocationSize = 1)
private Long id;
private String tagName;
#ManyToMany(mappedBy="tags")
List<Post> posts = new ArrayList<>();
public void addPost(Post post) {
posts.add(post);
post.getTags().add(this);
}
public void removePost(Post post) {
posts.remove(post);
post.getTags().remove(this);
}
}

i tried your case and this is working for me. Please try :
#Test
#Transactional
public void test() {
Post post = new Post();
post.setTitle("post");
Tag tag1 = new Tag();
tag1.setTagName("tag1");
tag1.getPosts().add(post);
Tag tag2 = new Tag();
tag2.setTagName("tag2");
tag2.getPosts().add(post);
post.getTags().add(tag1);
post.getTags().add(tag2);
postRepository.save(post1);
}
We benefit of cascade saving here. Don't forget to add #Transactional to your method.

The answer is simple. We can avoid #Transactional 100%.
Add the following lines to the testing class:
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Rollback(false)
#ActiveProfiles("test")
class PostRepositoryTest {
as we are testing work of JPA.
Use in the repository interface extends JpaRepository<..., ...> instead of CrudRepository<..., ...>
in tagRepository add the line:
List<Tag> findByNameIn(List<String> names);
see documentation.
Now we can modify or test to this:
#Test
void deletePostWithMultipleTags(){
Tag tag = new Tag(1L,"tag1", null);
Tag tag2 = new Tag(2L,"tag2", null);
Tag tag3 = new Tag(3L,"tag3", null);
tagRepository.saveAll(Arrays.asList(tag, tag2, tag3));
tagRepository.flush();
Post post = new Post();
post.setTitle("testTitle");
List<Tag> tags = tagRepository.findByNameIn(Arrays.asList(tag.getName(), tag2.getName(), tag3.getName(),));
for (Tag tagItem : tags){
post.addTag(tahItem);
}
postRepository.saveAndFlush(post);
}
After all these actions you can avoid the use of #Transactional

The quick fix would be either:
Annotate your test method with #Transactional (which will rollback all changes and has maybe unwanted side-effects. see this link for more information)
Annotate your test method with #Rollback(false), which will span a transaction around your test and not rollback.

Related

Why is EntityGraph not working with DataJpaTest?

Hey today i found out that my Repository-Test runs perfectly fine when i use the #SpringBootTest-Annotation. But when i switch it to #DataJpaTest-Annotation, my #OneToMany-Annotated Collection of child elements is null.
Here an example:
ParentEntity.class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#NamedEntityGraph(name="parent.childs", attributeNodes = #NamedAttributeNode("childEntities"))
#Table(name = "parent")
public class ParentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Integer id;
#OneToMany(mappedBy = "parentId")
Collection<ChildEntity> childEntities;
}
ParentRepository.class
#Repository
public interface ParentRepository extends JpaRepository<ParentEntity, Integer> {
#EntityGraph(value = "parent.childs")
Optional<ParentEntity> findById(Integer id);
}
ChildEntity.class
#Data
#Entity
#Table(name = "child")
public class ChildEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "parentId", nullable = false, insertable = false, updatable = false)
private Integer parentId;
#ManyToOne#JoinColumn(name = "parentId", referencedColumnName = "id", nullable = false)
private ParentEntity parentEntity;
}
ChildRepository.class
#Repository
public interface ChildRepository extends JpaRepository<ChildEntity, Integer> {
}
And this is the Test:
#SpringBootTest
#AutoConfigureTestDatabase
public class RepoTest {
#Autowired
ParentRepository parentRepository;
#Autowired
ChildRepository childRepository;
#Commit
#Rollback(false)
#Test
void test(){
/* arrange */
ParentEntity parent = new ParentEntity();
var parentId = parentRepository.save(parent).id;
ChildEntity child = new ChildEntity();
child.setParentEntity(parent);
childRepository.save(child);
/* act */
/* Yes, i know there should be an exists()-check but lets keep it simple */
ParentEntity returnedParent = parentRepository.findById(parentId).get();
/* assert */
assertThat(returnedParent.getChildEntities()).hasSize(1);
}
}
This test works as expected.
But when i change the #SpringBootTest-Annotation to #DataJpaTest, the childEntities-Field of the ParentEntity.class stays null
I tried to delombok the code and find the cause by debugging each step of the query but i couldnt make it out right now. The resulting hibernate query contains the left outer join that i would expect. So my guess is that the error has to do with Data-Binding. Maby some type of (auto-)configuration is not loaded when i run the test with the other annotation.
I am very interested in the cause, so I would appreciate an explanation
After a lot of further research, i found the following helpful link:
Spring Data Jpa Test returns null list even after child is saved
There is explained what the cause of the problem is:
The parent
gets not loaded for the database but from the internal cache.
And to solve this problem:
You need to write a FlushAndClear method
#PersistenceContext
private EntityManager em;
private void flushAndClear() {
em.flush();
em.clear();
}

JPA: ManyToMany issue - Spring Boot Project

I want to establish many-to-many relation between two table. One is called User and the other is Tag.
My goal is to add previously created Tag lists to newly created User objects. Tag must be added to the database first, and then merged with User after selecting from existing ones.
How can I solve this problem? Where am I doing wrong? Any advice?
Thank you.
All my codes are as follows.
Filename = Demo2Application.java
#SpringBootApplication
public class Demo2Application implements CommandLineRunner {
#Autowired
private UserService userService;
#Autowired
private TagService tagService;
public static void main(String[] args) {
SpringApplication.run(Demo2Application.class, args);
}
#Override
public void run(String... args) throws Exception {
//Scenario: Tag list is already created.
Tag tag1 = new Tag("strong", true); //id = 1
Tag tag2 = new Tag("weak", true); //id = 2
Tag tag3 = new Tag("nice", true); //id = 3
Tag tag4 = new Tag("clever", true); //id = 4
tagService.save(tag1);
tagService.save(tag2);
tagService.save(tag3);
tagService.save(tag4);
//Scenario: Defining a new user.
User user1 = new User("foo", "foo#gmail.com");
//Scenario: Appropriate predefined tags are being added to the new user object.
user1.addTag(tagService.findById(1));
user1.addTag(tagService.findById(4));
//Scenario: Registering a User object to the database.
userService.save(user1);
}
}
Filename = entity/Model.java
#MappedSuperclass
public class Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#CreationTimestamp
private Date createdAt;
#UpdateTimestamp
private Date updatedAt;
// standard constructors, getters, and setters
}
Filename = entity/User.java
#Entity
#Table(name = "user")
public class User extends Model {
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
private List<Tag> tags = new ArrayList<>();
//
public void addTag(Tag tag) {
tags.add(tag);
}
// standard constructors, getters, and setters
Filename = entity/Tag.java
#Entity
#Table(name = "tag")
public class Tag extends Model {
#Column(name = "tag_name")
private String tagName;
#Column(name = "tag_active")
private boolean tagActive;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_tag",
joinColumns = {#JoinColumn(name = "tag_id")},
inverseJoinColumns = {#JoinColumn(name = "user_id")})
private List<User> users = new ArrayList<>();
// standard constructors, getters, and setters
Repositories = TagRepository, UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
}
public interface TagRepository extends JpaRepository<Tag, Long> {
}
Filename = service/UserService.java
#Service
public class UserService {
private UserRepository userRepository;
#Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findById(long id) {
Optional<User> result = userRepository.findById(id);
return result.orElse(null);
}
public void save(User user) {
userRepository.save(user);
}
}
Filename = service/TagService.java
#Service
public class TagService {
private TagRepository tagRepository;
#Autowired
public TagService(TagRepository tagRepository) {
this.tagRepository = tagRepository;
}
public Tag findById(long id) {
Optional<Tag> result = tagRepository.findById(id);
return result.orElse(null);
}
public void save(Tag tag) {
tagRepository.save(tag);
}
}
Filename = resources/application.properties
server.port=7070
## Database (PostgreSQL + Hikari + JPA)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.maximumPoolSize=5
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
#
spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.username=admin
spring.datasource.password=1234
#
spring.jpa.hibernate.ddl-auto=create-drop
## Database ##

Spring JPA - deleting child element doesn't reflect in database table

I'm having problems deleting the child element of a one-to-many relationship entity.
Here's a code snippet:
#Override
#Transactional
public void deleteTask(UUID listId, UUID taskId) {
TaskList list = repo.findOne(listId);
System.out.println("Old List: " + list);
for(Task t : list.getTasks()) {
if(t.getId().toString().equals(taskId.toString())) {
System.out.println(list.getTasks().remove(t));
System.out.println("Task with id " + taskId + " deleted.");
}
}
System.out.println("New List: " + repo.save(list));
}
The Task class is this:
#Entity(name = "task")
public class Task implements Serializable {
// Id and 3 fields
#ManyToOne
#JoinColumn(name="tasklist_id")
private TaskList parentList;
// 3 more fields
// Constructor
public Task() {}
//Getters and Setters
}
and the TaskList class is this:
#Entity(name = "task_list")
public class TaskList implements Serializable {
// Id and two fields
#OneToMany(mappedBy="parentList", cascade={CascadeType.ALL})
private List<Task> tasks;
// Constructor
public TaskList() {}
}
The Task entity is the child, and even though the save() function returns the truncated TaskList, I can't get the changes to show in a separate query to the database. The number of tasks remains the same. However, deleting a list through repo.delete(listId) works fine with both the list and its tasks gone.
Here, repo is a repository corresponding to the parent TaskList class. All operations to the child Task class happen through a #OneToMany({cascade=CascadeType.ALL}) relation.
For some reason, searching for all TaskLists using repo.findAll() also returns faulty results.
I'm obviously doing something fundamentally wrong. Please tell me what to do.
You need to add orphanRemoval = true to your mapping:
#OneToMany(mappedBy="parentList", cascade={CascadeType.ALL}, orphanRemoval=true)
list.getTasks().remove(t) just removes entity from the collection, so you need to tell JPA to remove it also from DB. This is done by the orphanRemoval attribute.
Here's my task list
#Entity(name = "TASK_LIST")
public class TaskList {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME")
private String name;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "task", referencedColumnName = "id", nullable = false)
private List<Task> tasks = new ArrayList<Task>();
Here's my repository
#Repository
public interface TaskListRepository extends JpaRepository<TaskList, Long> {
}
Here's my test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = "classpath:applicationContext-test.xml")
public class TaskListRepositoryTest {
#Autowired
private TaskListRepository repository;
#Autowired
private TaskService service;
#Test
public void test1() throws SQLException {
TaskList taskList = new TaskList();
taskList.getTasks().add(makeTask("name1", "description1"));
taskList.getTasks().add(makeTask("name2", "description2"));
taskList.getTasks().add(makeTask("name3", "description3"));
taskList.getTasks().add(makeTask("name4", "description4"));
taskList.getTasks().add(makeTask("name5", "description5"));
service.save(taskList);
TaskList findOne = repository.findOne(1l);
assertEquals(5, findOne.getTasks().size());
taskList.getTasks().remove(2);
service.save(taskList);
findOne = repository.findOne(1l);
assertEquals(4, findOne.getTasks().size());
}
#Test
public void test2() throws SQLException {
TaskList findOne = repository.findOne(1l);
assertEquals(4, findOne.getTasks().size());
}
private Task makeTask(String name, String description) {
Task task = new Task();
task.setName(name);
task.setDescription(description);
return task;
}

Hibernate Inheritance strategy=InheritanceType.JOINED & onetoMany with spring-data-jpa

For Some reason, I am not able to get the combination of Hibernate Inheritance strategy=InheritanceType.JOINED & onetoMany working. Following are the entities.
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="OBJECT_TYPE")
public abstract class ExamObject {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "examid", nullable = false)
private Exam exam;
}
#Entity
#DiscriminatorValue("Q")
public class ExamQuestion extends ExamObject{
private Integer questionNumber;
private String questionDesc;
}
#Entity
public class Exam {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer examid;
private String examName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set<ExamObject> object
}
My Spring Boot start up class
#SpringBootApplication
public class ExamApp implements CommandLineRunner {
#Autowired
private ExamQuestionRepository examQuestionRepository;
#Autowired
private ExamRepository examRepository;
public static void main(String[] args) {
SpringApplication.run(ExamApp.class, args);
}
#Override
#Transactional
public void run(String... arg0) throws Exception {
Exam exam = new Exam();
exam.setExamName("Exam1");
examRepository.save(exam);
String[] questions = new String[]{"Question1,Question2"};
ArrayList<ExamQuestion> examQuestions = new ArrayList<ExamQuestion();
int index = 0;
for(String questionNoDesc: questions){
index++;
ExamQuestion examQuestion = new ExamQuestion();
examQuestion.setQuestionDesc(questionNoDesc);
examQuestion.setQuestionNumber(index);
examQuestion.setExam(exam);
examQuestions.add(examQuestion);
}
examQuestionRepository.save(examQuestions);
Iterable<Exam> examGet = examRepository.findAll();
for (Exam exam2: examGet) {
System.out.println("Exam question is .. " +exam2.getObjects());
}
}
}
The problems is that whenever I print "Exam question is .. "+exam2.getObjects(), I always get null. How can I get this to work ?
As explained in the comment in the original question, the problem is that the object graph is not being maintained properly. One extra line of code to the following function fixed the issue. exam.setObjects(examQuestions);has been added
#Override
#Transactional
public void run(String... arg0) throws Exception {
Exam exam = new Exam();
exam.setExamName("Exam1");
examRepository.save(exam);
String[] questions = new String[]{"Question1,Question2"};
ArrayList<ExamQuestion> examQuestions = new ArrayList<ExamQuestion();
int index = 0;
for(String questionNoDesc: questions){
index++;
ExamQuestion examQuestion = new ExamQuestion();
examQuestion.setQuestionDesc(questionNoDesc);
examQuestion.setQuestionNumber(index);
examQuestion.setExam(exam);
examQuestions.add(examQuestion);
}
examQuestionRepository.save(examQuestions);
exam.setObjects(examQuestions);
Iterable<Exam> examGet = examRepository.findAll();
for (Exam exam2: examGet) {
System.out.println("Exam question is .. " +exam2.getObjects());
}
}
May be the issue is
#OneToMany(fetch = FetchType.LAZY, mappedBy = "exam")
private Set object
When you fetch any thing by LAZY loading FetchType.LAZY. This will get all the object from teh parent table i.e Exam here but will not query the child/dependent tables for the data.
e.g Here it will not hit the ExamObject to get its data, it just replaces this by the proxy object, Thus if you query this object then you get null as the result.
Try your query with FetchType.EAGER

How to delete entity from database

i am new on hibernate-spring tirple..
i just try to code simple register book.. i have following codes:
Student.java
#Entity(name = "STUDENTS")
#NamedQueries({
#NamedQuery(name = "getAllStudent", query = "SELECT k FROM STUDENTS k ORDER BY k.id DESC"),
#NamedQuery(name = "findByName", query = "SELECT k FROM STUDENTS k WHERE k.name LIKE :name")
})
public class Student {
#Column(name = "STUDENTNO", nullable = false)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "SURNAME")
private String surname;
#Column(name = "AGE")
private String age;
// GET ve SET metods
StduentDAO.java
#Repository
#Transactional(readOnly = true)
public class StudentDAO implements IStudentDAO {
#PersistenceContext
EntityManager em;
#Override
public void deleteStudent(Student student) {
Student temp = em.getReference(Student.class, student.getId());
em.remove(temp);
System.out.println("### getting out from studentDAO deleteStudent method ###")
StudentController.java
#Component
#Scope(value = "request")
public class StudentController {
#Autowired
IStudentDAO studentDAO;
List<Student> allStudentList = new ArrayList();
Student student = new Student();
#PostConstruct
private void loadStudents() {
allStudentList = studentDAO.allStudent();
public void deleteStudent() {
studentDAO.deleteStudent(student);
System.out.println("### getting out from StudentController deleteStudent method ### ");
}
When I run deleteStudent() codes i am getting:
"### getting out from studentDAO deleteStudent method ###"
"### getting out from StudentController deleteStudent method ### "
i see these on output but nothing is deleting from database.. i searched a bit and i found this "every entitiy manager's methods open own session." that is why it says i should write my StudentDAO's deleteStudent methof like above..
i think i am missing something about transaciton but i have not recognized yet..
what should i do about this ?
Thanks..
#Transactional annotation create a transaction on your DBMS.
If you use (readOnly = true) you prevent operation on your DB (as INSERT/UPDATE/DELETE).
Remove readOnly = true so your delete method will work.

Resources