Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Duplicate entry by Hibernate on Join Table in SpringMVC

I've spent forever trying to understand why this is happening but I just can't seem to get it. I basically have three classes/entities. The top level entities have a OneToMany List of the second level entities, and the second level entities have a OneToMany List of the third level. Now, when 2 of more of the third level are added to any one of the second level, then on trying to add a new second level to the top level, I get a constraint violation on the join table of the top and second level entities. I've had hibernate show the sql and it seems it's trying to add the second level element with the two third level elements to the join table of the top and second level entities twice. Hence the violation. But why is it trying to do this? I just can't seem to understand why. I think it may have something to do with springs transaction management, as when I do the same thing outside of spring, just in a main method, I can't reproduce the behaviour.

Top level.

@Entity
public class ReviewSubject {

    @Id @GeneratedValue
    private long id;


    public long getId() {
        return id;
    }

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

    private String subject;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Review> reviews = new ArrayList<Review>();

    public ReviewSubject(){};

    public ReviewSubject(String subject) {
        this.subject = subject;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public List<Review> getReviews() {
        return reviews;
    }

    public void setReviews(List<Review> reviews) {
        this.reviews = reviews;
    }

    public void addReview(Review review) {
        getReviews().add(review);
    }

}

Second level

@Entity
public class Review {

    @Id @GeneratedValue
    private long id;

    private String text;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Comment> comments = new ArrayList<Comment>();

    public Review(){};

    Review(String text) {
        this.text = text;
    }

    public long getId() {
        return id;
    }

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

    public List<Comment> getComments() {
        return comments;
    }

    public void setComments(List<Comment> comments) {
        this.comments = comments;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

Third level.

@Entity
public class Comment {

    @Id @GeneratedValue
    private long id;

    private String text;

    public Comment(){};

    public Comment(long reviewId, String text) {

    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

}

Top level DAO

@Repository
public class ReviewSubjectDAOImpl implements ReviewSubjectDAO {

    private SessionFactory sessionFactory;


    @Autowired
    public ReviewSubjectDAOImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    public void saveReviewSubject(ReviewSubject reviewSubject) {
        currentSession().save(reviewSubject);

    }

    public ReviewSubject getReviewSubject(long id) {
        return (ReviewSubject) currentSession().get(ReviewSubject.class, id);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<ReviewSubject> getReviewSubjects() {
        return (List) currentSession().createQuery("from ReviewSubject").list();
    }



}

Second level DAO

@Repository
public class ReviewDAOImpl implements ReviewDAO {

    private SessionFactory sessionFactory;

    @Autowired
    public ReviewDAOImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }

    public Review getReview(long id) {
        return (Review) currentSession().get(Review.class, id);
    }

    public void saveReview(Review review) {
        currentSession().save(review);
    }

}

Third level DAO

@Repository
public class CommentDAOImpl implements CommentDAO{

    private SessionFactory sessionFactory;

    @Autowired
    public CommentDAOImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    private Session currentSession() {
        return sessionFactory.getCurrentSession();
    }
    public void saveComment(Comment comment) {
        currentSession().save(comment);
    }
}

Service layer

@Service
@Transactional
public class Test2ServiceImpl implements Test2Service {

    private ReviewDAO reviewDAO;

    private ReviewSubjectDAO reviewSubjectDAO;

    private CommentDAO commentDAO;

    @Autowired
    public Test2ServiceImpl(ReviewDAO reviewDAO, ReviewSubjectDAO reviewSubjectDAO, CommentDAO commentDAO) {
        this.reviewDAO = reviewDAO;
        this.reviewSubjectDAO = reviewSubjectDAO;
        this.commentDAO = commentDAO;
    }

    public void addReviewSubject(ReviewSubject reviewSubject) {
        reviewSubjectDAO.saveReviewSubject(reviewSubject);

    }

    public ReviewSubject getReviewSubject(long id) {
        return reviewSubjectDAO.getReviewSubject(id);
    }

    public void addReview(Review review, long reviewSubjectId) {
        ReviewSubject reviewSubject = reviewSubjectDAO.getReviewSubject(reviewSubjectId);
        reviewDAO.saveReview(review);
        reviewSubject.getReviews().add(review);
    }

    public void addComment(Comment comment, long id) {
        Review review = reviewDAO.getReview(id);
        commentDAO.saveComment(comment);
        review.getComments().add(comment);

    }

    public List<ReviewSubject> getReviewSubjects() {
        return reviewSubjectDAO.getReviewSubjects();
    }

}

Controller

@Controller
public class HomeController {

    private Test2Service test2Service;

    @Autowired
    public HomeController(Test2Service test2Service) {
        this.test2Service = test2Service;
    }

    @RequestMapping({"/"})
    public String showHomePage(Model model) {
        model.addAttribute(test2Service.getReviewSubjects());
        List<ReviewSubject> rs = (List) test2Service.getReviewSubjects();   
        return "home";
    }

    @RequestMapping({"/addReviewSubject"})
    public String addReviewSubject(Model model) {
        model.addAttribute(new ReviewSubject());
        return "addReviewSubject";
    }

    @RequestMapping(method=RequestMethod.POST, value="/addReviewSubject")
    public String addReviewSubjectFromForm(ReviewSubject reviewSubject) {
        test2Service.addReviewSubject(reviewSubject);
        return "redirect:/";
    }

    @RequestMapping({"/addReview/{id}"}) 
    public String addReview(Model model, @PathVariable long id) {
        model.addAttribute(id);
        model.addAttribute(new Review());
        return "addReview";
    }

    @RequestMapping(method=RequestMethod.POST, value ={"/addReview/{id}"}) 
    public String addReviewFromForm(Review review, @RequestParam("id") long id) {
        test2Service.addReview(review, id);
        return "redirect:/";
    }

    @RequestMapping({"/addComment/{id}"})
    public String addComment(Model model, @PathVariable long id) {
        model.addAttribute(id);
        model.addAttribute(new Comment());
        return "addComment";
    }

    @RequestMapping(method=RequestMethod.POST, value={"/addComment/{id}"})
    public String addCommentFromForm(Comment comment, @RequestParam("id") long id) {
        test2Service.addComment(comment, id);
        return "redirect:/";

    }

}

The view layer is just basic JSP with forms. Configuration files are just standard stuff, will post if requested.

Error.

01-May-2014 16:05:44 org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
WARN: SQL Error: 1062, SQLState: 23000
01-May-2014 16:05:44 org.hibernate.engine.jdbc.spi.SqlExceptionHelper logExceptions
ERROR: Duplicate entry '1' for key 'UK_rk0ljdj0mc3adaygir8wu9g9r'
01-May-2014 16:05:44 org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl release
INFO: HHH000010: On release of batch it still contained JDBC statements
01-May-2014 16:05:44 org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [test2] in context with path [/test2] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'UK_rk0ljdj0mc3adaygir8wu9g9r'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)

Here's the debugging readout from when the insert of the second review object

976321 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'test2' processing GET request for [/test2/addReview/1]
976321 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /addReview/1
976322 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Returning handler method [public java.lang.String resources.HomeController.addReview(org.springframework.ui.Model,long)]
976322 [http-bio-8080-exec-4] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'homeController'
976322 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet  - Last-Modified value for [/test2/addReview/1] is: -1
976323 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet  - Rendering view [org.springframework.web.servlet.view.JstlView: name 'addReview'; URL [/WEB-INF/Views/addReview.jsp]] in DispatcherServlet with name 'test2'
976323 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.view.JstlView  - Added model object 'id' of type [java.lang.Long] to request in view with name 'addReview'
976323 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.view.JstlView  - Added model object 'long' of type [java.lang.Long] to request in view with name 'addReview'
976324 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.view.JstlView  - Added model object 'review' of type [resources.Review] to request in view with name 'addReview'
976324 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.view.JstlView  - Added model object 'org.springframework.validation.BindingResult.review' of type [org.springframework.validation.BeanPropertyBindingResult] to request in view with name 'addReview'
976324 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.view.JstlView  - Forwarding to resource [/WEB-INF/Views/addReview.jsp] in InternalResourceView 'addReview'
976328 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet  - Successfully completed request
980068 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.DispatcherServlet  - DispatcherServlet with name 'test2' processing POST request for [/test2/addReview/1]
980068 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Looking up handler method for path /addReview/1
980070 [http-bio-8080-exec-4] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping  - Returning handler method [public java.lang.String resources.HomeController.addReviewFromForm(resources.Review,long)]
980070 [http-bio-8080-exec-4] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'homeController'
980071 [http-bio-8080-exec-4] WARN  org.springframework.validation.DataBinder  - Skipping URI variable 'id' since the request contains a bind value with the same name.
980072 [http-bio-8080-exec-4] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory  - Returning cached instance of singleton bean 'transactionManager'
980072 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Creating new transaction with name [resources.Test2ServiceImpl.addReview]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
980073 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Opened new Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@194dec09 updates=org.hibernate.engine.spi.ExecutableList@4ac34fd9 deletions=org.hibernate.engine.spi.ExecutableList@5caf55e7 orphanRemovals=org.hibernate.engine.spi.ExecutableList@7b30e03a collectionCreations=org.hibernate.engine.spi.ExecutableList@45d13f05 collectionRemovals=org.hibernate.engine.spi.ExecutableList@2c808512 collectionUpdates=org.hibernate.engine.spi.ExecutableList@29a07791 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@6609e5f0 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])] for Hibernate transaction
980073 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Preparing JDBC Connection of Hibernate Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@194dec09 updates=org.hibernate.engine.spi.ExecutableList@4ac34fd9 deletions=org.hibernate.engine.spi.ExecutableList@5caf55e7 orphanRemovals=org.hibernate.engine.spi.ExecutableList@7b30e03a collectionCreations=org.hibernate.engine.spi.ExecutableList@45d13f05 collectionRemovals=org.hibernate.engine.spi.ExecutableList@2c808512 collectionUpdates=org.hibernate.engine.spi.ExecutableList@29a07791 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@6609e5f0 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
980073 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl  - Obtaining JDBC connection
980073 [http-bio-8080-exec-4] DEBUG org.springframework.jdbc.datasource.DriverManagerDataSource  - Creating new JDBC DriverManager Connection to [jdbc:mysql://localhost:3306/test2]
980080 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.jdbc.internal.LogicalConnectionImpl  - Obtained JDBC connection
980080 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl  - begin
980080 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction  - initial autocommit status: true
980081 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction  - disabling autocommit
980081 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Exposing Hibernate transaction as JDBC transaction [com.mysql.jdbc.JDBC4Connection@5db2381b]
980082 [http-bio-8080-exec-4] DEBUG org.hibernate.SQL  - 
    select
        reviewsubj0_.id as id1_2_0_,
        reviewsubj0_.subject as subject2_2_0_,
        reviews1_.ReviewSubject_id as ReviewSu1_2_1_,
        review2_.id as reviews_2_3_1_,
        review2_.id as id1_1_2_,
        review2_.text as text2_1_2_,
        comments3_.Review_id as Review_i1_1_3_,
        comment4_.id as comments2_4_3_,
        comment4_.id as id1_0_4_,
        comment4_.text as text2_0_4_ 
    from
        ReviewSubject reviewsubj0_ 
    left outer join
        ReviewSubject_Review reviews1_ 
            on reviewsubj0_.id=reviews1_.ReviewSubject_id 
    left outer join
        Review review2_ 
            on reviews1_.reviews_id=review2_.id 
    left outer join
        Review_Comment comments3_ 
            on review2_.id=comments3_.Review_id 
    left outer join
        Comment comment4_ 
            on comments3_.comments_id=comment4_.id 
    where
        reviewsubj0_.id=?
Hibernate: 
    select
        reviewsubj0_.id as id1_2_0_,
        reviewsubj0_.subject as subject2_2_0_,
        reviews1_.ReviewSubject_id as ReviewSu1_2_1_,
        review2_.id as reviews_2_3_1_,
        review2_.id as id1_1_2_,
        review2_.text as text2_1_2_,
        comments3_.Review_id as Review_i1_1_3_,
        comment4_.id as comments2_4_3_,
        comment4_.id as id1_0_4_,
        comment4_.text as text2_0_4_ 
    from
        ReviewSubject reviewsubj0_ 
    left outer join
        ReviewSubject_Review reviews1_ 
            on reviewsubj0_.id=reviews1_.ReviewSubject_id 
    left outer join
        Review review2_ 
            on reviews1_.reviews_id=review2_.id 
    left outer join
        Review_Comment comments3_ 
            on review2_.id=comments3_.Review_id 
    left outer join
        Comment comment4_ 
            on comments3_.comments_id=comment4_.id 
    where
        reviewsubj0_.id=?
980084 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl  - Starting ResultSet row #0
980085 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl  - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
980085 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl  - Found row of collection: [resources.Review.comments#1]
980086 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl  - Found row of collection: [resources.ReviewSubject.reviews#1]
980086 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.ResultSetProcessorImpl  - Starting ResultSet row #1
980086 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl  - On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; should only happen on root returns with an optional identifier specified
980086 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl  - Found row of collection: [resources.Review.comments#1]
980087 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl  - Found row of collection: [resources.ReviewSubject.reviews#1]
980087 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Resolving associations for [resources.ReviewSubject#1]
980087 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Done materializing entity [resources.ReviewSubject#1]
980087 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Resolving associations for [resources.Review#1]
980087 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Done materializing entity [resources.Review#1]
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Resolving associations for [resources.Comment#1]
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Done materializing entity [resources.Comment#1]
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Resolving associations for [resources.Comment#2]
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.TwoPhaseLoad  - Done materializing entity [resources.Comment#2]
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - 1 collections were found in result set for role: resources.Review.comments
980088 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - Collection fully initialized: [resources.Review.comments#1]
980091 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - 1 collections initialized for role: resources.Review.comments
980091 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - 1 collections were found in result set for role: resources.ReviewSubject.reviews
980091 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - Collection fully initialized: [resources.ReviewSubject.reviews#1]
980091 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext  - 1 collections initialized for role: resources.ReviewSubject.reviews
980092 [http-bio-8080-exec-4] DEBUG org.hibernate.loader.entity.plan.AbstractLoadPlanBasedEntityLoader  - Done entity load : resources.ReviewSubject#1
980093 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.spi.ActionQueue  - Executing identity-insert immediately
980093 [http-bio-8080-exec-4] DEBUG org.hibernate.SQL  - 
    insert 
    into
        Review
        (text) 
    values
        (?)
Hibernate: 
    insert 
    into
        Review
        (text) 
    values
        (?)
980104 [http-bio-8080-exec-4] DEBUG org.hibernate.id.IdentifierGeneratorHelper  - Natively generated identity: 2
980106 [http-bio-8080-exec-4] DEBUG resources.Test2ServiceImpl  - Review Id for review 2 = 2
980106 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Initiating transaction commit
980106 [http-bio-8080-exec-4] DEBUG org.springframework.orm.hibernate4.HibernateTransactionManager  - Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[resources.Review#2], EntityKey[resources.Comment#2], EntityKey[resources.Comment#1], EntityKey[resources.Review#1], EntityKey[resources.ReviewSubject#1]],collectionKeys=[CollectionKey[resources.Review.comments#1], CollectionKey[resources.ReviewSubject.reviews#1]]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@194dec09 updates=org.hibernate.engine.spi.ExecutableList@4ac34fd9 deletions=org.hibernate.engine.spi.ExecutableList@5caf55e7 orphanRemovals=org.hibernate.engine.spi.ExecutableList@7b30e03a collectionCreations=org.hibernate.engine.spi.ExecutableList@45d13f05 collectionRemovals=org.hibernate.engine.spi.ExecutableList@2c808512 collectionUpdates=org.hibernate.engine.spi.ExecutableList@29a07791 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@6609e5f0 unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
980106 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.transaction.spi.AbstractTransactionImpl  - committing
980107 [http-bio-8080-exec-4] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener  - Processing flush-time cascades
980107 [http-bio-8080-exec-4] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener  - Dirty checking collections
980107 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.spi.CollectionEntry  - Collection dirty: [resources.ReviewSubject.reviews#1]
980108 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.Collections  - Collection found: [resources.ReviewSubject.reviews#1], was: [resources.ReviewSubject.reviews#1] (initialized)
980108 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.Collections  - Collection found: [resources.Review.comments#1], was: [resources.Review.comments#1] (initialized)
980108 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.internal.Collections  - Collection found: [resources.Review.comments#2], was: [<unreferenced>] (initialized)
980109 [http-bio-8080-exec-4] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener  - Flushed: 0 insertions, 0 updates, 0 deletions to 5 objects
980109 [http-bio-8080-exec-4] DEBUG org.hibernate.event.internal.AbstractFlushingEventListener  - Flushed: 1 (re)creations, 1 updates, 0 removals to 3 collections
980109 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - Listing entities:
980113 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - resources.Review{id=2, text=review 2, comments=[]}
980113 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - resources.Comment{id=2, text=comment 2}
980113 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - resources.Comment{id=1, text=comment 1}
980113 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - resources.Review{id=1, text=review 1, comments=[resources.Comment#1, resources.Comment#2]}
980113 [http-bio-8080-exec-4] DEBUG org.hibernate.internal.util.EntityPrinter  - resources.ReviewSubject{id=1, reviews=[resources.Review#1, resources.Review#1, resources.Review#2], subject=review subject 1}
980114 [http-bio-8080-exec-4] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister  - Deleting collection: [resources.ReviewSubject.reviews#1]
980114 [http-bio-8080-exec-4] DEBUG org.hibernate.SQL  - 
    delete 
    from
        ReviewSubject_Review 
    where
        ReviewSubject_id=?
Hibernate: 
    delete 
    from
        ReviewSubject_Review 
    where
        ReviewSubject_id=?
980115 [http-bio-8080-exec-4] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister  - Done deleting collection
980115 [http-bio-8080-exec-4] DEBUG org.hibernate.persister.collection.AbstractCollectionPersister  - Inserting collection: [resources.ReviewSubject.reviews#1]
980116 [http-bio-8080-exec-4] DEBUG org.hibernate.SQL  - 
    insert 
    into
        ReviewSubject_Review
        (ReviewSubject_id, reviews_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        ReviewSubject_Review
        (ReviewSubject_id, reviews_id) 
    values
        (?, ?)
980117 [http-bio-8080-exec-4] DEBUG org.hibernate.SQL  - 
    insert 
    into
        ReviewSubject_Review
        (ReviewSubject_id, reviews_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        ReviewSubject_Review
        (ReviewSubject_id, reviews_id) 
    values
        (?, ?)
980125 [http-bio-8080-exec-4] DEBUG org.hibernate.engine.jdbc.spi.SqlExceptionHelper  - could not execute statement [n/a]
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'UK_rk0ljdj0mc3adaygir8wu9g9r'
like image 724
AlyoshaKaramazov Avatar asked Dec 09 '25 03:12

AlyoshaKaramazov


2 Answers

Finally solved this.

It turns out It was reproducible outside of spring and so wasn't a problem with the transaction manager.

What Hibernate was doing if you look at the debugging logs is, firstly, creating a Cartesian Product of successive left outer joins from each Entity table and Join Table, like this

select
        reviewsubj0_.id as id1_2_0_,
        reviewsubj0_.subject as subject2_2_0_,
        reviews1_.ReviewSubject_id as ReviewSu1_2_1_,
        review2_.id as reviews_2_3_1_,
        reviews1_.reviews_ORDER as reviews_3_1_,
        review2_.id as id1_1_2_,
        review2_.text as text2_1_2_,
        comments3_.Review_id as Review_i1_1_3_,
        comment4_.id as comments2_4_3_,
        comments3_.comments_ORDER as comments3_3_,
        comment4_.id as id1_0_4_,
        comment4_.text as text2_0_4_ 
    from
        ReviewSubject reviewsubj0_ 
    left outer join
        ReviewSubject_Review reviews1_ 
            on reviewsubj0_.id=reviews1_.ReviewSubject_id 
    left outer join
        Review review2_ 
            on reviews1_.reviews_id=review2_.id 
    left outer join
        Review_Comment comments3_ 
            on review2_.id=comments3_.Review_id 
    left outer join
        Comment comment4_ 
            on comments3_.comments_id=comment4_.id 
    where
        reviewsubj0_.id=?

The variable being 1. Giving this,

+----------+------------------+----------------+----------------+--------------+----------+------------+----------------+----------------+--------------+----------+------------+
| id1_2_0_ | subject2_2_0_    | ReviewSu1_2_1_ | reviews_2_3_1_ | reviews_3_1_ | id1_1_2_ | text2_1_2_ | Review_i1_1_3_ | comments2_4_3_ | comments3_3_ | id1_0_4_ | text2_0_4_ |
+----------+------------------+----------------+----------------+--------------+----------+------------+----------------+----------------+--------------+----------+------------+
|        1 | review subject 1 |              1 |              1 |            0 |        1 | review 1   |              1 |              1 |            0 |        1 | comment 1  |
|        1 | review subject 1 |              1 |              1 |            0 |        1 | review 1   |              1 |              2 |            1 |        2 | comment 2  |
|        1 | review subject 1 |              1 |              2 |            1 |        2 | review 2   |           NULL |           NULL |         NULL |     NULL | NULL       |
+----------+------------------+----------------+----------------+--------------+----------+------------+----------------+----------------+--------------+----------+------------+

As you would expect from the outer joins.

The problem was that next Hibernate deletes all rows in the ReviewSubject_Review tables and seems to be attempting to repopulate the table from the table of outer joins above. This table has an entry for each Review entity in the join table column for each Comment the Review has. This is what was leading to the constraint violation.

Now, I don't know how to, or even if you can, prevent Hibernate from trying to repopulate from the table of left outer joins, but ultimately, at least in this case, you don't need to.

This issue is really just a result of the well documented effect of using a List without an Index column. This causes Hibernate to treat the List like a Bag and so deleting the join table before inserting is expected and necessary behaviour. Bags and their behaviour are well documented in countless blogs and the Hibernate documentation so I won't go into any of that here.

The solution was just to slap a @OrderColumn annotation on each collection. With this index hibernate no longer needs to treat the List like a Bag and so has no need to perform the delete before insert. So no need to use the tables of outer joins.

So really quite a common problem, with a simple solution. Surprised no one picked up on it.

like image 170
AlyoshaKaramazov Avatar answered Dec 10 '25 15:12

AlyoshaKaramazov


Use java.util.Set instead of java.util.List for your collections and make sure that you have correctly implemented equals and hashCode for each entity bean.

like image 40
Steve C Avatar answered Dec 10 '25 15:12

Steve C



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!