manning Hibernate in Action phần 9 doc

47 363 0
manning Hibernate in Action phần 9 doc

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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 data- base transaction. The getSession() method has been extended to use the thread-local variable; we also wrap the checked HibernateException in an unchecked InfrastructureEx- ception (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 transac- tion. 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 thread- local 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 commitTransac- tion() 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 inter- ceptors 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 exe- cuted). 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 trans- action. After all actions (servlets) and the view are executed, we commit any pend- ing 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> 305Designing layered applications } 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 excep- tions; 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/redi- recting 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 persis- tent store, and business logic. There is also a catch clause for the HibernateExcep- tion 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 Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 306 CHAPTER 8 Writing Hibernate applications able to reuse in the admittedly unlikely event that our application gained a new user interface—and our domain model consists of “dumb” data-holding objects. The persistent classes define state but no behavior. We migrate the business logic into our domain model. Doing so adds a couple of lines of code but also increases the potential for later reuse; it’s also certainly more object-oriented and therefore offers various ways to extend the business logic (for example, using a strategy pattern for different bid strategies). First, we add the new method placeBid() to the Item class: public Bid placeBid(User bidder, BigDecimal bidAmount) throws BusinessException { // Auction still valid if ( this.getEndDate().before( new Date() ) ) { throw new BusinessException("Auction already ended."); } // Create new Bid Bid newBid = new Bid(bidAmount, this, bidder); // Place bid for this Item this.addBid(newBid); return newBid; } This code enforces business rules that constrain the state of our business objects but don’t execute data-access code. The motivation is to encapsulate business logic in classes of the domain model without any dependency on persistent data access. You might have discovered that this method of Item doesn’t implement the check for the highest bid. Keep in mind that these classes should know nothing about persistence because we might need them outside the persistence context (for example, in the presentation tier). We could even implement “Check the highest bid amount” in this placeBid() method by iterating the collection of bids for the item and finding the highest amount. This isn’t as performant as an HQL query, so we prefer to implement the check elsewhere later. Now, we simplify our action to the following: public void execute() { // Get values from request try { HibernateUtil.beginTransaction(); Session session = HibernateUtil.getSession(); // Load requested Item Item item = (Item) session.load(Item.class, itemId); Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 307Designing layered applications // Check amount of Bid with a query Query q = session.createQuery("select max(b.amount)" + " from Bid b where b.item = :item"); q.setEntity("item", item); BigDecimal maxBidAmount = (BigDecimal) q.uniqueResult(); if (maxBidAmount.compareTo(bidAmount) > 0) { throw new BusinessException("Bid amount too low."); } // Place Bid User bidder = (User) session.load(User.class, userId); Bid newBid = item.placeBid(bidder, bidAmount); // Place new Bid in scope for next page // Forward to showSuccess.jsp page } catch (HibernateException ex) { throw new InfrastructureException(e1); } catch (BusinessException ex) { // Execute exception specific code } catch (Exception ex) { // Throw application specific exception } } The business logic for placing a bid is now (almost completely) encapsulated in the placeBid() method and control logic in the action. We can even design a different pageflow by catching and forwarding specific exceptions. But the MVC pattern doesn’t say much about where P for Persistence should go. We’re sure the Hiber- nate code doesn’t belong in the action, however: Persistence code should be iso- lated in the persistence layer. Let’s encapsulate that code with a DAO and create a façade for persistence operations. Data access objects Mixing data access code with control logic violates our emphasis on separation of concerns. For all but the simplest applications, it makes sense to hide Hibernate API calls behind a façade with higher level business semantics. There is more than one way to design this façade—some small applications might use a single Persis- tenceManager object; some might use some kind of command-oriented design— but we prefer the DAO pattern. The DAO design pattern originated in Sun’s Java BluePrints. It’s even used in the infamous Java Petstore demo application. A DAO defines an interface to persis- tence operations ( CRUD and finder methods) relating to a particular persistent entity; it advises you to group code that relates to persistence of that entity. Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 308 CHAPTER 8 Writing Hibernate applications Let’s create an ItemDAO class, which will eventually implement all persistence code related to Item s. For now, it contains only the getItemById() method, along with getMaximumBidAmount() . The full code of the DAO implementation is shown in listing 8.5. Listing 8.5 A simple DAO abstracting item-related persistence operations public class ItemDAO { public ItemDAO() { HibernateUtil.beginTransaction(); } public Item getItemById(Long itemId) { Session session = HibernateUtil.getSession(); Item item = null; try { item = (Item) session.load(Item.class, itemId); } catch (HibernateException ex) { throw new InfrastructureException(ex); } return item; } public BigDecimal getMaxBidAmount(Long itemId) { Session session = HibernateUtil.getSession(); BigDecimal maxBidAmount = null; try { String query = "select max(b.amount)" + " from Bid b where b.item = :item"; Query q = session.createQuery(query); q.setLong("itemId", itemId.longValue()); maxBidAmount = (BigDecimal) q.uniqueResult(); } catch (HibernateException ex) { throw new InfrastructureException(ex); } return maxBidAmount; } } Whenever a new ItemDAO is created, we start a new database transaction or join the current database transaction of the running thread. Whether getMaximumBid- Amount() belongs on ItemDAO or a BidDAO is perhaps a matter of taste; but since the argument is an Item identifier, it seems to naturally belong here. By letting the DAO Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 309 Designing layered applications wrap all HibernateException s in our application’s InfrastructureException , we’ve finally managed to move all Hibernate exception handling out of the action. We also need a UserDAO , which, for now, contains just a getUserById() method: public class UserDAO { public UserDAO() { HibernateUtil.beginTransaction(); } public User getUserById(Long userId) { Session session = HibernateUtil.getSession(); User user = null; try { user = (User) session.load(User.class, userId); } catch (HibernateException ex) { throw new InfrastructureException(ex); } return user; } } You can begin to see a new advantage of the thread-local session pattern. All our DAOs can share the same Hibernate session (and even database transaction) with- out the need for you to pass the session explicitly as a parameter to the DAO instance. This is a powerful advantage that becomes more important as your appli- cation grows and layering becomes more complex. Armed with our new DAO classes, we can further simplify our action code to the following: public void execute() { // Get values from request try { ItemDAO itemDAO = new ItemDAO(); UserDAO userDAO = new UserDAO(); if (itemDAO.getMaxBidAmount(itemId).compareTo(bidAmount) > 0) throw new BusinessException("Bid amount too low."); Item item = itemDAO.getItemById(itemId); Bid newBid = item.placeBid(userDAO.getUserById(userId), bidAmount); // Place new Bid in scope for next page // Forward to showSuccess.jsp page } catch (BusinessException ex) { // Forward to error page Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> 310 CHAPTER 8 Writing Hibernate applications } catch (Exception ex) { // Throw application specific exception } } Notice how much more self-documenting this code is than our first implementa- tion. Someone who knows nothing about Hibernate can still understand immedi- ately what this method does, without the need for code comments. We’re now almost satisfied with our implementation of this use case. Our meth- ods are all short, readable, and somewhat reusable. Messy exception- and transac- tion-related code is completely externalized to infrastructure. However, there is still a mix of concerns. One piece of our business logic is still visible in the action implementation: the check against the current maximum bid. Code that throws a BusinessException should be in the domain model. An important question arises: If we moved this routine into the placeBid() method of Item , the domain model implementation will have a dependency on the persistence API, the DAOs. This should be avoided, because it would complicate unit testing of the domain objects and business logic (the “persistence” concern leaked into the domain model implementation). So, do we have no other choice but to keep this piece of business code with our control logic? The solution for this problem is some slight refactoring of the placeBid() method, two new methods on the ItemDAO class, and some changes to our control code, summarized in the following code snippet: BigDecimal currentMaxAmount = itemDAO.getMaxBidAmount(itemId); BigDecimal currentMinAmount = itemDAO.getMinBidAmount(itemId); Item item = itemDAO.getItemById(itemId); User user = userDAO.getUserById(userId) newBid = item.placeBid(user, newAmount, currentMaxAmount, currentMinAmount); We changed several things. First, we moved the business logic and exception to the placeBid() method. We call this method with new arguments: the current maximum and minimum bid amounts. We retrieve the two values using new meth- ods of the ItemDAO . Now, all that’s left in our action servlet are calls to the persis- tence layer and calls that start the execution of some business logic. Our business logic is encapsulated in the domain model and fully reusable; there is no depen- dency on the persistence layer’s DAO interface. You will likely face challenges like this is your own application, so be prepared to re-think and refactor your code for clean layering. Licensed to Jose Carlos Romero Figueroa <jose.romero@galicia.seresco.es> Designing layered applications 311 Let’s get back to our discussion of the DAO pattern. Actually, a DAO is barely a pattern at all—there are many ways to implement this basic idea. Some developers go so far as to combine their DAO framework with an abstract factory pattern, allowing runtime switching of the persistence mechanism. This approach is usu- ally motivated by the need to remain independent of vendor-specific SQL. Since Hibernate already does a good (although not a complete) job of abstracting our Java code away from the vendor-specific SQL dialect, we prefer to keep things sim- ple for now. The next step is to see how we can take this code and adapt it to run in an EJB container. Obviously, we’d like to change as little as possible. We’ve been arguing all along that one advantage of POJOs and transparent persistence is portability between different runtime environments. If we now have to rewrite all the code for placing a bid, we’re going to look a bit silly. 8.1.2 Using Hibernate in an EJB container From our point of view, the most important difference between a servlet-based application and an application where business logic and data access executes in the EJB container is the possibility of physical separation of tiers. If the EJB con- tainer runs in a different process than the servlet engine, it’s absolutely essential to minimize requests from the servlet tier to the EJB tier. Latency is added by every interprocess request, increasing the application response time and reduc- ing concurrency due to the need for either more database transactions or longer transactions. Hence it’s essential that all data access related to a single user request occur within a single request to the EJB tier. This means you can’t use the previous lazy approach, where the view was allowed to pull data from the domain model objects as needed. Instead, the business ( EJB) tier must accept responsibility for fetching all data that will be needed subsequently for rendering the view. In existing systems that use entity beans, you can already see this idea. The session façade pattern allows these systems to group all activity related to a particular user request into a single request to the EJB tier. The ubiquitous data-transfer object ( DTO) pattern provides a way of packaging together the data that the view will need. A DTO is a class that holds the state of a particular entity; you can think of a DTO as a JavaBean or POJO without any business methods. DTOs are required in an entity bean environment, since entity beans aren’t serializable and can’t be transported across tiers. In our case, we can easily make our POJOs serializable, so we naturally find ourselves questioning the need for DTOs. [...]... database transaction: Hibernate transparently handles the fact that it’s run­ ning in an EJB container with JTA, so the database transaction might remain in effect until the container commits it However, a Hibernate Session flush occurs at this point The failure of one of our business rules is indicated by throwing a BusinessException back to the client of this session bean A failure of an infrastructure... request).getSession(); Session hibernateSession = (Session) userSession.getAttribute("HibernateSession"); // and reconnect it to the current thread if (hibernateSession != null) HibernateUtil.reconnect(hibernateSession); try { chain.doFilter(request, response); // Commit any pending database transaction HibernateUtil.commitTransaction(); } finally { // Disconnect the Session hibernateSession = HibernateUtil.disconnectSession();... 324 CHAPTER 8 Writing Hibernate applications 8.2.3 Using detached persistent objects Suppose we kept the Item as a detached instance, storing it in the user’s HTTP ses­ sion, for example We could reuse it in the second database transaction by reasso­ ciating it with the new Hibernate session using either lock() or update() Let’s see what these two options look like In the case of lock(), we... same Hibernate session for both database transactions, a pattern we described in chapter 5 as session-perapplication-transaction 8.2.4 Using a long session A long session is a Hibernate session that spans a whole application transaction, allowing reuse of persistent instances across multiple database transactions This approach avoids the need to reassociate detached instances created or retrieved in. .. described 8.2 Implementing application transactions We discussed the notion of an application transaction in chapter 5, section 5.2, “Working with application transactions.” We also discussed how Hibernate helps detect conflicts between concurrent application transactions using managed ver­ sioning We didn’t discuss how application transactions are used in Hibernate applications, so we now return to... it in the user's HttpSession userSession.setAttribute("HibernateSession", hibernateSession); } } Instead of closeSession(), we call disconnectSession() in the finally block Before running the filter chain, we check whether the user session contains an existing Hibernate Session and, if found, reconnect() it and associate it with the current thread The disconnect and reconnect operations detach the Hibernate. .. item.approve(admin); } More complex application transactions sometimes make changes to the domain model in several sequential requests Since the session is flushed by our interceptor at the end of each request, the application transaction would be nonatomic if data was written in a request in the middle In applications that require recovery of incomplete application transactions in the case of a system failure, this...312 CHAPTER 8 Writing Hibernate applications Rethinking data transfer objects The notion that, in an EJB-based application, the web tier shouldn’t communicate directly with the domain model, is deeply embedded in J2EE practices and think­ ing We doubt that this idea will vanish overnight, and there are certain reasonable arguments in favor of this notion However, you shouldn’t... value even in the context of Hibernate DTO assembly provides you with a convenient point at which to ensure that all data the view will need is fully fetched before returning control to the web tier If you find yourself wrestling with Hibernate LazyInitializationExceptions in the web tier, one possible solution is to try the DTO pattern, which naturally imposes extra dis­ cipline by requiring that all... coarse-grained that you avoid the latency of many fine-grained interprocess calls We won’t spend much time discussing this pattern, since it’s well understood and noncontroversial Instead, we’ll demonstrate how our previous action example can be rewritten using a session façade We make two major changes to our code from the previous section First, we change the HibernateUtil class so that the Hibernate . database transaction: Hibernate transparently handles the fact that it’s run- ning in an EJB container with JTA, so the database transaction might remain in effect until the container commits. ctx = new InitialContext(); String jndiName = "java :hibernate/ HibernateFactory"; sessions = (SessionFactory)ctx.lookup(jndiName); } catch (NamingException ex) { throw new InfrastructureException(ex);. application transactions in an application that uses Hibernate: using a long session, using detached objects, and doing it the hard way. We’ll start with the hard way, since if you’ve been using EJB

Ngày đăng: 06/08/2014, 02:20

Từ khóa liên quan

Mục lục

  • Writing Hibernate applications

    • 8.1 Designing layered applications

      • 8.1.2 Using Hibernate in an EJB container

      • 8.2 Implementing application transactions

        • 8.2.1 Approving a new auction

        • 8.2.2 Doing it the hard way

        • 8.2.3 Using detached persistent objects

        • 8.2.4 Using a long session

        • 8.2.5 Choosing an approach to application transactions

        • 8.3 Handling special kinds of data

          • 8.3.1 Legacy schemas and composite keys

          • 8.3.2 Audit logging

          • 8.4 Summary

          • Using the toolset

Tài liệu cùng người dùng

Tài liệu liên quan