Hosting Java Servlet on Apache Tomcat

The servlet uses JDBC. To make the servlet work, put the jar file with the driver in the folder WEB-INF/lib/.
When running the servlet, the driver is not picked up. What am I doing wrong?

UPD: The problem was solved by explicitly creating a driver object Class.forName("com.mysql.jdbc.Driver"). I wonder why this was required here. In a normal Java SE application, it worked even without explicit creation.

Project structure
structure

Java Servlet:

package com.company.app.logic;

import java.sql.ResultSet;
import java.sql.SQLException;

public class Item {

    private int itemId;
    private String itemName;

    public Item(ResultSet rs) {
        try {
            this.itemId = rs.getInt(1);
            this.itemName = rs.getString(2);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public String getItemName() {
        return itemName;
    }

    public int getItemId() {
        return itemId;
    }

    public String toString() {
        return itemName;
    }
}


package com.company.app.logic;
import java.sql.*;
import java.util.*;
public class Main {
    private static Main instance = new Main();
    private static Connection connection;
    private Main() {
       try {
           Class.forName("com.mysql.jdbc.Driver");

           String url = "jdbc:mysql://localhost:3306/maindb";
           connection = DriverManager.getConnection(url, "root", "root");
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
    public static Main getInstance() {
        return instance;
    }
    public List<Item> getItems() {
        List<Item> items = new ArrayList<>();
        Statement statement = null;
        ResultSet rs = null;
        try {
            statement = connection.createStatement();
            rs = statement.executeQuery("SELECT * FROM items");
            while (rs.next()) {
                Item g = new Item(rs);
                items.add(g);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return items;
    }
}


package com.company.app.web;

import com.company.app.logic.Item;
import com.company.app.logic.Main;

import javax.servlet.ServletException;
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.io.PrintWriter;
import java.util.List;

@WebServlet("/items")
public class MainServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");

        PrintWriter pw = resp.getWriter();
        pw.println("<table border=1>");
        try {
            List<Item> l = Main.getInstance().getItems();
            for (Item item : l) {
                pw.println("<tr>");
                pw.println("<td>" + item.getItemId() + "</td>");
                pw.println("<td>" +item.getItemName() + "</td>");
                pw.println("</tr>");
            }
        } catch (Exception e) {
            throw new ServletException(e);
        }
        pw.println("</table>");
    }
}

A regular Java SE project:

package com.company.app.logic;

import java.sql.ResultSet;
import java.sql.SQLException;

public class Item {

    private int itemId;
    private String itemName;

    public Item(ResultSet rs) {
        try {
            this.itemId = rs.getInt(1);
            this.itemName = rs.getString(2);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public String getItemName() {
        return itemName;
    }

    public int getItemId() {
        return itemId;
    }

    public String toString() {
        return itemName;
    }
}


package com.company.app.logic;
import java.sql.*;
import java.util.*;
public class Main {
    private static Main instance = new Main();
    private static Connection connection;
    private Main() {
       try {

           String url = "jdbc:mysql://localhost:3306/maindb";
           connection = DriverManager.getConnection(url, "root", "root");
       } catch (Exception e) {
           e.printStackTrace();
       }
    }
    public static Main getInstance() {
        return instance;
    }
    public List<Item> getItems() {
        List<Item> items = new ArrayList<>();
        Statement statement = null;
        ResultSet rs = null;
        try {
            statement = connection.createStatement();
            rs = statement.executeQuery("SELECT * FROM items");
            while (rs.next()) {
                Item g = new Item(rs);
                items.add(g);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (statement != null) {
                    statement.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return items;
    }

    public static void main(String[] args) {
        Main m = Main.getInstance();
        m.getItems().forEach(System.out::println) 
    }
}
Author: jisecayeyo, 2016-08-18

1 answers

The problem was solved by explicitly creating the driver object Class.forName("com.mysql.jdbc.Driver"). I wonder why this was required here. In a normal Java SE application, it worked even without explicit creation.

This is normal. If you look at the DriverManager code (or read the JDBC spec), you will see that it requires calling the static registerDriver() method. Obviously, to do this, the driver class must pull this method, and the earliest place where this can be done is the static initialization block of the class. (static {...}).

Static initialization of the class occurs when it is loaded by the classloader. Calling the class by the name Class.forName("com.mysql.jdbc.Driver") just causes the classloader to load the class, and the class in turn registers in DriverManger. Until then, it lies quietly in CLASSPATH and no one knows about it.


Now I understand, your question can be rephrased like this: "Why does Tomcat not find the JDBC driver through the service provider?".

In the case of a JavaSE application, the mechanism is used downloads via ServiceLoader from the JDBC 4 specification. At startup, CLASSPATH is scanned for the file (s) META-INF/services/java.sql.Driver, from which DriverManager can get the names of classes that implement this interface and register them automatically.

The problem with Tomcat is described on the official website and is related to a more complex structure of boot loaders and memory leaks. In short: Tomcat searches for drivers through the service provider only at its start, and not at the deployment / start of each application. And available it only has jar files in the $CATALINA_BASE/lib directory (shared by all web applications). This feature is not available for classes inside your application from WEB-INF/lib.

In addition, if you use the method with manual driver registration (Class.forName), it is strongly recommended to unregister the driver when the application is stopped by implementing ServletContextListener.

 3
Author: Nofate, 2016-08-18 13:48:20