Jsp+Servlet+JDBC的使用复习
最近对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与具体方法的关系,采用这种添加隐藏字段的方式只能是一个折中的方法,没有配置文件那样灵活。