1

Entity Survey:

    @Entity
    @Table(name = "Survey")
    public class Survey implements Serializable {

        private Long id;
        private String name;
        private String address;
        private List<Question> questions;

        @Id
        @Column(name = "id")
        public Long getId() {
            return id;
        }

        @Column(name = "name")
        public String getName() {
            return name;
        }

        @Column(name = "address")
        public String getAddress() {
            return address;
        }

        @OneToMany(fetch = FetchType.LAZY, mappedBy = "survey")
        public List<Question> getQuestions() {
            return questions;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public void setName(final String name) {
            this.name = name;
        }

        public void setAddress(final String address) {
            this.address = address;
        }

        public void setQuestions(List<Question> _questions) {
            this.questions = _questions;
        }
    }

Question Entity:

@Entity
@Table(name = "Question")
public class Question {

    private Long id;
    private String question;
    private Survey survey;

    @Id
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    @Column(name = "question")
    public String getQuestion() {
        return question;
    }

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "survey_id")
    public Survey getSurvey() {
        return survey;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public void setQuestion(String question) {
        this.question = question;
    }

    public void setSurvey(Survey survey) {
        this.survey = survey;
    }
}

Question Crud Repo:

 public interface QuestionRepo extends CrudRepository<Question, Long> {
    }

Controller:

@RestController
@RequestMapping("/default")
public class DefaultEndpoint {

    @Autowired
    private QuestionRepo questionRepo;


    @PostMapping(value = "/saveQuestion")
    public void saveQuestion(@Param("id") Long id, @Param("question") String question, @Param("survey_id") Long survey_id) {
        Question questionObject = new Question();
        questionObject.setId(id);
        questionObject.setQuestion(question);
        Survey survey = surveyRepo.findById(survey_id).get();
        survey.setName("example");
        questionObject.setSurvey(survey);
        questionRepo.save(questionObject);
    }
}

In the Controller snippet, when I do survey.setName("example");. this change is reflected on the database.

It means that save() operation, which is implemented with EntityManager methods MERGE and PERIST, cascades to the child entity even if no Cascade type is specified.
Is this how it is supposed to work, or am I doing some stupid mistake ?
Thanks a lot !

Stefan Drissen
  • 3,266
  • 1
  • 13
  • 21
GionJh
  • 2,742
  • 2
  • 29
  • 68

2 Answers2

1

Please check @ManyToOne annotation to see that the default cascade is none.

public @interface ManyToOne {
   /**
     * (Optional) The operations that must be cascaded to 
     * the target of the association.
     *
     * <p> By default no operations are cascaded.
     */
    CascadeType[] cascade() default {};
}

There is no cascading taking place.

Most likely, the effect you are observing is a combination of 2 features:

If you are running a web application, Spring Boot by default registers OpenEntityManagerInViewInterceptor to apply the “Open EntityManager in View” pattern, to allow for lazy loading in web views. If you do not want this behavior, you should set spring.jpa.open-in-view to false in your application.properties.

Dirty Checking is quite well known, but Open Session In View tends to be a surprise for some devs (For example, see another related effect in Spring - Same entity detached on the @EventListener but attached in the @Service class)

Lesiak
  • 22,088
  • 2
  • 41
  • 65
  • This is it ! but..I did not know about this feature, is it like having a @Transactional annotation in the controller method ? – GionJh Mar 13 '20 at 19:54
  • Open Session in View vs @Transactional : https://stackoverflow.com/questions/16465269/open-session-in-view-vs-transactional – Lesiak Mar 13 '20 at 20:03
  • I this did not get why this happens: with spring.jpa.open-in-view enabled if I execute survey.setName("example") after the save, the change is not persisted..so I guess it's not really like having a @transactional annotation ? – GionJh Mar 13 '20 at 20:12
  • 1
    As mentioned in the link above, the filter does not flush the session – Lesiak Mar 13 '20 at 20:14
  • ohh yeah ok, so it's the save that flushes it, so the behaviour is similar to @transactional, One last question, thanks for you help!, this feature only happens in Controller/RestControllers and not in Services ? – GionJh Mar 13 '20 at 20:15
  • @GionJh 1. Yep, you were piggybacking on the save that flushed the session. 2. Not triggered in services, but if you use a service in a controller, you’ll still experience it – Lesiak Mar 13 '20 at 20:20
0

It's the dirty checking mechanism. E.g when you flush persistence context or commit transaction Hibernate checks the context for the dirty state of entities and executes SQL update to synchronize in-memory with the database state. You can check this post to get more detail

Anthony
  • 571
  • 3
  • 11
  • 1
    Please note that I'm not using @Transactional so Survey instance is NOT managed when I call setName – GionJh Mar 13 '20 at 19:19