Results 1 to 10 of 10

Thread: Spring Data JPA replaces the persistenceSet?

  1. #1
    Join Date
    Feb 2012
    Posts
    5

    Default 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 at 03:33 PM.

  2. #2

    Default

    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.

  3. #3
    Join Date
    Feb 2012
    Posts
    5

    Default

    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).

  4. #4

    Default

    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,

  5. #5
    Join Date
    Feb 2012
    Posts
    5

    Default

    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.
    Attached Files Attached Files

  6. #6

    Default

    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

  7. #7
    Join Date
    Feb 2012
    Posts
    5

    Default

    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

  8. #8

    Default

    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.

  9. #9
    Join Date
    Feb 2012
    Posts
    5

    Default

    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.

  10. #10
    Join Date
    Feb 2012
    Posts
    15

    Default

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

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •