1

Assume the following model:

@Entity
public class A {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   @OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
   private List<B> bs;

   public B getB(long id) {
      for(B b : bs)
         if(b.getId() == id) {
            return b;
         }
   }

}

@Entity
public class B {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   @ManyToOne
   @JoinColumn(name = "a_id")
   private A a;

   private String someString;

}

I then try to update a property of some entity B :

@Transactional(rollbackFor = Exception.class)
public void doSomeWork() {
   A a = aRepository.findById(/* some id */);
   a.getB(/* */).setSomeString(/* some string */);
}

When the method returns, I expect the modified entity B to be updated (SQL UPDATE). For some reason, it doesn't happen. I suspect that the framework is only aware about additions/removals to the bs collection, but since every instance in the collection should be a managed entity, the framework should be aware of the changes.

Not sure what I'm missing here.


EDIT:

I created a repository to reproduce the issue:

https://github.com/mikomarrache/hibernate-spring-issue

If you comment lines 25-27 of the MyServiceImpl class, the save in line 22 is performed. However, if you uncomment these lines, it looks like the save in line 22 is ignored but the second save in line 27 is done, and of course it breaks the unique constraint on name. In order to test, simply run the unit test. No need to populate the database, there is an SQL script on the classpath that is executed at startup.

manash
  • 6,985
  • 12
  • 65
  • 125
  • ..do you use lombok, or just omitted the getters/setters, but maybe `someString` is really "not mapped"!? – xerx593 Jan 13 '19 at 16:33
  • I omitted the getters/setters for brievity, the string value is correctly set. Also the aRepository instance is a Spring Data JPA repository that uses Hibernate as underlying framework (v5.3.7). – manash Jan 13 '19 at 16:35
  • If it is spring repo entities might not be managed in the same manner even you use @Transactional. Try yet repo.save(..) if it works. Is it spring or java annotation? – pirho Jan 13 '19 at 16:37
  • @pirho I also tried repo.save() passing the instance "a" to the save() method, same thing. – manash Jan 13 '19 at 16:39
  • Ok, how about if using save() & no @Transactional? It looks like that internal em does not see this change. It could also be tested with injecting @PersistenceContext em and persist(). – pirho Jan 13 '19 at 16:44
  • Do you get an error? –  Jan 13 '19 at 16:46
  • `getB(long)` is very unlikely mapped!, for that plz try to acces `bs`within @Transactional! ;) – xerx593 Jan 13 '19 at 16:56
  • @xerx593 I'm not sure I understand your comment. getB() uses bs internally. What do you mean by accessing bs within @Transactional? – manash Jan 13 '19 at 18:32
  • Ok - I tried without @Transactional and it works. Any idea why? – manash Jan 13 '19 at 18:43
  • You mean without `@Trasactional`, but `Repostiroy.save(a)`? (otherwise I am confused.) To understand it, you must understand "what/where are transaction boundaries" (https://stackoverflow.com/questions/5538262/understanding-spring-transaction-boundaries) ...and the mechanism of "auto-persisting" (https://stackoverflow.com/questions/21552483/why-does-transactional-save-automatically-to-database) ...in your case, I just guess, that changes to `getB` got not applied, because you didn't "use bs internally", but "left the transaction boundary" ...and changed a "transient/unmapped" object. – xerx593 Jan 13 '19 at 19:15
  • if, you "refactor" `getB(long)` method (assuming original code) into the `@Transactional`class, I predict, that it will apply the changes (as expected/desired), because, then you don't leave tx-boundaries (privat internal call), use `bs` internally, in which all single items are mapped & "managed". – xerx593 Jan 13 '19 at 19:22
  • Yes - without '@Transactional', but still using repository.save(). I think I know enough about transaction boundaries. If '@Transactional' is not used, the transaction will start and finish inside the save() method. Regarding getB, I do use bs internally (more specifically, I go over it using an iterator that I get from bs itself). How did I left transaction boundary since I'm still in the doSomework() method which is annotated with '@Transactional'? – manash Jan 13 '19 at 19:22
  • @xerx593 I replaced the occurrences of getB() by the actual code, inside the '@Transactional' method. I also changed bs visibility from private to package, so that I can access it directly without using getBs() instead. Same issue. – manash Jan 13 '19 at 19:31
  • @xerx593 I'm confused. The method doSomeWork() is annotated with Transactional, so the tx starts before entering the method and ends after the method returns. Inside the method, I don't call any other method that changes transactional behavior. The repository methods are, if I'm not mistaken, transactional with default propagation (which is required, so no new tx is created). So, I'm not sure to understand where I leave the tx boundaries? Even by calling getB(), I don't see how tx boundaries are left?! – manash Jan 13 '19 at 19:41
  • @xerx593 FYI, if I an add an element to the collection inside doSomeWork() (a.addB(b)), I do see the INSERT... There is a different between doing an insert, a remove or, the case of updating an entity already in the collection. – manash Jan 13 '19 at 20:03
  • yes, i can reproduce... but when I try to acces `bs` or `getB()` outside @Transactional i get a `LazyInitializationException` – xerx593 Jan 13 '19 at 20:31
  • after checking my test (setup & validation), I must reconsider: I *cannot reproduce*! https://github.com/xerx593/soq54170708 : `someString` is updated as expected. – xerx593 Jan 13 '19 at 22:04
  • @xerx593 Please see my edit, I created a repo. – manash Jan 14 '19 at 07:08
  • How (and *when*) do you determine whether the changes had been applied to the DB? Hibernate sends updates as late as possible to the DB, which is usually just before commit (which is not in your code). While in your method, it doesn't update anything. (Except of inserts, which are done immediately because of the Identity.) There is also a Hibernate configuration to control when changes are applied (flushed) to the DB. It's called flush mode. – Stefan Steinegger Jan 14 '19 at 09:23
  • @StefanSteinegger Okay I get you, so the update is done just before commit (which is done when my method returns i.e. handled by Spring), and the insert is done immediately, and of course, that's a problem. However, I also tried saveAndFlush() instead of save() for the update, and it doesn't force the update to happen immediately, not sure what is the purpose of the flush then. – manash Jan 14 '19 at 09:25
  • `save` only means: add it to the session. Because it's already there, it doesn't do anything. `flush` however should actually perform updates (and deletes). If you have an ordering problem with a constraint, you need to delete and then flush explicitly before you can use the same name again. Frankly, I don't know Spring. `saveAndFlush` looks suspicious to me, because you pass in an entity as argument. Flush is actually done on the Hibernate session. How can you flush the deletion of an entity when you have to pass in the deleted entity? This certainly doesn't work. – Stefan Steinegger Jan 14 '19 at 09:35
  • Forgot to mention: when you use another ID generator but Identity, Hibernate can do inserts at the end of the transaction too. Most probably it does deletes first. Try an ID generator like sequence, hi-lo or guid. Hibernate likes it more and works better. – Stefan Steinegger Jan 14 '19 at 09:41

0 Answers0