JavaWeb-database connection pool(5)

One, the database connection pool

  • To operate the database through the java program, you must first obtain the connection object with the database. The java program and the database are two processes. In order to perform various operations on the data in the database, a communication connection must be established between the two (both ends It is necessary to maintain a Socket socket), and also to send requests and data to the other party through the socket. Therefore, this operation is very resource intensive and burdens both sides. Therefore, the number of connection objects is as small as possible. To create a connection object,
  • In a project, you must frequently operate the database. If you establish a connection every time, it will cause more and more resource consumption. In order to solve the above problems, a database connection pool has appeared.

What is a database connection pool?

  • The javax.sql Interface DataSource interface is used to represent the database connection pool object. If a class implements this interface, the class is the database connection pool object.
  • One of its main methods is: Connection getConnection() throws SQLException, the function is to get a connection object from the connection pool.

The basic principle of connection pool

  • A queue is maintained internally, and the initially created connection object will be queued in this queue, waiting to be allocated to a thread for use. This kind of queue is a bounded queue, in which the number of objects is limited and will not increase indefinitely. Most connection pools have three basic parameters. The first is initSize represents the initial number of connections in the connection pool, minIdleSize represents the smallest idle connection object that can be maintained, and maxActivedSize represents the maximum number of connections in the pool. This value cannot be exceeded. The number of threads that need to be connected is greater than the maximum number of connections, and the thread pool also needs to queue to wait for the allocation of connections.
  • When the connection pool is just created, the number of internal connections is 0. If there is a thread requesting the connection object, the connection object will be created at this time, and it will be returned to the pool for the next use after use. If there is a free connection object in the pool, the object is directly allocated to the thread.
  • If the free objects are also allocated, new objects will be created, and will not be created until the maximum number of connections is reached.
  • If the number of threads is reduced, the newly created connection objects will be destroyed.

How the connection pool is used

  • Only one connection pool is used in a project, so the connection pool object should be singleton.
  • When a thread needs a connection object, it must call the getConnection() method of the connection pool object to obtain the connection and use it.
  • When the thread finishes using the connection object, it must release and close the object and ensure that the connection object is returned to the pool.

Commonly used connection pool

  • In the tradition, the c3p0 connection pool is used very much, and its speed is relatively fast, but when the system scale reaches a certain scale, it will make mistakes in connection creation and return. Alibaba's self-developed connection pool is the druid connection pool, which is known as the fastest connection pool in the world. In addition, it is made in China, so it is the first choice.
  • In addition, Japan also has a connection pool, which is used by springboot by default. It is said that the speed is also very fast, called HikariCP. Known as the best performance.

How to create a druid connection pool

Refer to the jar package of the druid connection pool in the project.

Create a singleton connection pool object for use in the project.

driverName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/students?serverTimezone=UTC&characterEncoding=utf-8&useSSL=false
user=huayunliufeng
password=*********
MaxActive=10
MinIdle=3
InitialSize=3
package util;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

/**
 * //                       .::::.
 * //                     .::::::::.
 * //                    :::::::::::
 * //                 ..:::::::::::'
 * //              '::::::::::::'
 * //                .::::::::::
 * //           '::::::::::::::..
 * //                ..::::::::::::.
 * //              ``::::::::::::::::
 * //               ::::``:::::::::'        .:::.
 * //              ::::'   ':::::'       .::::::::.
 * //            .::::'      ::::     .:::::::'::::.
 * //           .:::'       :::::  .:::::::::' ':::::.
 * //          .::'        :::::.:::::::::'      ':::::.
 * //         .::'         ::::::::::::::'         ``::::.
 * //     ...:::           ::::::::::::'              ``::.
 * //    ````':.          ':::::::::'                  ::::..
 * //                       '.:::::'                    ':'````..
 *
 * @author 华韵流风
 * @ClassName DruidPool
 * @Description TODO
 * @Date 2021/5/7 14:46
 * @packageName util
 */
public class DruidPool {

    //饿汉式,单例模式
    private static DruidDataSource pool = new DruidDataSource();

    //静态代码快,保证连接池对象在类加载过程中就完成初始化
    static {
        //对象连接池进行初始化,设置一些必须的参数,这些参数从外部文件中获取
        Properties pt = new Properties();//创建属性集合对象

        //把外部参数加载到该对象中
        InputStream inputStream = DruidPool.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            pt.load(inputStream);//利用字节输入流对象完成参数的加载
            //从集合中取出各参数,并设置连接池
            pool.setDriverClassName(pt.getProperty("driverName"));
            pool.setUrl(pt.getProperty("url"));
            pool.setUsername(pt.getProperty("user"));
            pool.setPassword(pt.getProperty("password"));
            //以上四个为必须要有的
            pool.setInitialSize(Integer.parseInt(pt.getProperty("InitialSize")));
            pool.setMinIdle(Integer.parseInt(pt.getProperty("MinIdle")));
            pool.setMaxActive(Integer.parseInt(pt.getProperty("MaxActive")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            DruidPooledConnection connection = pool.getConnection();
            System.out.println(connection);
        } catch (SQLException sqlException) {
            sqlException.printStackTrace();
        }
    }



}

Improve:

package util;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author 华韵流风
 * @ClassName DruidPool
 * @Description TODO
 * @Date 2021/5/7 14:46
 * @packageName util
 */
public class DruidPool {

    private static ThreadLocal<Connection> tl = new ThreadLocal<>();

    //饿汉式,单例模式
    private static final DruidDataSource pool = new DruidDataSource();

    //静态代码快,保证连接池对象在类加载过程中就完成初始化
    static {
        //对象连接池进行初始化,设置一些必须的参数,这些参数从外部文件中获取
        Properties pt = new Properties();//创建属性集合对象

        //把外部参数加载到该对象中
        InputStream inputStream = DruidPool.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            pt.load(inputStream);//利用字节输入流对象完成参数的加载
            //从集合中取出各参数,并设置连接池
            pool.setDriverClassName(pt.getProperty("driverName"));
            pool.setUrl(pt.getProperty("url"));
            pool.setUsername(pt.getProperty("user"));
            pool.setPassword(pt.getProperty("password"));
            //以上四个为必须要有的
            pool.setInitialSize(Integer.parseInt(pt.getProperty("InitialSize")));
            pool.setMinIdle(Integer.parseInt(pt.getProperty("MinIdle")));
            pool.setMaxActive(Integer.parseInt(pt.getProperty("MaxActive")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //该方法返回一个连接对象给线程,并保证同一线程中只有一个连接对象
    public static Connection getConnection() throws SQLException {
        DruidPooledConnection connection = null;
        System.out.println(Thread.currentThread().getName() + " 当前的连接数量:" + pool.getActiveCount() + " 当前空闲的连接数量:" + pool.getPoolingCount());
        if (tl.get() == null) {
            try {
                connection = pool.getConnection();
                //把连接对象放到ThreadLocal变量中,保证线程在各个方法中都可以使用
                tl.set(connection);
            } catch (SQLException sqlException) {
                sqlException.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 当前的连接数量:" + pool.getActiveCount() + " 当前空闲的连接数量:" + pool.getPoolingCount());
            return connection;
        } else {
            throw new SQLException("本线程已有连接对象!");
        }
    }

    //释放连接
    public static void releaseConnection() {
        if (tl.get() != null) {
            try {
                tl.get().close();
                tl.remove();
            } catch (SQLException sqlException) {
                sqlException.printStackTrace();
            }
        }
    }
}

Two, ThreadLocal class

java.lang Class ThreadLocal<T>, it is a basic API. This class provides thread-local variables, which are different from the general variables of other classes. For each ThreadLocal variable created, in a multithreaded environment, each Threads can get a copy of the variable. The copy is not a simple copy, but each thread maintains a different variable value of the ThreadLocal type.

In the thread, the set() method is used to assign the value of the variable, and the get() method is used to get the value of the variable. In different threads, no matter what value they give each, they will not affect each other.

package com.zhong.test;

import java.util.Random;

/**
 * @author 华韵流风
 * @ClassName TestDemo
 * @Description TODO
 * @Date 2021/5/7 15:29
 * @packageName com.zhong.test
 */
public class TestDemo {

    private static final ThreadLocal<Integer> tl = new ThreadLocal<>();

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                //在本线程内,给变量指定值,此值一旦指定,在本线程的所有方法中都可以使用
                tl.set(new Random().nextInt(100));
                outI();
            }).start();
        }

    }

    private static void outI(){
        //在本线程内,从ThreadLocal对象中得到该值
        System.out.println(Thread.currentThread().getName()+" "+tl.get());
    }
}

3. Learning methods at the project stage

  1. The learning path is from point -> line -> surface -> volume.
  • Points: all the knowledge points learned before.
  • Line: In a web project, when the request is sent to the response is received, the technology and related execution principles and steps used in the whole process. In the running process of the whole project, various functions are realized by the above various lines.
  • Surface: A module in the project, such as user management, commodity management, etc. The various functions performed on a specific representation tend to be grouped into the same module, which is closely related to the function of the project.
  • Body: The content of the project composed of various modules, pay attention to the interaction between the modules.
  1. In the project learning and development, we mainly focus on the process of designing the project and the process of the realization of its functions.
  2. Through the design process of the project, further study and consolidate the foundation of java.
  3. For the selection of items, try to choose representative ones.

Four, MVC design pattern

It is a specific mode in javaEE.

It is a structural model.

The structure and role of the MVC design pattern

  • M = model
  • V = view
  • C = controller
  • Modern web applications basically use the mvc design pattern.
  • The traditional design adopts model1 mode, which is based on jsp to design the system. All the code is written in the page. There are many shortcomings, including difficult to maintain, messy structure, messy code, poor readability, and serious coupling.
  • The MVC mode is divided into three parts according to the realization of each function. The advantage is to reduce the coupling. Each part realizes the function (high cohesion) that it should realize, so it is easy to maintain and has a clear structure.

How to divide the package structure in the project based on the requirements of mvc?

Any one class or interface is unique in the world.

The division of packages is either based on the level of content, or based on functional modules or a combination of the two.

Subcontracting method:

  • Organization name (mailbox)
  • project name
  • Module name
  • MVC level name (dao, service, controller)
  • Implementation class of the interface

The above division method is conducive to the classification management of various classes and interfaces, as well as the division of classes and interfaces.

Five, Dao's underlying implementation

You can write the code by yourself to achieve the underlying functions. If you write a lot of the underlying code, the problem is that some interfaces and classes at the bottom of jdbc will be used. Second, there will be a lot of repetitive code. Third, for common operations such as CRUD, The code writing steps are also similar. Based on the above situation, it is not advisable to write this kind of code without technical content repeatedly in the project, and it is not recommended to do so in the project.

Use the underlying code already written by others to implement the underlying functions. For the realization of different functions, you only need to call different methods and receive the required results. This is the recommended way.

It is recommended to use apache's common-dbutils as the underlying implementation. It provides a complete jar package, which can be copied to the project and used directly.

Understand the api provided by dbutils.

  • public class QueryRunner This class provides the main methods for implementing CRUD and batch updates. Each method has different overloading forms. First of all, the return class of the method is different, whether the method needs to create a connection, and whether the method needs to pass execution parameters.
  • update (parameter list...), the function of this kind of method, realize the function of addition, deletion and modification, the specific function is determined by the sql command.
  • query (parameter list...), to realize the query, there are many types of query results, so the method of selection is different for different queries.
  • batch (parameter list...), the parameters include a sql command and a two-dimensional array (multiple sets of parameters). You can give it multiple sets of parameters for the specified sql command and execute them separately. Call this method to combine the same sql with different The parameter combination of is executed repeatedly, and the number of executions is determined by the number of rows in the two-dimensional array.
  • The above three have a common feature: the method is divided into whether to pass the connection object.

Test the update method

  • The update method can be implemented for additions, deletions, and modifications, and it is up to you to decide whether to create a connection object.

Test query

One of the prerequisites for querying is to establish a corresponding po (persistent object) in the project for the form, which is a JavaBean.

There is an interface parameter interface ResultSetHandler<T> in the query method. The function of the interface can convert the query result into the specified result type (including single Bean, scalar value (original data type), Map, List, List<Map>, etc.), the interface has many implementation classes, and objects of different implementation classes represent a specific result type.

BeanHandler is a single Bean, MapHandler, returns Map, ScalarHandler returns a scalar value, BeanListHandler returns List<Bean>, and MapListHandler returns List<Map>. Note that when selecting them, you must combine the returned results and query conditions of the sql command.

package com.zhong.test;

import cn.softeem.test.po.User;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.junit.Test;
import util.DruidPool;

import javax.sound.midi.Soundbank;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @author 华韵流风
 * @ClassName DbUtilsTest
 * @Description TODO
 * @Date 2021/5/8 16:41
 * @packageName com.zhong.test
 */
public class DbUtilsTest {
    private final QueryRunner qr = new QueryRunner(DruidPool.getPool());

    @Test
    public void testUpdate() throws SQLException {
        String sql = "delete from user where username=?";
        Connection connection = DruidPool.getConnection();
        int count = qr.update(connection, sql, "sss");
        System.out.println(count);
        DruidPool.releaseConnection();
    }

    @Test
    public void testQuery1() throws SQLException {
        String sql = "select * from user where username=?";
        Connection connection = DruidPool.getConnection();
        User user = qr.query(connection, sql, new BeanHandler<>(User.class), "root");
        System.out.println(user);
        DruidPool.releaseConnection();
    }

    @Test
    public void testQuery2() throws SQLException {
        String sql = "select * from user where password=?";
        Connection connection = DruidPool.getConnection();
        List<User> users = qr.query(connection, sql, new BeanListHandler<>(User.class), "1234");
        for (User user : users) {
            System.out.println(user);
        }
        DruidPool.releaseConnection();
    }

    @Test
    public void testQuery3() throws SQLException {
        String sql = "select * from user where username=?";
        Connection connection = DruidPool.getConnection();
        Map user = qr.query(connection, sql, new MapHandler(), "root");
        for (Object o : user.entrySet()) {
            System.out.println(((Map.Entry)o).getKey()+" "+((Map.Entry)o).getValue());
        }
        DruidPool.releaseConnection();
    }
}

How to use QueryRunner well

  • In the project design of the realization, we must consider the handling of affairs, which is a problem that must be paid attention to in the realization of business logic.
  • There are two problems with the underlying implementation of QueryRunner. There is a distance between it and the project design. The first is transaction processing. The transaction method provided requires external connection objects. However, under the requirements of MVC mode, transaction control It is done in the service layer. If its transaction method is used in the service, then the connection object is used in the service, and the connection object is an object belonging to the Dao layer, which affects the low coupling requirements of the structure. Second, it does not take into account that the same transaction contains multiple additions, deletions, and modifications.
  • Based on the second point, the QueryRunner class cannot be used directly in the project, it needs to be modified,

To transform the connection pool tool, consider the process of transaction processing, and add a new class to encapsulate QueryRunner.

In the code that encapsulates the QueryRunner, the batch method must be a method for addition, deletion, and modification. After the operation is completed, the connection must be released. This operation is an operation in a transaction. The release of the connection object should be completed by the method of submitting or rolling back the transaction. The releaseConnection() method cannot close the current connection during the duration of the transaction, otherwise the transaction will go wrong.

package cn.softeem.taobao.utils;

import com.alibaba.druid.pool.DruidDataSource;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @author 华韵流风
 * @ClassName DruidPool
 * @Description TODO
 * @Date 2021/5/7 14:46
 * @packageName util
 */
public class DruidPool {

    private static final ThreadLocal<Connection> tl = new ThreadLocal<>();

    /**
     * 连接池,饿汉式,单例模式
     */
    private static final DruidDataSource pool = new DruidDataSource();

    //静态代码快,保证连接池对象在类加载过程中就完成初始化
    static {
        //对象连接池进行初始化,设置一些必须的参数,这些参数从外部文件中获取
        //创建属性集合对象
        Properties pt = new Properties();

        //把外部参数加载到该对象中
        InputStream inputStream = DruidPool.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            //利用字节输入流对象完成参数的加载
            pt.load(inputStream);
            //从集合中取出各参数,并设置连接池
            pool.setDriverClassName(pt.getProperty("driverName"));
            pool.setUrl(pt.getProperty("url"));
            pool.setUsername(pt.getProperty("user"));
            pool.setPassword(pt.getProperty("password"));
            //以上四个为必须要有的
            pool.setInitialSize(Integer.parseInt(pt.getProperty("InitialSize")));
            pool.setMinIdle(Integer.parseInt(pt.getProperty("MinIdle")));
            pool.setMaxActive(Integer.parseInt(pt.getProperty("MaxActive")));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 该方法返回一个连接对象给线程,并保证同一线程中只有一个连接对象
     *
     * @return Connection
     * @throws SQLException SQL
     */

    public static Connection getConnection() throws SQLException {
        if (tl.get() == null) {
            //当前线程有可能拿到一个不存在于tl中的连接对象
            return pool.getConnection();
        }
        return tl.get();
    }

 public static void beginTransaction() throws SQLException {
        Connection connection = tl.get();
        if (connection != null) {
            throw new SQLException("当前已有事务!不能开始");
        }
        connection = pool.getConnection();
        //设置为手动提交
        connection.setAutoCommit(false);
        tl.set(connection);
    }

    public static void commitTransaction() throws SQLException {
        Connection connection = tl.get();
        if (connection == null) {
            throw new SQLException("当前没有事务!不能提交");
        }
        //提交事务
        connection.commit();
        //释放连接
        connection.close();
        tl.remove();
    }

    public static void rollbackTransaction() throws SQLException {
        Connection connection = tl.get();
        if (connection == null) {
            throw new SQLException("当前没有事务");
        }
        //回滚事务
        connection.rollback();
        //释放连接
        connection.close();
        tl.remove();
    }

	//关闭连接
    public static void releaseConnection(Connection connection) throws SQLException {
        Connection threadCon = tl.get();
        if (threadCon == null) {
            if (connection != null) {
                connection.close();
            }
        }
    }
}
package util;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import util.DruidPool;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * @author 华韵流风
 * @ClassName TxQueryRunner
 * @Description TODO
 * @Date 2021/5/8 18:00
 * @packageName com.zhong.test
 */
public class TxQueryRunner extends QueryRunner {
    @Override
    public int[] batch(String sql, Object[][] params) throws SQLException {
        Connection connection = DruidPool.getConnection();
        int[] result = super.batch(connection, sql, params);
        DruidPool.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
        Connection connection = DruidPool.getConnection();
        T result = super.query(connection, sql, rsh, params);
        DruidPool.releaseConnection(connection);
        return result;
    }

    @Override
    public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
        Connection connection = DruidPool.getConnection();
        T result = super.query(connection, sql, rsh);
        DruidPool.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql) throws SQLException {
        Connection connection = DruidPool.getConnection();
        int result = super.update(connection, sql);
        DruidPool.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql, Object param) throws SQLException {
        Connection connection = DruidPool.getConnection();
        int result = super.update(connection, sql,param);
        DruidPool.releaseConnection(connection);
        return result;
    }

    @Override
    public int update(String sql, Object... params) throws SQLException {
        Connection connection = DruidPool.getConnection();
        int result = super.update(connection, sql,params);
        DruidPool.releaseConnection(connection);
        return result;
    }
}
package com.zhong.test;

import cn.softeem.test.po.User;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.junit.Test;
import util.DruidPool;
import util.TxQueryRunner;
import java.sql.SQLException;
import java.util.Arrays;

/**
 * @author 华韵流风
 * @ClassName DbUtilsTest
 * @Description TODO
 * @Date 2021/5/8 16:41
 * @packageName com.zhong.test
 */
public class DbUtilsTest {
    private TxQueryRunner tqr = new TxQueryRunner();

    @Test
    public void testUpdate() throws SQLException {
        String sql = "insert into user values (?,?)";
        String[][] params = {{"aaa", "1234"}, {"bbb", "1234"}};
        DruidPool.beginTransaction();
        try{
            int[] result = tqr.batch(sql, params);
            DruidPool.commitTransaction();
            System.out.println(Arrays.toString(result));

        }catch (Exception e){
            e.printStackTrace();
            DruidPool.rollbackTransaction();
        }
    }

    @Test
    public void testUpdate1() throws SQLException {
        String sql = "update user set password=? where username=?";
        String[][] params = {{"1234", "aaa"}};
        DruidPool.beginTransaction();
        try{
            int[] result = tqr.batch(sql, params);
            System.out.println(Arrays.toString(result));
            DruidPool.commitTransaction();
        }catch (Exception e){
            e.printStackTrace();
            DruidPool.rollbackTransaction();
        }
    }
    @Test
    public void testQuery() throws SQLException {
        String sql = "select * from user where username=?";
        User result = tqr.query(sql, new BeanHandler<>(User.class),"root");
        System.out.println(result);
    }
}

6. Project

  1. New product
<input type="file" name="pimg" class="test"/><!--文件上传的控件-->

To implement a universal Servlet, all other servets in the project inherit it. The advantage is that different processing methods can be defined for different requests, and the code set of the request can be sent to the client in a unified manner. For common operations such as request redirection, Request forwarding can be implemented in a simpler way.

package cn.softeem.taobao.utils;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;

/**
 * @author 华韵流风
 * @ClassName ${NAME}
 * @Description TODO
 * @Date 2021/5/11 16:27
 * @packageName ${PACKAGE_NAME}
 * 在整个后台,主要功能是商品管理,对商品的所有操作统一使用同一个Servlet
 */
@WebServlet(name = "BaseServlet")
public class BaseServlet extends HttpServlet {
    /**
     * 以下方法可以对所有请求进行一般化处理,以达到三个目的
     *
     * @param req req
     * @param res res
     * @throws ServletException servlet
     * @throws IOException      io
     */
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        //转换类型
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        //1、对客户端的输出统一为utf-8的编码
        res.setContentType("text/html;charset=utf-8");
        //2、得到method的参数值
        String method = req.getParameter("method");
        try {
            //3、判断当前servlet中是否有method方法,如果有就调用该方法,用到了反射技术
            Method processMethod = this.getClass().getDeclaredMethod(method, HttpServletRequest.class, HttpServletResponse.class);
            //4、方法存在,也就是子类中添加的处理请求的方法,接收方法的返回值
            try {
                String result = (String) processMethod.invoke(this, req, res);
                //判断是重定向还是转发
                int pos = result.lastIndexOf(':');
                //得到冒号前的字符
                char ch = result.charAt(0);
                String path = result.substring(pos + 1);
                //转发
                if (ch == 'f') {
                    req.getRequestDispatcher(path).forward(req, res);
                } else {
                    //重定向,用绝对路径
                    response.sendRedirect(request.getContextPath() + path);
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("指定的方法不存在!");
        }

    }
}

The value of enctype="application/x-www-form-urlencoded" determines the method of submitting data in the form, application/x-www-form-urlencoded means using the default method, and multipart/from-data means using a stream to transmit , If it is a file upload, use this.

If the addition fails, the exception will be captured and processed in the service. The client does not know that the addition failed, and the situation is suffocated by the exception. This should not be done. The exception needs to be thrown to the controller for unified exception handling.

The enctype of the form = "multipart/form-data", the common control of the form and the data of the file will be transmitted to the server in a stream, the stream is received in the server program, the stream is parsed, and the common control data is separated from the data of the file. Come out and deal with them separately.