JAVA WEB从入门到精通 day18 JDBC学习(三)

工具类BaseServlet

编写原因

以前我们每处理一个请求都需要写一个Servlet,这样会写大量的Servlet,会让我们写大量的重复代码,效率低下。

我们希望在一个Servlet中可以有多个请求处理的方法。

我们来回顾一下服务器处理请求的方式,我们发送请求到服务器
服务器的service()方法处理我们的请求,并且根据我们的请求方式,自动调用doget或者dopost方法。

最简单的实现方式

1)我们需要用户传递一个要调用的方法的名字的参数。

    (2)然后我们根据用户传递的方法名字来判断调用哪个方法 

        public class AServlet extends HttpServlet {

            public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                String method=request.getParameter("m");            //获取用户传入的参数 
                if(method==null)                                    //如果没有传入参数则抛出异常
                {
                    throw new RuntimeException("请输入您要请求的方法");
                }
                if(method.equalsIgnoreCase("addUser"))              //使用 if...else来判断调用哪个方法
                {
                    addUser(request,response);
                }else if(method.equalsIgnoreCase("findUser"))
                {
                    findUser(request,response);
                }else if(method.equalsIgnoreCase("deleteUser"))
                {
                    deleteUser(request,response);
                }
            }

            //自定义的一些方法
            public void addUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                System.out.println("addUser()");
            }
            public void findUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                System.out.println("findUser()");
            }
            public void deleteUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                System.out.println("deleteUser()");
            }

        }

利用反射实现

我们发现,当我们需要再添加其他方法时,还需要再添加if..else语句。非常麻烦。
我们能否用其他的方法代替if…else语句呢?
答案是可以。我们利用反射来调用方法。

        public class BaseServlet extends HttpServlet {
                public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                    String method=request.getParameter("m"); //获取参数
                    if(method==null){
                        throw new RuntimeException("您没有传递method参数");
                        } 
                    Class c=this.getClass();        //获取当前类对象
                    Method m=null;                 //创建方法对象
                    try {                    //获取方法对象,获取不到就抛出异常
                        m=c.getMethod(method, HttpServletRequest.class,HttpServletResponse.class);
                        } catch (NoSuchMethodException | SecurityException e) {
                        throw new RuntimeException("您要调用的方法不存在");
                        }

                    try {                     //执行方法,执行错误抛出异常
                        m.invoke(this, request,response);
                        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                        throw new RuntimeException("方法内部出现异常");
                        }
                }
        }   

这样我们就解决了需要不断去判断的问题
如果我们需要这个功能的话,只需要我们的Servlet继承这个类即可。

我们继续来完善一下我们的BaseServlet类,我们在Servlet中常常需要做重定向和请求转发工作
我们可以简化一下

    public class BaseServlet extends HttpServlet {
                public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                    String method=request.getParameter("m");  //获取参数
                    if(method==null){
                        throw new RuntimeException("您没有传递method参数");
                    } 
                    Class c=this.getClass();
                    Method m=null;
                    try {
                        m=c.getMethod(method, HttpServletRequest.class,HttpServletResponse.class);
                    } catch (NoSuchMethodException | SecurityException e) {
                        throw new RuntimeException("您要调用的方法不存在");
                    }

                    try {
                        String result=(String)m.invoke(this, request,response);      //执行方法后返回一个字符串,看是否需要请求转发或者重定向操作
                        if(result==null)                                //如果字符串为空,则直接结束
                        {
                            return ;
                        }
                        if(result.contains(":"))                        //判断字符串包含:字符
                        {
                            //获取:的位置
                            int index=result.indexOf(":");                          
                            //获得:前面的字符串,判断是请求转发还是重定向
                            String s=result.substring(0, index);
                            //获取:后面的路径 
                            String path=result.substring(index+1);   

                            //如果前面的字符串是forward,则进行请求转发操作 
                            if(s.equals("forward"))                                 
                            {
                                request.getRequestDispatcher(path).forward(request, response);
                            }else if(s.equals("redirect"))              //如果是redirect,则执行重定向
                            {
                                response.sendRedirect(request.getContextPath()+path);
                            }else{                                                  //否则抛出异常
                                throw new RuntimeException("您当前的操作有误");
                            }
                        }

                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                        throw new RuntimeException("方法内部出现异常");
                    }
                }
            }

我们测试一下我们的类

public class BServlet extends BaseServlet { 

                    public String fun1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                        return "forward:/index.jsp";
                    }
                    public String fun2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                        return "redirect:/index.jsp";
                    }
                    public void fun3(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                        System.out.println("addUser()");
                    }

                }

我们只需要传递一个参数就可以调用相应的方法
例如:http://localhost:8080/BServlet?m=fun2 

service层对事务的操作

前提原因

DAO中只存在对数据库的操作。如果我们需要进行银行转账的操作,那么DAO层只负责修改数据库。
而具体的业务逻辑应该放在service层中,也就是在service层中处理事务。

例如:

    我们在DAO层处理数据
        public class BankDao {
            public void update(String name,int money) throws SQLException
            {
                QueryRunner qr=new QueryRunner();           //使用DBUtils帮我们简化操作
                String sql="update bank set money=money+? where name=?"; //sql模板
                Object[] params={money,name};                       //参数数组
                Connection con=JdbcUtils.getConnection();       //获取连接
                qr.update(con,sql,params);                      //执行操作
            }
        }
我们在Service层完成转账业务的逻辑。
        public class ServiceTest {

            public static void main(String[] args) throws SQLException {
                BankDao dao=new BankDao();      //先创建dao层对象
                try{
                JdbcUtils.beginTransaction();   //我们如果想要完成事务的话必须获取连接。而我们不能在service层暴露出 Connection con=getConnection();等方法。所以我们直接封装在我们的工具类中。
                dao.update("zhangsan", -500);   //调用dao层对数据的处理方法
                dao.update("lisi", 500);
                JdbcUtils.commitTransaction();  //提交事务
                }catch (Exception e) {
                    JdbcUtils.rollbackTransaction();//回滚事务
                }
            }
        }

而事务的操作必须保证是同一个Connction,我们必须在service层中声明Connection对象。
例如 Connection conn=new Connection()。
但是我们不想将这个对象暴露出来,我们选择将处理事务的方法封装在我们的JdbcUtils工具类中。

修改JdbcUtils类来完成对事务的支持

事务的一系列操作必须保证是同一个Connection。所以我们需要考虑如何在dao层处理数据时使用同一个连接
-我们在JdbcUtils定义一个事务连接,当我们开启事务时就将这个连接赋值

-当我们dao层处理数据获取连接时,获取连接之前先判断事务连接是否为null,如果不为空,则说明要处理事务,返回这个事务连接


    public class JdbcUtils {
            private static ComboPooledDataSource ds = new ComboPooledDataSource();
            private static Connection conn=null;                        //定义一个事务专用的连接

            public static Connection getConnection() throws SQLException
            {
                if(conn!=null) return conn;     //判断是否开启了事务,如果开启,就将专用的事务的连接返回
                return ds.getConnection();      //否则返回一个连接池中的连接
            }
            public static DataSource getDataSource()
            {
                return ds;
            }

            public static void beginTransaction() throws SQLException
            {
                if(conn!=null) throw new RuntimeException("您已经开启过事务");
                //得到连接
                //开启事务
                conn=getConnection();                   //如果调用开启事务的方法,就将我们的连接赋值,这样我们就可以保证事务的一系列操作用的是一个连接
                conn.setAutoCommit(false);             //开启事务
            }
            public static void commitTransaction() throws SQLException
            {
                if(conn==null) throw new RuntimeException("您还没有开启事务");
                conn.commit();
                conn.close();
                conn=null;                      //当我们提交完事务之后,连接只是被归还了,conn并没有为空,这时候再调用,就会报错,因为连接已经归还。所以提交完之后需要将连接为null
            }
            public static void rollbackTransaction() throws SQLException
            {
                if(conn==null) throw new RuntimeException("您还没有开启事务");
                conn.rollback();
                conn.close();
                conn=null;
            }

        }

当我们在DAO层获取连接时,我们是否需要关闭连接呢?答案是看情况而定。

比如我们开启了事务,调用了DAO层的方法,如果我们擅自关闭的话,那就会导致调用后面的DAO方法不可用。

所以我们需要判断连接是否是事务的连接,是的话不关闭,因为事务连接提交或者回滚时就会关闭,不是就需要我们自己关闭。

DAO层不涉及事务的处理,所以不能判断连接是否属于事务,我们在service层判断,我们将方法封装到JdbcUtils工具类中。

public class JdbcUtils {
            private static ComboPooledDataSource ds = new ComboPooledDataSource();
            private static Connection conn=null;

            public static Connection getConnection() throws SQLException
            {
                if(conn!=null) return conn;     //判断是否开启了事务,如果开启,就将事务的连接返回
                return ds.getConnection();
            }
            public static DataSource getDataSource()
            {
                return ds;
            }

            public static void beginTransaction() throws SQLException
            {
                if(conn!=null) throw new RuntimeException("您已经开启过事务");
                //得到连接
                //开启事务
                conn=getConnection();
                conn.setAutoCommit(false);
            }
            public static void commitTransaction() throws SQLException
            {
                if(conn==null) throw new RuntimeException("您还没有开启事务");
                conn.commit();
                conn.close();
                conn=null;                      //当我们提交完事物之后,连接只是被归还了,conn并没有为空,这时候再调用,就会报错,因为连接已经归还。。所以提交完之后需要将连接为null
            }
            public static void rollbackTransaction() throws SQLException
            {
                if(conn==null) throw new RuntimeException("您还没有开启事务");
                conn.rollback();
                conn.close();
                conn=null;
            }
            public static void closeConnection(Connection con) throws SQLException{
                if(conn==null) con.close();                 //如果事务连接为null,那就直接关闭
                if(conn!=con) con.close();                  //如果传入的连接和事务连接不相同,关闭连接。

            }

        }

JdbcUtils处理多线程并发访问问题

问题原因

当有多个线程使用JdbcUtils,同时开启事务时会抛出异常。 

或者一个线程开启事务,一个线程关闭事务,也会出现错误,原因是我们共享了了一个事务连接。 

为了解决这个问题,我们需要每一个线程有一个独立的连接,我们使用ThreadLocal来解决这个问题。 
        public class JdbcUtils {
                private static ComboPooledDataSource ds = new ComboPooledDataSource();
                private static ThreadLocal<Connection> tl=new ThreadLocal<Connection>();//创建ThreadLocal对象

                public static Connection getConnection() throws SQLException
                {
                    Connection conn=tl.get();     //从ThreadLocal中获取连接
                    if(conn!=null) return conn;     //判断是否开启了事务,如果开启,就将事务的连接返回
                    return ds.getConnection();
                }
                public static DataSource getDataSource()
                {
                    return ds;
                }

                public static void beginTransaction() throws SQLException
                {
                    Connection conn=tl.get();//从ThreadLocal中获取连接
                    if(conn!=null) throw new RuntimeException("您已经开启过事务");
                    //得到连接
                    //开启事务
                    conn=getConnection();
                    conn.setAutoCommit(false);
                    tl.set(conn);   //将连接设置到ThreadLocal中
                }
                public static void commitTransaction() throws SQLException
                {
                    Connection conn=tl.get();
                    if(conn==null) throw new RuntimeException("您还没有开启事务");
                    conn.commit();
                    conn.close();
                    tl.remove();                        //我们只需要将tl给移除即可
                }
                public static void rollbackTransaction() throws SQLException
                {
                    Connection conn=tl.get();
                    if(conn==null) throw new RuntimeException("您还没有开启事务");
                    conn.rollback();
                    conn.close();
                    tl.remove();
                }
                public static void closeConnection(Connection con) throws SQLException{ 
                    Connection conn=tl.get();
                    if(conn==null) con.close();                 //如果事务连接为null,那就直接关闭
                    if(conn!=con) con.close();                  //如果传入的连接和事务连接不相同,关闭连接。

                }

            }

你可能感兴趣的:(java,Web,jdbc)