servlets

Een interactieve applicatie - deel 3

1. Werken met de database

Een applicatie is meestal pas echt interactief als er een database aan gekoppeld is. Wat een database is kun je leren in de cursus MySQL. Wat we in dit deel gaan doen is de database aan de applicatie koppelen. MySQL is daar heel geschikt voor.

We zullen eerst beginnen met het maken van een database. Maak een nieuwe database die je dezelfde naam geeft als je applicatie. Maak vervolgens binnen de database de tabel users met de volgende kolommen:

id(int 11) username (varchar(255)) password (varchar(255)

 We gaan in deze cursus niet uitleggen hoe dit wordt gemaakt. Hiervoor verwijzen we naar de cursus MySQL.

 

2. De class user maken

Maak vervolgens een class User. We gaan ervan uit dat je deze class maakt in een nieuwe applicatie dus dat deze nog niet aanwezig is. Maak een constructor met de drie klassevariabelen als parameters. Maak ook de getters en de setters.

class user

 

Hieronder nog een filmpje hoe je een class snel kunt opbouwen met behulp van automatische functies in Netbeans

3. SQL-connectie in Netbeans

We gaan nu eerst een connectie met de database maken in Netbeans. Ga naar het tabblad services en maak een nieuwe databaseconnectie bij Databases.

https://images.computational.nl/galleries/servlets/2017-04-12_09-03-11.png

Kies hierna voor de MySQL driver.

https://images.computational.nl/galleries/servlets/2017-04-12_09-03-55.png

Klik hierna op Next en nu zie je het volgende scherm:

https://images.computational.nl/galleries/servlets/2017-04-12_09-04-33.png

In eerste instantie staat de database op mysql. Verander de naam van de naam van de database in de naam van de database die je hebt gemaakt. Bij ons heet de database loginuser en de naam van onze applicatie is eveneens loginuser. Als je de naam verandert zul je zien dat de JDBC URL ook verandert. JDBC staat voor Java Database Connectivity.

Als je de naam goed hebt ingevuld komt er de melding te staan Connection Succeeded, een teken dat er een connectie is. Nu kun je de URL kopiëren en gebruiken als string voor de dbURL in de class User.

 

4. De MySQL driver toevoegen in je applicatie

In het vorige onderdeel kon je zien dat er een MySQL driver aanwezig is in Glassfish. Om dit in je applicatie actief te krijgen moet je deze eerst daaraan toevoegen. Klik hiervoor in je applicatie met de rechtermuisknop op Libraries en daarna op Add Library.

https://images.computational.nl/galleries/servlets/2017-04-12_09-58-27.png

Kies daarna de MYSQL JDBC Driver en klik op Add Library. Hierna is de bibliotheek toegevoegd.

https://images.computational.nl/galleries/servlets/2017-04-12_10-02-24.png

5. De connectie met de database

We gaan nu de daadwerkelijke connectie met de database maken. In de class User maak je drie variabelen connection, statement en resultset en tevens fix je  de imports zoals in de afbeelding is te zien.

three variables

 Maak hierna aan het einde van de class, dus onder de getters en setters, een private methode openConnection(). De code van deze methode is als volgt:

private void openConnection() throws SQLException, ClassNotFoundException {
        String dbURL = "jdbc:mysql://localhost:3306/loginuser";
        String username = "root";
        String password = "";

        Class.forName("com.mysql.jdbc.Driver");
        //making the connection
        connection = DriverManager.getConnection(dbURL, username, password);
        statement = (Statement) connection.createStatement();
    }

Met deze code ben je in staat om de database te raadplegen of te manipuleren. Dat zullen we doen in de volgende lessen.

Nog iets over waarom er staat throws SQLException, ClassNotFoundException. Dit heeft te maken met het zogenaamde try and catch systeem in Java. Je kunt namelijk een foutmelding, bijvoorbeeld als de database niet wordt gevonden, helemaal op je eigen manier afhandelen. Een Exception is een foutmelding in Java. Als je deze throwt dan wil dat zeggen dat je deze in een andere methode afhandelt. Deze wordt dan daar gecacht met behulp van try and catch. Hoe? Dat zie je in de volgende lessen.

6. Users in de database opvragen

Nu je een connectie hebt kun je gegevens van de database opvragen. We zullen als voorbeeld users opvragen. Maak hiervoor de volgende methode:

public ArrayList getUsers() throws SQLException, ClassNotFoundException {
    openConnection();

}

Ook in deze methode kiezen we ervoor om evt. foutmeldingen pas in een bovenliggende class af te handelen. Dat zal in ons geval de servlet zijn.

Netbeans geeft nog een foutmelding. Eerst doe je met de rechtermuisknop Fix Imports om de ArrayList te importeren. Vervolgens lossen we de foutmelding op waarbij de compiler van Netbeans verwacht dat er een lijst van users wordt teruggegeven. We zullen alvast een lege lijst teruggeven.

public ArrayList<String> getUsers() throws SQLException, ClassNotFoundException {
    openConnection();
    ArrayList<String> names = new ArrayList();;
    return names;
}

Wat is een ArrayList?

Een ArrayList is een lijst waarin van alles geplaatst mag worden. Je mag er ook objecten in plaatsen zoals users. Schematisch ziet dat er als volgt uit:

https://images.computational.nl/galleries/servlets/2017-04-12_11-42-24.png

De lijst mag groeien zolang er geheugencapaciteit is. Dit is anders dan een array, waarbij je van tevoren de capaciteit moet aangeven. Het is dus iets handiger.

Breid nu de code uit door eerst de query te maken (queries heb je leren maken in de cursus MySQL). Vervolgens voeren we die uit en lezen we het resultaat uit met een resultset.

public ArrayList<String> getUsers() throws SQLException, ClassNotFoundException {
    openConnection();
    String query = "SELECT username, password FROM user";
    ArrayList<String> names = new ArrayList();;
    resultset = statement.executeQuery(query);
    while (resultset.next()) {
        names.add(resultset.getString("username"));
    }
    return names;
}

Je hebt nu een methode die je kunt gebruiken in de servlet. Dit zullen we in de volgende les doen vanuit een lege servlet.

Wat is resultset.next()?

In een resultset komt het resultaat. Je kunt zo'n resultset enigzins vergelijken met een tekstbestand wat er zo uitziet:

0.
1.rij 1 van de database
2.rij 2 van de database
3.etc. 

Als je zo'n resultset wilt gebruiken, stel je dan voor dat de cursus op rij 0 staat. Zodra je resultset.next() doet geeft deze methode twee dingen terug:

  1. Het resultaat van die rij, kan een object zijn
  2. Of er wel (true) of niet (false) een resultaat is.

7. Een aparte databaseclass

Tot nu toe hebben we de de methode openConnection() in de class User gebruikt. Maar stel nu dat we meer classes maken, dan moeten we daar overal een openConnection() methode in plaatsen. Het zou handiger zijn als deze code op één plek staat. Dit is mogelijk omdat bij een object geörienteerde taal het pincipe van overerving is. We gaan nog eens even terug naar Greenfoot.

Hierboven erft de class LittleRedCap van BaseClass. Alle methoden die in BaseClass zitten, zitten nu ook in LittleRedCap. Hoe deden we dat ook al weer?

public class LittleRedCap extends BaseClass

Met het codewoord extends kunnen we een class laten erven. Dit kunnen we ook doen met de class User. Dit zullen we doen in de in de opdrachten.

8. Classes en verantwoordelijkheid

De naam van de class User suggereert dat de class verantwoordelijk is voor één user. Dus de class kan een object maken waarin één user tegelijk zit. Desondanks hebben wij wel de methode public ArrayList getUsers() in deze class gemaakt. Dat klopt niet want met deze methode kunnen we namelijk meerdere users in één object uitvoeren. Dat is teveel verantwoordelijkheid en maakt de code onoverzichtelijk.

9. De bijbehorende servlet maken

We gaan nu een servlet maken. Noem deze UserServlet en maak de servlet aan zoals is uitgelegd in deel 2. Verwijder alle code van de methode processRequest(). We zullen in onderstaand filmpje laten zien hoe de servlet wordt opgebouwd. De beschrijving hiervan is als volgt:

  1. Er wordt een ArrayList userList gemaakt. Een ArrayList is een lijst waarin objecten opgeslagen kunnen worden, in dit geval het object User.
  2. Vervolgens wordt er een object Users gemaakt. Dit is om de methode users.getUsers(); te kunnen uitvoeren. Deze methode haalt alle users op uit de database en plaats deze in de userList.
  3. Omdat de methode users.getUsers(); een SQLException of een ClassNotFoundException kan genereren verplicht Java ons om een zogenaamde try and catch eromheen te zetten. Een try and catch werkt als volgt: in de try wordt de code uitgeprobeerd. Als de code een foutmelding geeft wordt er een exception "opgegooid" en wordt het catch-gedeelte uitgevoerd.
  4. Als laatste wordt er een session Attribuut gemaakt van de userList en wordt de servlet geforward naar user.jsp. Deze file zullen we in de volgende les aanmaken. De servlet moet worden getriggerd op /user in een zogenaamde annotation (@WebServlet(name = "UserServlet", urlPatterns = {"/user"}))

10. de file user.jsp aanmaken

Hieronder de code van de file user.jsp

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>JSP Page</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <th>username</th>
                <th>password</th>
            </tr>
            <c:forEach items="${userList}" var="user">
                <tr>
                    <td><c:out value="${user.username}"/></td>
                    <td><c:out value="${user.password}"/></td>
                </tr>
            </c:forEach>
        </table>
    </body>
</html>

In deze code zit een zogenaamde loop. Om deze werkend te kunnen maken is een zogenaamde tag-library nodig.

<%@ taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %>

De foreach loop werkt als volgt. In het attribuut items plaats je het session attribuut en in het attribuut var geef je de variabelen (die in de arraylist zitten) een naam. In dit geval is user een logische naam.

opmerking:

In bovenstaande code wordt de zogenaamde <c:out value="${user.username}"/> gebruikt. Dat hoeft niet persé. De jsp-variabele ${user.username} zal ook gewoon werken.

11. Zelf fouten tonen

Met behulp van try and catch is het mogelijk om de zogenaamde exceptions af te vangen. Een voorbeeld van een exception is als volgt:

Exception in thread "main" com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure 
    The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1122)
    at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2260)
    at com.mysql.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:787)
    at com.mysql.jdbc.JDBC4Connection.<init>(JDBC4Connection.java:49)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
    at com.mysql.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:357)
    at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:285)
    at java.sql.DriverManager.getConnection(DriverManager.java:582)
    at java.sql.DriverManager.getConnection(DriverManager.java:207)
    at SqlTest.main(SqlTest.java:22)
Caused by: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
    The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:409)
    at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1122)
    at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:344)
    at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2181)
    ... 12 more
Caused by: java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:432)
    at java.net.Socket.connect(Socket.java:529)
    at java.net.Socket.connect(Socket.java:478)
    at java.net.Socket.<init>(Socket.java:375)
    at java.net.Socket.<init>(Socket.java:218)
    at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:256)
    at com.mysql.jdbc.MysqlIO.<init>(MysqlIO.java:293)
    ... 13 more

 Dit soort fouten wil je liever niet aan de gebruiker tonen. We kunnen dat op een nettere manier doen.

Netbeans had al automatisch de volgende code gegenereerd:

Logger.getLogger(UserServlet.class.getName()).log(Level.SEVERE, null, ex);

Dit houdt in de hier de exception wordt afgevangen (met de variabele ex) en vervolgens wordt doorgestuurd naar de server. In de logfile van de server kunnen we dan de fout nalezen en hopelijk oplossen.

De gebruiker kunnen we de volgende fout tonen:

catch (SQLException | ClassNotFoundException ex) {
    Logger.getLogger(UserServlet.class.getName()).log(Level.SEVERE, null, ex);
    session.setAttribute("message", "Er gaat iets mis met de connectie van de database.");

}

En in de jsp kunnen we dit message attribute als volgt tonen:

 <p style="color:red">${message}</p>

12. pom file instellingen voor netbeans 11

<dependencies>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>${jakartaee}</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
</dependencies>