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

原文地址:https://www.cnblogs.com/lz2017/p/7055591.html