Thanks !
Code:
The problem that you're pointing out is one of concurrency. There are
two basic ways to solve this problem: optimistic locking and pessimistic
locking.
Pessimistic locking involves locking the row in the database. While
this was a reasonable and often used option in client/server
applications, it is not as attractive when dealing with three tiered
applications.
Optimistic locking is the approach used by most "well designed" three
tiered applications. To implement optimistic locking, you would add a
"version" column to your database table. Each row would have an
associated version. Each time an update to a row is made, its version
number is incremented. Lets take a look what happens in your scenario,
where you have two different applications reading the same row and then
updating it in turn:
-------------------------------------------------
| id | name | version |
-------------------------------------------------
| 1 | Maciej | 0 |
-------------------------------------------------
1. app1 reads row with id=1 and version=0
2. app2 reads row with id=1 and version=0
3. app1 updates row with id=1 and version=0 and sets name=Zawadzki --
version=1
4. app2 tries to update row with id=1 and version=0 to set name=Bogdan
-- this update does not happen
The reason the update does not happen in step 4 is because the SQL for
the update is something like this:
UPDATE ITEM
SET
NAME = ?,
... // additional attributes
VERSION = ? // new version value
WHERE
ID = ? AND
VERSION = ? // old version value
The Java code to do this would be something like:
1. // handle the concurrency control
2. int oldVersion = state.getVersion();
3. int newVersion = oldVersion + 1;
4.
5. try {
6. conn = getConnection();
7. stmnt = conn.prepareStatement(UPDATE_SQL);
8.
9. // update the attributes
10. ...
11. stmnt.setInt(6, newVersion);
12.
13. // set the concurrency control attribute on the where clause
14. stmnt.setInt(7, oldVersion);
15.
16. // execute the query
17. int rowCount = stmnt.executeUpdate();
18.
19. // check for concurrent modification and rollback version
20. if (rowCount == 0) {
21. throw new DFConcurrentModificationException("Could not update "+
22. "version " + oldVersion);
23. }
24. } catch (SQLException e) {
25. ...
26. } finally {
27. // close the statement and connection
28. ...
29. }
Notice that we can check how many rows were modified by the update and
throw an exception if no rows were modified -- which would happen if
some one else got in there and modified the row before us.
Now, back to your email though. EJBs do NOT protect against concurrent
modification if you have multiple applications accessing the same row.
Even Entity beans will not solve the problem of concurrency. What's
more, EJBs do not solve this problem even when used within the same
application. That's because the way that we use EJBs is with
DataTransferObjects. And that means that we have one transaction to
obtain the DTO, which is then displayed to the user. And when the user
submits edits, we need another transaction to write those edits to the
database. There is no telling how many other clients have made
modification to the underlying data between the two transactions -- one
to get the DTO and one to write the updated state back to the database.
This is one of those often misunderstood parts of EJBs. And no-one is
out there trying to make this better understood because the marketecture
says that EJBs will solve all of your concurrency problems -- well, they
don't. EJBs do enforce concurrency (by using basically pessimistic
locking) but only within the scope of a Transaction. But as you pointed
out, that is pretty much useless since most three tiered apps will need
concurrency across two transactions -- and EJBs don't help there.
So, now back to Hibernate. I think there is a simple solution that
you've missed. Take a look at 5.1.7
(http://www.hibernate.org/hib_docs/reference/html/or-mapping.html#or-mapping-s1-6).
You can define a version column as part of your database schema. Now,
based on that, take a look at 17.3.1 one more time. In a web app, you
would simply associate the Hibernate Session with the user's HTTP
session. When the user submits a request, you simply get that user's
Hibernate session, reconnect it and do another transaction. Hibernate
takes care of the optimistic locking for you if you have a version
column in your schema.
Notice that there is nothing in Hibernate that says that a Session can
not be accessed by multiple Threads. The only things that is said about
Sessions is that they are not Thread safe and should not be accessed by
multiple *concurrent* threads. So, it is OK to bind a Hibernate Session
to an HTTP session, because even though each successive HTTP request may
be handled by a different thread, such threads will be accessing the
Hibernate session one at a time -- and that is OK, no problem with
thread safety there.
A little disclaimer. I'm not a Hibernate guru and have not actually
used the version (optimistic locking) feature mentioned above. I have
used (and developed) similar features in other O/R mapping tools though.
But it should be very easy to test whether Hibernate does exactly what
we need it to do to implement optimistic locking.