Thư viện tri thức trực tuyến
Kho tài liệu với 50,000+ tài liệu học thuật
© 2023 Siêu thị PDF - Kho tài liệu học thuật hàng đầu Việt Nam

manning Hibernate in Action phần 9 doc
Nội dung xem thử
Mô tả chi tiết
Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es>
302 CHAPTER 8
Writing Hibernate applications
try {
if (s == null) {
s = sessionFactory.openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
public static void closeSession() { E
try {
Session s = (Session) threadSession.get();
threadSession.set(null);
if (s != null && s.isOpen())
s.close();
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
public static void beginTransaction() { F
Transaction tx = (Transaction) threadTransaction.get();
try {
if (tx == null) {
tx = getSession().beginTransaction();
threadTransaction.set(tx);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
}
public static void commitTransaction() { G
Transaction tx = (Transaction) threadTransaction.get();
try {
if ( tx != null && !tx.wasCommitted()
&& !tx.wasRolledBack() )
tx.commit();
threadTransaction.set(null);
} catch (HibernateException ex) {
rollbackTransaction();
throw new InfrastructureException(ex);
}
}
public static void rollbackTransaction() { H
Transaction tx = (Transaction) threadTransaction.get();
try {
threadTransaction.set(null);
if ( tx != null && !tx.wasCommitted()
&& !tx.wasRolledBack() ) {
Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es>
303
B
C
D
E
F
G
H
Designing layered applications
tx.rollback();
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
} finally {
closeSession();
}
}
}
The Session of the current thread is stored in this ThreadLocal variable.
We use one database transaction for all operations, so we use another ThreadLocal
for the Transaction. Both Session and Transaction are now associated with the
thread, and many action executions in a thread can participate in the same database transaction.
The getSession() method has been extended to use the thread-local variable; we
also wrap the checked HibernateException in an unchecked InfrastructureException (part of CaveatEmptor).
We also wrap the exceptions thrown by Session.close() in this static helper
method.
The code used to start a new database transaction is similar to the getSession()
method.
If committing the database transaction fails, we immediately roll back the transaction. We don’t do anything if the transaction was already committed or rolled back.
After rolling back the database transaction, the Session is closed.
This utility class is much more powerful than our first version: It provides threadlocal sessions and database transactions, and it wraps all exceptions in a runtime
exception defined by our application (or framework). This simplifies exception
handling in application code significantly, and the thread-local pattern gives us the
flexibility to share a single session among all actions and JSPs in a particular thread.
The same is true for database transactions: You can either have a single database
transactions for the whole thread or call beginTransaction() and commitTransaction() whenever you need to.
You can also see that calling getSession() for the first time in a particular thread
opens a new Session. Let’s now discuss the second part of the thread-local session
Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es>
304 CHAPTER 8
Writing Hibernate applications
design pattern: closing the Session after the view is rendered, instead of at the end
of each execute() method.
We implement this second part using a servlet filter. Other implementations are
possible, however; for example, the WebWork2 framework offers pluggable interceptors we could use. The job of the servlet filter is to close the Session before the
response is sent to the client (and after all views are rendered and actions are executed). It’s also responsible for committing any pending database transactions. See
the doFilter() method of this servlet filter in listing 8.4.
Listing 8.4 The doFilter() method closes the Hibernate Session
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
HibernateUtil.commitTransaction();
} finally {
HibernateUtil.closeSession();
}
}
We don’t start a database transaction or open a session until an action requests
one. Any subsequent actions, and finally the view, reuse the same session and transaction. After all actions (servlets) and the view are executed, we commit any pending database transaction. Finally, no matter what happens, we close the Session to
free resources.
Now, we can simplify our action’s execute() method to the following:
public void execute() {
// Get values from request
try {
HibernateUtil.beginTransaction();
Session session = HibernateUtil.getSession();
// Load requested Item
// Check auction still valid
// Check amount of Bid
// Add new Bid to Item
// Place new Bid in scope for next page
// Forward to showSuccess.jsp page
Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es>
Designing layered applications 305
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
} catch (Exception ex) {
// Throw application specific exception
}
}
We’ve reduced the exception-handling code to a single try/catch block. We can
safely rethrow checked exceptions such as HibernateException as runtime exceptions; we can use our application’s (or framework’s) exception hierarchy.
The thread-local session pattern isn’t perfect, unfortunately. Changes made to
objects in the Session are flushed to the database at unpredictable points, and we
can only be certain that they have been executed successfully after the Transaction
is committed. But our transaction commit occurs after the view has been rendered.
The problem is the buffer size of the servlet engine: If the contents of the view
exceed the buffer size, the buffer might get flushed and the contents sent to the
client. The buffer may be flushed many times when the content is rendered, but
the first flush also sends the HTTP status code. If the SQL statements executed at
transaction commit time were to trigger a constraint violation in the database, the
user might already have seen a successful output! We can’t change the status code
(for example, use a 500 Internal Server Error), because it’s already been sent to
the client (as 200 OK).
There are several ways to prevent this rare exception: You could adjust the buffer
size of your servlet engine, or flush the Hibernate session before forwarding/redirecting to the view (add a flushSession() helper method to HibernateUtil). Some
web frameworks don’t immediately fill the response buffer with rendered content;
they use their own OutputStream and flush it with the response only after the view
has been completely rendered. So, we consider this a problem only with plain Java
servlet programming.
Our action is already much more readable. Unfortunately, it still mixes
together three distinctly different responsibilities: pageflow, access to the persistent store, and business logic. There is also a catch clause for the HibernateException that looks misplaced. Let’s address the last responsibility first, since it’s the
most important.
Creating "smart" domain models
The idea behind the MVC pattern is that control logic (in our application, this is
pageflow logic), view definitions, and business logic should be cleanly separated.
Currently, our action contains some business logic—code that we might even be