1. Introductie
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.
Instructiefilmpje voor het snel opbouwen van een class
3. SQL-connectie in Netbeans
We gaan nu eerst een connectie met de database maken in Netbeans. Om dit werkend te krijgen heb je de MySQL connector nodig. Download deze eerst als zip en plaats hem ergens in je Mijn Documenten. Bewaar deze voor iedere les. Dit is de driver die je kunt gebruiken om een database connectie te maken. We gebruiken deze op twee manieren:
- In het tabblad services om te testen of de database werkt.
- In de Payara server (dit is een verbeterde variant van de Glassfish server).
Laten we eerst kijken of de driver werkt. Ga naar het tabblad services en maak een nieuwe databaseconnectie bij Databases.. Klik hiervoor op New Connection.
Voeg vervolgens de driver toe door te klikken op Add. Kies tevens voor de MySQL Connector/J driver in het bovenste vakje.
Klik op next en dan kom je in het volgende scherm.
In het vakje Database type je de naam van de database. De JDBC url wordt hierbij automatisch aangepast. Wat dit is, komt later. Klik nu op Test Connection en er komt een melding Connection Succeeded. Je weet nu dat het werkt. Voor de applicatie heb je daar nog niet zoveel aan. We moeten dit eerst nog werkend maken in onze Payara of Glassfish server. Dit is vrij eenvoudig. Kopieer de driver naar het volgende mapje: C:\Users\<leerlingnummer>\Payara_Server\glassfish\domains\domain1\lib waarbij we ervan uitgaan dat je in plaats van de Glassfish server de Payara server gebruikt.
Je bent nu in staat om binnen de applicatie een connectie te maken.
4. De connectie met de database
We gaan nu de daadwerkelijke connectie met de database maken. In de class User maak je drie variabelen genaamd connection, statement en resultset en tevens fix je de imports zoals in de afbeelding is te zien. Gebruik hiervoor de rechtermuisknop in de class.
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.
5. 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:
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:
- Het resultaat van die rij, kan een object zijn
- Of er wel (true) of niet (false) een resultaat is.
6. 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.
7. 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.
8. 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:
- Er wordt een ArrayList userList gemaakt. Een ArrayList is een lijst waarin objecten opgeslagen kunnen worden, in dit geval het object User.
- 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.
- 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.
- 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"}))
9. 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.
10. 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>