Announcement Announcement Module
Collapse
No announcement yet.
Spring Data JPA replaces the persistenceSet? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring Data JPA replaces the persistenceSet?

    Hi All

    New to spring data jpa so I was wondering if someone can explain to me why when I try to save a entity with a set in it, the save causes all the items in the set to be replace by new items. Not sure if this is a bug with spring data jpa since it works if I call save with EntityManager without going though spring data.

    my sample class

    @Entity
    public class Customer extends AbstractPersistable<Long>{
    @OneToMany(cascade = CascadeType.ALL)
    private Set<Comment> comments = new HashSet<Comment>();

    public Set<Comment> getComments() { return comments; }
    public void setComments(Set<Comment> comments) { this.comments = comments; }
    }

    @Entity
    public class Comment extends AbstractPersistable<Long>{
    private String text;

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

    code:
    Customer customer = dao.findOne(1L);

    Comment comment = new Comment();
    comment.setText("test");
    customer.getComments().add(comment);

    dao.save(customer);

    so comment doesn't get its id and is no longer in the customer's comment hashset
    customer.getComments().contains(comment) returns false if you call it after the save function
    if you inspect customer.getComments(), there is a new comment entity with the id and everything filled out.

    Any help would be appreciated.
    Last edited by narutoboy; Feb 11th, 2012, 04:33 PM.

  • #2
    Customer customer = dao.findOne(1L);

    That line implies that customer already has a persistent id (already exists in the DB), remember save will merge your changes into the entity in the persitece context with that same id and return the merged managed entity. In other words you should operate on the customer object that calling save() returns.

    I also assume that 'dao' is your customer repository as you called findOne() and returned a customer. So its interface probabaly extends JpaRepository<Customer, Long> yet I see you are using it to persist the comment. Since you have cascade.All it seems to me that you would do dao.save(customer)

    It's hard to tell for sure with what you have shown but I would maybe start looking there.

    Comment


    • #3
      Thanks for the replay. Yes, the consumer object is assume to be already persisted and I ment to call the save on the customer object and not the comment object (I've since corrected the code in my post). Like you said, my dao implements a JPSRepository<Customer, Long>. The problem is when I add a comment and persist the customer, the comment I added to the customer object is is replace with a new comment (the one persisted) which I don't have a reference to. Also if you check if the customer contains the comment it will return false after calling dao.save(customer).

      Comment


      • #4
        I am not sure what the problem is I ran a test case and everything looks good.

        Comment
        Code:
        @Entity
        public class Comment extends AbstractPersistable<Long> {
        	private static final long serialVersionUID = 1804213477250265140L;
        	private String text;
        
        	public String getText() {
        		return text;
        	}
        
        	public void setText(String text) {
        		this.text = text;
        	}
        }

        Customer

        Code:
        @Entity
        public class Customer extends AbstractPersistable<Long> {
        
        	private static final long serialVersionUID = 118458269657011416L;
        	@OneToMany(cascade = CascadeType.ALL)
        	private Set<Comment> comments = new HashSet<Comment>();
        
        	public Set<Comment> getComments() {
        		return comments;
        	}
        
        	public void setComments(Set<Comment> comments) {
        		this.comments = comments;
        	}
        }

        Repository
        Code:
        public interface CustomerRepository extends JpaRepository<Customer, Long> {
        
        }
        Test Case

        Code:
        @ContextConfiguration(classes = { ApplicationConfig.class })
        @RunWith(SpringJUnit4ClassRunner.class)
        @ActiveProfiles("standard")
        @Transactional
        @TransactionConfiguration(defaultRollback = false)
        public class RepositoryTest {
        
        	@Autowired
        	private CustomerRepository customerRepository;
        
        	@Before
        	public void init() {
        		Customer customer = new Customer();
        		Comment comment = new Comment();
        		comment.setText("test");
        		customer.getComments().add(comment);
        		customerRepository.saveAndFlush(customer);
        		System.out.println("Save 1");
        	}
        
        	@Test
        	public void testSaveEmployee() throws Exception {
        		Comment comment = new Comment();
        		comment.setText("test2");
        		List<Customer> customers = customerRepository.findAll();
        		// There will only be one customer
        		Customer customer = customers.get(0);
        		for (Comment c : customer.getComments()) {
        			System.out.println(c.getText());
        		}
        		customer.getComments().add(comment);
        		Customer persistedCustomer = customerRepository.saveAndFlush(customer);
        		System.out.println("save 2");
        		for (Comment c : persistedCustomer.getComments()) {
        			System.out.println(c.getText());
        		}
        	}
        }
        Here is the output from the test case (which really was not testing anything :P)

        Hibernate: insert into Customer (id) values (default)
        Hibernate: values identity_val_local()
        Hibernate: insert into Comment (id, text) values (default, ?)
        Hibernate: values identity_val_local()
        Hibernate: insert into Customer_Comment (Customer_id, comments_id) values (?, ?)
        Save 1
        Hibernate: select customer0_.id as id1_ from Customer customer0_
        test
        Hibernate: insert into Comment (id, text) values (default, ?)
        Hibernate: values identity_val_local()
        Hibernate: insert into Customer_Comment (Customer_id, comments_id) values (?, ?)
        save 2
        test
        test2

        I also logged into the database I was using a derby client DB and verified that the data was correct after running.

        Not sure if this helps you or not.

        Thanks,

        Comment


        • #5
          Hi

          So you basically have the right idea. If you try to add assertTrue(customer.getComments().contains(comment )); after you call saveAndFlush in the testSaveEmployee, it will fail because it doesn't think its in the list of comments. This is the problem i've been having.

          Code:
          	@Test
          	public void testSaveEmployee() throws Exception {
          		Comment comment = new Comment();
          		comment.setText("test2");
          		List<Customer> customers = customerRepository.findAll();
          		// There will only be one customer
          		Customer customer = customers.get(0);
          		for (Comment c : customer.getComments()) {
          			System.out.println(c.getText());
          		}
          		customer.getComments().add(comment);
          		Customer persistedCustomer = customerRepository.saveAndFlush(customer);
          		System.out.println("save 2");
          		
          		for (Comment c : persistedCustomer.getComments()) {
          			System.out.println(c.getText());
          		}
          		
          		assertTrue(customer.getComments().contains(comment));  // this will fail
          	}
          I've attached the sample project incase anyone else wants to test it.

          Comment


          • #6
            It comes back to what I mentioned earlier when you call save or save and flush the saved and managed entity is returned. When you do the contains with the initial comment as you did in your test case it does not have a persistent id yet (its null), therefore it will not test true for object equality as that is based on the id. To do what you want change the test case as follows and it will pass. Alternatively you could do a select based on some other unique combination of comment attributes (or the comments id) and do your contains check with what is returned from that query. The key is that all the objects must be managed entities and have a persistent id associated with them.

            Code:
            @ContextConfiguration(classes = { ApplicationConfig.class })
            @RunWith(SpringJUnit4ClassRunner.class)
            @ActiveProfiles("embedded")
            @Transactional
            @TransactionConfiguration(defaultRollback = true)
            public class RepositoryTest {
            
            	@Autowired
            	private CustomerRepository customerRepository;
            
            	@Autowired
            	private CommentRepository commentRepository;
            
            	@Before
            	public void init() {
            		Customer customer = new Customer();
            		Comment comment = new Comment();
            		comment.setText("test");
            		customer.getComments().add(comment);
            		customerRepository.saveAndFlush(customer);
            		System.out.println("Save 1");
            	}
            
            	@Test
            	public void testSaveEmployee() throws Exception {
            		Comment comment = new Comment();
            		comment.setText("test2");
            		comment = commentRepository.save(comment);
            		List<Customer> customers = customerRepository.findAll();
            		// There will only be one customer
            		Customer customer = customers.get(0);
            		for (Comment c : customer.getComments()) {
            			System.out.println(c.getText());
            		}
            		customer.getComments().add(comment);
            		Customer persistedCustomer = customerRepository.saveAndFlush(customer);
            		System.out.println("save 2");
            		for (Comment c : persistedCustomer.getComments()) {
            			System.out.println(c.getText());
            		}
            		Assert.assertTrue(persistedCustomer.getComments().contains(comment));
            	}
            FYI this was the output from the above test case (which passed)
            Hibernate: insert into Customer (id) values (default)
            Hibernate: values identity_val_local()
            Hibernate: insert into Comment (id, text) values (default, ?)
            Hibernate: values identity_val_local()
            Hibernate: insert into Customer_Comment (Customer_id, comments_id) values (?, ?)
            Save 1
            Hibernate: insert into Comment (id, text) values (default, ?)
            Hibernate: values identity_val_local()
            Hibernate: select customer0_.id as id1_ from Customer customer0_
            test
            Hibernate: insert into Customer_Comment (Customer_id, comments_id) values (?, ?)
            save 2
            test
            test2

            Comment


            • #7
              Ok, I think I finally understand whats happening. Thanks for the help. The only thing I don't get is why spring data is not filling in the id of the comment after it persist the customer object. Here is what I'm talking about.

              Code:
              @ContextConfiguration({ "/spring/data.xml" })
              @RunWith(SpringJUnit4ClassRunner.class)
              @ActiveProfiles("standard")
              @Transactional
              @TransactionConfiguration(defaultRollback = false)
              public class RepositoryTest {
              
              	@Autowired
              	private CustomerRepository customerRepository;
              	
              	@PersistenceContext
              	private EntityManager entityManager;
              	
              	
              	@Test
              	public void testSpringData() throws Exception {
              		Customer customer = new Customer();
              		customerRepository.saveAndFlush(customer);
              		
              		customer = customerRepository.findOne(1l);
              		
              		Comment comment = new Comment();
              		comment.setText("test2");
              		
              		customer.getComments().add(comment);
              		customerRepository.saveAndFlush(customer);
              				
              		System.out.println(comment.getId());  // this is null
              	}
              	
              	@Test
              	public void testJpa() throws Exception {
              		Customer customer = new Customer();
              		entityManager.persist(customer);
              		entityManager.flush();
              		
              		customer = entityManager.find(Customer.class, 1l);
              		
              		Comment comment = new Comment();
              		comment.setText("test2");
              		
              		customer.getComments().add(comment);
              		
              		entityManager.persist(customer);
              		entityManager.flush();
              				
              		System.out.println(comment.getId());  // this is 1l
              	}	
              }
              Notice int testJpa() the comment id is getting filled while the testSpringData() doesn't fill the id. The problem is without the id being filled, I can't search the customer's comment for the persisted comment. Shouldn't spring data do the same thing as the entity manager? Is this a bug I should submit?

              Thanks

              Comment


              • #8
                The answer lies in the implementation of JpaRepository.

                Code:
                	@Transactional
                	public T save(T entity) {
                
                		if (entityInformation.isNew(entity)) {
                			em.persist(entity);
                			return entity;
                		} else {
                			return em.merge(entity);
                		}
                	}
                Because the entity being passed in is a Customer not a comment and the customer already exists in the database you are not actually calling em.persist but rather em.merge. Merging copies the state of the entity you pass in and merges it into the persistence context and returns the new copy (managed ) entity. Making changes in this case to the original object has no effect as it is not managed. In contrast calling persist makes the provided entity managed and future changes to that entity will be tracked by the persistence context.

                Looking back at the last test case I showed you I could have done this:

                commentRepository.save(comment);

                instead of

                comment = commentRepository.save(comment);

                and the test case still would have passed as the comment I was passing in had not been persisted yet and so em.persist would have been called rather than em.merge. However I am in the habit of just assigning the result back to the variable as then I don't have to concern myself with whether it was persisted or merged.

                Hope that clears it up for you.

                Comment


                • #9
                  Thanks a bunch for spending the time to explain it to me. I think you clear it all up for me. Didn't realize the save was doing a merge.

                  Comment


                  • #10
                    It's hard to tell for sure with what you have shown but I would maybe start looking there. Attachment
                    Attached Files

                    Comment

                    Working...
                    X