最近對JDBC進行了復習,對事物的理解,連接池的使用等部分都有一個復習,所以使用Servlet+JDBC完成了一個小Demo,在這里對這種底層的操作進行總結。框架的使用的確方便了我們的開發,但是底層的實現也不應該忘記
在這里還是用Web三層的分層結構,只不過是:表示層(Web層)使用Servlet,業務層還是使用Service(在這里,Service的作用並不明顯,只是調用Dao層的方法),持久層(Dao層)
我做的這個小項目使用的Jar有:c3p0-0.9.2.jar (連接池),mchange-commons-0.2.jar(連接池需要依賴), commons-beanutils-1.8.3.jar (簡化數據bean的封裝),commons-dbutils-1.4.jar (簡化JDBC),commons-logging-1.1.1.jar(日志) ,mysql-connector-java-5.1.28-bin.jar(數據庫驅動) ,jstl-1.2.jar(我在jsp中使用了JSTL標簽)
需要的配置文件有:c3p0-config.xml
額外的類有:JdbcUtils.java (配置連接池和事務), TxQueryRunner.java (處理線程的類,繼承於QueryRunner),CommonUtils.java (一個小工具類,提供獲得UUID和將map轉換為對應的JavaBean),BaseServlet.java(多個Servlet方便操作)
其實,在前面的復習文章中我已經將以上文件的源碼分享出來了,這里在粘一份了
c3p0-config.xml:
<?xml version="1.0" encoding="UTF-8" ?> <c3p0-config> <default-config> <!--記得換成項目使用的數據庫--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/customers</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">3</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">2</property> <property name="maxPoolSize">10</property> </default-config> </c3p0-config>
在使用c3p0是會自動加載這個c3p0-config.xml配置文件,也就是 new ComboPooledDataSource() 的時候,所以只要我們將這個配置文件放對位置(src下)就會可以了,無需自己去解析配置文件,c3p0內部已經做了這個工作
JdbcUtils.java:
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; /** * 使用本類的方法,必須提供c3p0-copnfig.xml文件 */ public class JdbcUtils { private static DataSource ds = new ComboPooledDataSource(); /** * 它為null表示沒有事務 * 它不為null表示有事務 * 當開啟事務時,需要給它賦值 * 當結束事務時,需要給它賦值為null * 並且在開啟事務時,讓dao的多個方法共享這個Connection */ private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); public static DataSource getDataSource() { return ds; } /** * dao使用本方法來獲取連接 * @return * @throws SQLException */ public static Connection getConnection() throws SQLException { /* * 如果有事務,返回當前事務的con * 如果沒有事務,通過連接池返回新的con */ Connection con = tl.get();//獲取當前線程的事務連接 if(con != null) return con; return ds.getConnection(); } /** * 開啟事務 * @throws SQLException */ public static void beginTransaction() throws SQLException { Connection con = tl.get();//獲取當前線程的事務連接 if(con != null) throw new SQLException("已經開啟了事務,不能重復開啟!"); con = ds.getConnection();//給con賦值,表示開啟了事務 con.setAutoCommit(false);//設置為手動提交 tl.set(con);//把當前事務連接放到tl中 } /** * 提交事務 * @throws SQLException */ public static void commitTransaction() throws SQLException { Connection con = tl.get();//獲取當前線程的事務連接 if(con == null) throw new SQLException("沒有事務不能提交!"); con.commit();//提交事務 con.close();//關閉連接 con = null;//表示事務結束! tl.remove(); } /** * 回滾事務 * @throws SQLException */ public static void rollbackTransaction() throws SQLException { Connection con = tl.get();//獲取當前線程的事務連接 if(con == null) throw new SQLException("沒有事務不能回滾!"); con.rollback(); con.close(); con = null; tl.remove(); } /** * 釋放Connection * @param con * @throws SQLException */ public static void releaseConnection(Connection connection) throws SQLException { Connection con = tl.get();//獲取當前線程的事務連接 if(connection != con) {//如果參數連接,與當前事務連接不同,說明這個連接不是當前事務,可以關閉! if(connection != null &&!connection.isClosed()) {//如果參數連接沒有關閉,關閉之! connection.close(); } } } }
TxQueryRunner.java:
import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; public class TxQueryRunner extends QueryRunner { @Override public int[] batch(String sql, Object[][] params) throws SQLException { Connection con = JdbcUtils.getConnection(); int[] result = super.batch(con, sql, params); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException { Connection con = JdbcUtils.getConnection(); T result = super.query(con, sql, rsh, params); JdbcUtils.releaseConnection(con); return result; } @Override public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException { Connection con = JdbcUtils.getConnection(); T result = super.query(con, sql, rsh); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql) throws SQLException { Connection con = JdbcUtils.getConnection(); int result = super.update(con, sql); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql, Object param) throws SQLException { Connection con = JdbcUtils.getConnection(); int result = super.update(con, sql, param); JdbcUtils.releaseConnection(con); return result; } @Override public int update(String sql, Object... params) throws SQLException { Connection con = JdbcUtils.getConnection(); int result = super.update(con, sql, params); JdbcUtils.releaseConnection(con); return result; } }
CommonUtils.java:
import java.util.Map; import java.util.UUID; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.ConvertUtils; import org.apache.commons.beanutils.converters.DateConverter; /** * 小小工具 * */ public class CommonUtils { /** * 返回一個不重復的字符串 * @return */ public static String uuid() { return UUID.randomUUID().toString().replace("-", "").toUpperCase(); } /** * 把map轉換成對象 * @param map * @param clazz * @return * * 把Map轉換成指定類型 */ @SuppressWarnings("rawtypes") public static <T> T toBean(Map map, Class<T> clazz) { try { /* * 1. 通過參數clazz創建實例 * 2. 使用BeanUtils.populate把map的數據封閉到bean中 */ T bean = clazz.newInstance(); ConvertUtils.register(new DateConverter(), java.util.Date.class); BeanUtils.populate(bean, map); return bean; } catch(Exception e) { throw new RuntimeException(e); } } }
BaseServlet.java:
import java.io.IOException; import java.lang.reflect.Method; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * BaseServlet用來作為其它Servlet的父類 * * * 一個類多個請求處理方法,每個請求處理方法的原型與service相同! 原型 = 返回值類型 + 方法名稱 + 參數列表 */ @SuppressWarnings("serial") public class BaseServlet extends HttpServlet { @Override public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8");//處理響應編碼 request.setCharacterEncoding("UTF-8"); /** * 1. 獲取method參數,它是用戶想調用的方法 2. 把方法名稱變成Method類的實例對象 3. 通過invoke()來調用這個方法 */ String methodName = request.getParameter("method"); Method method = null; /** * 2. 通過方法名稱獲取Method對象 */ try { method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); } catch (Exception e) { throw new RuntimeException("您要調用的方法:" + methodName + "它不存在!", e); } /** * 3. 通過method對象來調用它 */ try { String result = (String)method.invoke(this, request, response); if(result != null && !result.trim().isEmpty()) {//如果請求處理方法返回不為空 int index = result.indexOf(":");//獲取第一個冒號的位置 if(index == -1) {//如果沒有冒號,使用轉發 request.getRequestDispatcher(result).forward(request, response); } else {//如果存在冒號 String start = result.substring(0, index);//分割出前綴 String path = result.substring(index + 1);//分割出路徑 if(start.equals("f")) {//前綴為f表示轉發 request.getRequestDispatcher(path).forward(request, response); } else if(start.equals("r")) {//前綴為r表示重定向 response.sendRedirect(request.getContextPath() + path); } } } } catch (Exception e) { throw new RuntimeException(e); } } }
接下來我是從Servlet開始寫:
添加客戶,JavaBean是Customer.java , 里面就是一些屬性和set/get方式,這里不貼出了
/** * 添加客戶 * */ public String add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Customer customer = CommonUtils.toBean(request.getParameterMap(), Customer.class); customer.setCid(CommonUtils.uuid()); customerService.add(customer); request.setAttribute("msg", "恭喜,添加客戶成功"); return "f:/msg.jsp"; }
客戶id使用的是UUID,從表單獲得數據是沒有id的,就需要我們在程序中指定,然后進到Service層,不要忘記在Web.xml中配置我們這個Servlet哦,BaseServlet不需要配置。
Service層做的工作很簡單,就是調用Dao的方法(這里還沒有使用事務):
public void add(Customer c){ customerDao.add(c); }
Dao層的添加方法:
/** * 添加客戶 * @param c */ public void add(Customer c){ try { String sql="insert into t_customer values(?,?,?,?,?,?,?)"; Object[] params={c.getCid(),c.getCname(),c.getGender() ,c.getBirthday(),c.getCellphone(),c.getEmail(),c.getDescription()}; qr.update(sql, params); } catch (SQLException e) { e.printStackTrace(); } }
可以看到簡化后的Dao操作很簡單了,分三步,第一步:寫出SQL語句;第二步:設置參數,這里使用Object數組,因為類型不一致,所以使用Object;第三步:使用QueryRunner的update方法(增刪該,查詢使用query)值得注意的是用 TxQueryRunner 類構建我們的QueryRunner:
private QueryRunner qr=new TxQueryRunner();
再看看一個查詢的方法:
/** * 查詢所有客戶 * @return */ public List<Customer> findAll() { try { String sql="select * from t_customer"; return qr.query(sql, new BeanListHandler<Customer>(Customer.class)); } catch (SQLException e) { e.printStackTrace(); } return null; }
這里使用的結果集處理器是BeanListHandler,可以根據結果集的不同使用不同的結果集處理器
注意:這里TxQueryRunner類只是重寫了query(String sql, ResultSetHandler<T> rsh, Object... params) 和query(String sql, ResultSetHandler<T> rsh)這兩個方法,前一個是帶查詢條件的,后一個不帶,帶條件的查詢使用前一個即可。QueryRunner類的其他query方法沒有重寫,所以不要用錯,如要使用記得給它們connection。
在頁面如何訪問
我們在表示層如何訪問這個Servlet呢?我們需要在提交的表單中添加一個隱藏的字段,名為method,其值為需要訪問的Servlet中的具體方法:
<form action="<c:url value='/customerServlet'/>" method="post"> <!-- 向servlet傳遞一個名為method的參數,其值表示要調用servlet的哪一個方法 --> <input type="hidden" name="method" value="add"/>
現在整個工作方式類似於Struts,但是我們沒有采用類似Struts.xml一樣的配置文件,沒有很好的辦法來映射action與具體方法的關系,采用這種添加隱藏字段的方式只能是一個折中的方法,沒有配置文件那樣靈活。