Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 62 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
62
Dung lượng
1,02 MB
Nội dung
94 CHAPTER 4 The Model 2 design pattern As you can see, the UI is very sparse. This is intentional so that the design and architecture of the application become the center of attention. The information flow in Model 2 applications can sometimes be hard to see from the code. Because each artifact that the user sees is the culmination of several classes and JSPs, it is useful to see a collaboration diagram of the interaction before the code. Figure 4.2 shows the interaction between the classes and JSPs discussed. In the diagram in figure 4.2, the solid lines represent control flow and the dot- ted lines represent a user relationship. Views use the model to show information, and the controller uses the model to update it. The user invokes the ViewSchedule controller, which creates the appropriate model objects and forwards them to ScheduleView . The view allows the user to invoke the ScheduleEntry controller, which also uses the model. It forwards to the ScheduleEntryView , which posts to the SaveEntry controller. If there are validation errors, this controller returns the entry view. Otherwise, it forwards back to the main view to show the results of the addition. Let’s take a look at the code that makes all this happen. Building the schedule model The ScheduleBean class is the main model of the application. It is responsible for pulling schedule information from the database. The database structure for this application is simple, as shown in figure 4.3. Figure 4.1 The Model 2 Schedule application’s first page shows the events scheduled for a busy person. Using Model 2 as your framework 95 Browser <controller> ViewSchedule <model> ScheduleBean <value> ScheduleItem <controller> ScheduleEntry <controller> SaveEntry <view> ScheduleView <view> ScheduleEntryView Figure 4.2 The controller servlets create and manipulate the model beans, eventually forwarding the ones with displayable characteristics to a view JSP. event PK event_key start duration description event_type FK1 event_type_key event_types PK event_type_key event_text Figure 4.3 The database schema diagram for the schedule application shows that it is a simple database structure. 96 CHAPTER 4 The Model 2 design pattern The first part of ScheduleBean establishes constants used throughout the class. It is important to isolate strings and numbers so that they can be changed easily without searching high and low through code. The first part of ScheduleBean is shown in listing 4.1. package com.nealford.art.mvcsched; import java.sql.*; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; import java.util.*; public class ScheduleBean { private List list; private Map eventTypes; private Connection connection; private static final String COLS[] = {"EVENT_KEY", "START", "DURATION", "DESCRIPTION", "EVENT_TYPE"}; private static final String DB_CLASS = "org.gjt.mm.mysql.Driver"; private static final String DB_URL = "jdbc:mysql://localhost/schedule?user=root"; private static final String SQL_SELECT = "SELECT * FROM event"; private static final String SQL_INSERT = "INSERT INTO event (start, duration, description, " + "event_type) VALUES(?, ?, ?, ?)"; private static final String SQL_EVENT_TYPES = "SELECT event_type_key, event_text FROM event_types"; private Connection getConnection() { // naive, inefficient connection to the database // to be improved in subsequent chapter Connection c = null; try { Class.forName(DB_CLASS); c = DriverManager.getConnection(DB_URL); } catch (ClassNotFoundException cnfx) { cnfx.printStackTrace(); } catch (SQLException sqlx) { sqlx.printStackTrace(); } return c; } Listing 4.1 The declarations and database connection portions of ScheduleBean Using Model 2 as your framework 97 The constants define every aspect of this class’s interaction with the database, including driver name, URL, column names, and SQL text. Because these values are likely to change if the database type or definition changes, it is critical that they appear as constants. Many of these values could also appear in the deploy- ment descriptor configuration file (and will in subsequent examples). Note that the two collections used in this class are declared as the base inter- faces for the corresponding collection classes. For example, the List interface is the basis for all the list-based collection classes, such as Vector and ArrayList . Obviously, you cannot instantiate the collection using the interface—a concrete class must be assigned to these variables. However, you should always use the most generic type of definition possible for things like lists. This gives you the flexibility to change the underlying concrete class at some point in time without changing much code. In fact, you should just be able to change the actual constructor call for the list, enabling more generic and flexible code. You can always declare an object as a parent, an abstract parent, or an interface as long as you instantiate it with a concrete subclass or implementing class. List eventTypes = new ArrayList(); Vector and ArrayList offer the same func- tionality. The key difference between them relates to thread safety: the Vector class is thread safe and the ArrayList class is not. A thread-safe collection allows multiple threads to access the collection without corrupting the internal data structures. In other words, all the critical methods are synchronized. Thread safety imposes a performance penalty because each operation is locked against multithreaded access. A non-thread-safe collection doesn’t include these safe- guards and is therefore more efficient. If you know that your collections are never accessed from multiple threads, then you don’t need thread safety and you can use the more efficient ArrayList class. If in the future you need thread safety, you can change the declaration to create a Vector instead, enhancing your code to make it thread safe with a small change. Vector is left over from earlier versions of Java. If you need a thread-safe collection, you should use Collections.synchro- nizedCollection(Collection c) , which encapsulates any collection in a thread- safe wrapper. For more information about collections and thread safety, consult the Collections class in the SDK documentation. The getConnection() method in listing 4.1 creates a simple connection to the database. This practice does not represent a good technique for creating connec- tions. You generally shouldn’t create direct connections to the database from model beans because of scalability and performance reasons. The preferred way to handle database connectivity through beans is either through Enterprise Java- Beans ( EJBs) or database connection pools. This is a quick-and-dirty way to 98 CHAPTER 4 The Model 2 design pattern connect to the database for the purposes of this sample. We discuss better ways to manage database connectivity in chapter 12. The next slice of code from ScheduleBean (listing 4.2) handles database con- nectivity for the bean. public void populate() throws SQLException { // connection to database Connection con = null; Statement s = null; ResultSet rs = null; list = new ArrayList(10); Map eventTypes = getEventTypes(); try { con = getConnection(); s = con.createStatement(); rs = s.executeQuery(SQL_SELECT); int i = 0; // build list of items while (rs.next()) { ScheduleItem si = new ScheduleItem(); si.setStart(rs.getString(COLS[1])); si.setDuration(rs.getInt(COLS[2])); si.setText(rs.getString(COLS[3])); si.setEventTypeKey(rs.getInt(COLS[4])); si.setEventType((String) eventTypes.get( new Integer(si.getEventTypeKey()))); list.add(si); } } finally { try { rs.close(); s.close(); con.close(); } catch (SQLException ignored) { } } } public void addRecord(ScheduleItem item) throws ScheduleAddException { Connection con = null; PreparedStatement ps = null; Statement s = null; ResultSet rs = null; try { con = getConnection(); ps = con.prepareStatement(SQL_INSERT); ps.setString(1, item.getStart()); Listing 4.2 The database population and addition code for ScheduleBean Using Model 2 as your framework 99 ps.setInt(2, item.getDuration()); ps.setString(3, item.getText()); ps.setInt(4, item.getEventTypeKey()); int rowsAffected = ps.executeUpdate(); if (rowsAffected != 1) { throw new ScheduleAddException("Insert failed"); } populate(); } catch (SQLException sqlx) { throw new ScheduleAddException(sqlx.getMessage()); } finally { try { rs.close(); s.close(); con.close(); } catch (Exception ignored) { } } } The methods populate() and addRecord() are typical low-level Java Database Connectivity ( JDBC) code. In both cases, the unit of work is the ScheduleItem class. The populate() method builds a list of ScheduleItem instances and the addRecord() method takes a ScheduleItem to insert. This is an example of using a value object. A value object is a simple class, consisting of member variables with accessors and mutators, that encapsulates a single row from a database table. If the value object has methods beyond accessors and mutators, they are utilitarian methods that interact with the simple values of the object. For example, it is com- mon to include data-validation methods in value objects to ensure that the encapsulated data is correct. When populate() connects to the database in the ScheduleBean class, it builds a list of ScheduleItems . A design alternative could be for the populate() method to return a java.sql.ResultSet instance, connected to a cursor in the database. While this would yield less code, it should be avoided. You don’t want to tie the implementation of this class too tightly to JDBC code by using a ResultSet because it reduces the maintainability of the application. What if you wanted to port this application to use EJBs for your model instead of regular JavaBeans? In that case, the EJB would need to return a list of value objects and couldn’t return a ResultSet because ResultSet isn’t serializable and therefore cannot be passed from a server to a client. The design principle here is that it is preferable to return a collection of value objects from a model than to return a specific instance of a JDBC class. 100 CHAPTER 4 The Model 2 design pattern The only disadvantage to using the collection is that it will occupy more mem- ory than the ResultSet. Because a ResultSet encapsulates a database cursor, the data stays in the database and is streamed back to the ResultSet only as requested. This is much more efficient than storing the results in the servlet engine’s mem- ory—the records are stored in the database’s memory instead. This should be a decision point in your application: do you want to enforce good design practices at the expense of memory usage, or is the memory issue more important? Fortu- nately, this isn’t a binary decision. It is possible to write the populate() method more intelligently to return only a portion of the results as a list and retrieve more on demand. Generally, it is better to put a little more effort at the begin- ning into keeping the design correct than to try to “fix” it later once you have compromised it. The populate() method includes a throws clause indicating that it might throw a SQLException . The throws clause appears because we don’t want to han- dle the exception here in the model. Ultimately, we need to write the exception out to the log file of the servlet engine (and perhaps take other actions to warn the user). However, the model class doesn’t have direct access to the ServletCon- text object, which is required to write to the error log. Therefore, our model class is deferring its error handling to the servlet that called it. The controller servlet can take the appropriate action based on the exception. One incorrect solution to this problem is to pass the ServletContext object into the model object. The model should not be aware that it is participating in a web application (as opposed to a client/server application). The goal is reusabil- ity of the model object. Tying it too closely with a web implementation is a design error, going against the concept of clean separation of responsibilities underlying Model 2 implementations. The addRecord() method takes a populated ScheduleItem and adds it to the database via typical JDBC calls, using a parameterized query. The executeUpdate() method of PreparedStatement returns the number of rows affected by the SQL statement. In this case, it should affect exactly one row (the newly inserted row). If not, an exception is thrown. In this case, a ScheduleAddException is thrown instead of a SQLException . The ScheduleAddException (listing 4.3) is a custom exception class created just for this web application. package com.nealford.art.mvcsched; public class ScheduleAddException extends Exception { public ScheduleAddException() { Listing 4.3 The ScheduleAddException custom exception Using Model 2 as your framework 101 super(); } public ScheduleAddException(String msg) { super(msg); } } This exception class allows an explicit message to be sent back from the model bean to the controller—namely, that a new record could not be added. This is preferable to throwing a generic exception because the catcher has no way of dis- cerning what type of exception occurred. This technique demonstrates the use of a lightweight exception. A lightweight exception is a subclass of Exception (or Run- timeException ) that permits a specific error condition to propagate. Chapter 14 discusses this technique in detail. The last portion of ScheduleBean , shown in listing 4.4, returns the two impor- tant lists used by the other parts of the application: the list of event types and the list of schedule items. public Map getEventTypes() { if (eventTypes == null) { Connection con = null; Statement s = null; ResultSet rs = null; try { con = getConnection(); s = con.createStatement(); rs = s.executeQuery(SQL_EVENT_TYPES); eventTypes = new HashMap(); while (rs.next()) eventTypes.put(rs.getObject("event_type_key"), rs.getString("event_text")); } catch (SQLException sqlx) { throw new RuntimeException(sqlx.getMessage()); } finally { try { rs.close(); s.close(); con.close(); } catch (Exception ignored) { } } } return eventTypes; } Listing 4.4 The getEventTypes() and getList() methods of ScheduleBean 102 CHAPTER 4 The Model 2 design pattern public List getList() { return list; } The getEventTypes() method retrieves the records in the event_types table shown in figure 4.2. Because this list is small and practically constant, it isn’t efficient to execute a query every time we need a mapping from the foreign key event_type in the event table to get the corresponding name. To improve efficiency, this method caches the list upon the first request. Whenever this method is called, it checks to see whether the map has been created yet. If it has, it simply returns the map. If the table hasn’t been created yet, the method connects to the database, retrieves the records, and places them in a HashMap. This is an example of “lazy loading,” a caching technique in which information isn’t gathered until it is needed, and is kept for any future invocation, avoiding having to reload the same data every time. Chapters 15 and 16 discuss this and other performance techniques. The other item of note in both these methods is the use of the generic inter- face as the return type rather than a concrete class. Remember that the public methods of any class form the class’s contract with the outside world. You should be free to change the internal workings of the class without breaking the contract, which requires other code that relies on this class to change. Building the ScheduleItem value object Applications that access rows from SQL tables commonly need an atomic unit of work. In other words, you need a class that encapsulates a single entity that forms a unit of work that cannot be subdivided. This unit of work is usually imple- mented as a value object. Methods in model classes, such as the model bean dis- cussed earlier, can use the value object to operate on table rows. If the value object contains methods other than accessors and mutators, they are usually methods that interact with the internal values. Range checking and other valida- tions are good examples of helper methods in a value object. The schedule application uses a value object to encapsulate the event table. The ScheduleItem class is shown in listing 4.5. package com.nealford.art.mvcsched; import java.io.Serializable; import java.util.ArrayList; import java.util.List; Listing 4.5 The ScheduleItem value object Using Model 2 as your framework 103 public class ScheduleItem implements Serializable { private String start; private int duration; private String text; private String eventType; private int eventTypeKey; public ScheduleItem(String start, int duration, String text, String eventType) { this.start = start; this.duration = duration; this.text = text; this.eventType = eventType; } public ScheduleItem() { } public void setStart(String newStart) { start = newStart; } public String getStart() { return start; } public void setDuration(int newDuration) { duration = newDuration; } public int getDuration() { return duration; } public void setText(String newText) { text = newText; } public String getText() { return text; } public void setEventType(String newEventType) { eventType = newEventType; } public String getEventType() { return eventType; } public void setEventTypeKey(int eventTypeKey) { this.eventTypeKey = eventTypeKey; } public int getEventTypeKey() { return eventTypeKey; [...]... import import import import import import java. io.IOException; java. util.List; javax.servlet.RequestDispatcher; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; com.nealford .art. mvcsched.boundary.ScheduleDb; com.nealford .art. mvcsched.entity.ScheduleItem; com.nealford .art. mvcsched.util.ScheduleAddException; public... architecture If you start allowing model code into the view, you end up with the worst of all worlds—more source files, each of which is a tangle of spaghetti-like coupled code Instead of searching through one poorly designed file, you must search Parameterizing commands with controller servlets 117 through a set of them A perfect example of this kind of diligence appears in the entry view of our sample application,... Listing 4.6 105 The ViewSchedule controller package com.nealford .art. mvcsched; import import import import import import import java. io.IOException; javax.servlet.RequestDispatcher; javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; com.nealford .art. mvcsched.boundary.ScheduleBean; public class ViewSchedule extends... page of the application has a link at the bottom that allows the user to add new schedule items This link leads the user to the entry portion of the application, shown in figure 4.5 Listing 4.8 contains the code for the entry controller, ScheduleEntry Listing 4.8 The entry controller package com.nealford .art. mvcsched; import import import import javax.servlet.*; javax.servlet.http.*; java. io.*; java. util.*;... Listing 4.9 The header of the entry view Listing Listing of Java Keywords ... Depending on how often the user needs to see updated schedule information, this list of schedule items could have been added to the user’s session instead The advantage of that approach would be fewer database accesses for this user upon repeated viewing of this page The controller could check for the presence of the list and pull it from the session on subsequent invocations The disadvantage of adding it... The advantage of parameterizing the commands and building a generic controller servlet lies in the fact that you never have to create any more controller servlets The action classes are simpler than full-blown servlets and are tightly focused on their job Part of the plumbing of the web application has been written generically and can thus be used whenever it is needed A disadvantage of servlets in . part of ScheduleBean is shown in listing 4.1. package com.nealford .art. mvcsched; import java. sql.*; import java. util.ArrayList; import java. util.List; import javax.sql.DataSource; import java. util.*; public. { this.start = start; this.duration = duration; this.text = text; this.eventType = eventType; } public ScheduleItem() { } public void setStart(String newStart) { start = newStart; } . com.nealford .art. mvcsched; import java. io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import