Download the client. Run with:
-java -jar client.jar server:8080
-
- ```
-
-## Build the client and server jars
-
-Build the client Jar and copy it to your server's resources web directory so that it can be downloaded from the server when someone wants to play chess.
-
-```sh
-mvn package -pl client -DskipTests
-cp client/target/client-jar-with-dependencies.jar server/src/main/resources/web/client.jar
-```
-
-Now we can build the server jar.
-
-```sh
-mvn package -pl server -DskipTests
-```
-
-This will create the following `server/target/server-jar-with-dependencies.jar`
-
-## Copy server code
-
-With the server and client built, we can copy the server jar up to the EC2 server using the secure copy utility `scp`.
-
-```sh
-scp -i youkeypairhere.pem server/target/server-jar-with-dependencies.jar ec2-user@youripaddresshere:server.jar
-```
-
-## Start up the server
-
-Now you are ready to start up the server.
-
-1. SSH into server as described above
-1. Run your server using the Java runtime and your server.jar
-
-```sh
-java -jar server.jar
-```
-
-This should output that the server is successfully running on the assigned port. Now you should be able to open up a browser on any computer in the world and see the web interface for your chess server. When you open your browser, put the public IP address and port number of your EC2 Instance. This should be something like `http://youripaddresshere:8080`.
-
-
-
-You can then click the link that you created to download the client. Note that you may need to tell your browser to load your web page even though it is not secure, as well as specifying that you want to download the jar file.
-
-
-
-Now you can use the Java runtime to run your chess client. Make sure you provide the ip address and port number as parameters.
-
-```sh
-java -jar client.jar youripaddress:8080
-```
-
-
-
-## Make your chess server start whenever you start the AWS instance
-
-Invoking your chess server from ssh will require you to keep the ssh terminal open. You can make your chess server start automatically every time your AWS instance is started by following these instructions:
-
-1. SSH into your server as described above
-2. Create and open a service description file: `sudo nano /etc/systemd/system/chess_server.service`
-3. Enter and save the following in the Nano code editor window:
-
-```[Unit]
-Description=Chess Server
-After=network.target
-
-[Service]
-Type=simple
-ExecStart=java -jar /home/ec2-user/server.jar 8080
-
-[Install]
-WantedBy=multi-user.target
-sudo systemctl enable chess_server
-```
-
-4. Enable the chess server service: `sudo systemctl enable chess_server`
-5. Start the chess server service: `sudo systemctl start chess_server`
-6. Check the status of the chess server service: `sudo systemctl status chess_server`
-7. To confirm that the service starts whenever the AWS instance is started, stop and restart the AWS instance from the AWS console or use this command: `sudo reboot`
-
-Have fun! You have just taken the first step in becoming the world's best chess server.
-
-## Videos
-
-- 🎥 [Overview (1:41)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=10672ba0-ba2d-4c9b-b6c1-b1ae01071d55&start=0) - [[transcript]](https://github.com/user-attachments/files/17707228/CS_240_Deploying_your_Chess_Server_on_AWS_Overview_Transcript.pdf)
-- 🎥 [Launch an EC2 Instance (14:42)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=feea3668-a904-402d-97d1-b1ae0107c46d&start=0) - [[transcript]](https://github.com/user-attachments/files/17707232/CS_240_Launch_an_EC2_Instance_Transcript.pdf)
-- 🎥 [Setup Your EC2 Instance (13:42)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=a5252593-01e6-479b-8532-b1ae010c4e9b&start=0) - [[transcript]](https://github.com/user-attachments/files/17707242/CS_240_Set_Up_Your_EC2_Instance_Transcript.pdf)
-- 🎥 [Modify Your Client Code (7:50)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=c20793cb-452e-4a52-bf1d-b1ae01121603&start=0) - [[transcript]](https://github.com/user-attachments/files/17707251/CS_240_Modify_Your_Client_Code_Transcript.pdf)
-- 🎥 [Deploy Code and Start the Server (17:49)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=d0fcb3f4-5aea-44b1-af53-b1ae01149092&start=0) - [[transcript]](https://github.com/user-attachments/files/17707258/CS_240_Deploy_Code_and_Start_the_Server_Transcript.pdf)
-- 🎥 [Configure Your Chess Server to Start When Your EC2 Instance Starts (8:39)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=56af2673-5e59-4019-8e49-b1ae0119d612&start=0) - [[transcript]](https://github.com/user-attachments/files/17707267/CS_240_Configure_Your_Chess_Server_to_Start_When_Your_EC2_Instance_Starts_Transcript.pdf)
diff --git a/instruction/aws-chess-server/awsEc2Selection.png b/instruction/aws-chess-server/awsEc2Selection.png
deleted file mode 100644
index 11ae7044..00000000
Binary files a/instruction/aws-chess-server/awsEc2Selection.png and /dev/null differ
diff --git a/instruction/aws-chess-server/chessServerWebInterface.png b/instruction/aws-chess-server/chessServerWebInterface.png
deleted file mode 100644
index 55cc6c89..00000000
Binary files a/instruction/aws-chess-server/chessServerWebInterface.png and /dev/null differ
diff --git a/instruction/aws-chess-server/createSecurityGroup.png b/instruction/aws-chess-server/createSecurityGroup.png
deleted file mode 100644
index 7842eadc..00000000
Binary files a/instruction/aws-chess-server/createSecurityGroup.png and /dev/null differ
diff --git a/instruction/aws-chess-server/ec2InstanceSettings.png b/instruction/aws-chess-server/ec2InstanceSettings.png
deleted file mode 100644
index fa58e3fa..00000000
Binary files a/instruction/aws-chess-server/ec2InstanceSettings.png and /dev/null differ
diff --git a/instruction/aws-chess-server/ignoreSecurityWarnings.png b/instruction/aws-chess-server/ignoreSecurityWarnings.png
deleted file mode 100644
index 3dad78b7..00000000
Binary files a/instruction/aws-chess-server/ignoreSecurityWarnings.png and /dev/null differ
diff --git a/instruction/aws-chess-server/inboundRules.png b/instruction/aws-chess-server/inboundRules.png
deleted file mode 100644
index ecb7102a..00000000
Binary files a/instruction/aws-chess-server/inboundRules.png and /dev/null differ
diff --git a/instruction/aws-chess-server/install.sh b/instruction/aws-chess-server/install.sh
deleted file mode 100644
index 56f5551f..00000000
--- a/instruction/aws-chess-server/install.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-sudo mkdir /opt/chess
-
-sudo useradd -r chess
-sudo chown -R chess:chess /opt/chess
-
-echo "DAWN Installing service"
-sudo mv /opt/chess/service.sh /etc/init.d/chess
-sudo sed -i -e 's/\r//g' /etc/init.d/chess #Make sure we don't have any /r in the script
-sudo chmod +x /etc/init.d/chess
-sudo chkconfig chess on #Enable the service for runlevels 2, 3, 4, and 5
-sudo ln -s /opt/chess/chess /usr/bin/chess #Create the symlink to point to the actual binaries
-sudo systemctl daemon-reload #Tell sysv that new scripts are loaded
-sudo setcap 'cap_net_bind_service=+ep' /opt/chess/chess #Enable use of port 80
-sudo service chess start
\ No newline at end of file
diff --git a/instruction/aws-chess-server/instanceType.png b/instruction/aws-chess-server/instanceType.png
deleted file mode 100644
index e587d7da..00000000
Binary files a/instruction/aws-chess-server/instanceType.png and /dev/null differ
diff --git a/instruction/aws-chess-server/playingChess.png b/instruction/aws-chess-server/playingChess.png
deleted file mode 100644
index 3d6d80cf..00000000
Binary files a/instruction/aws-chess-server/playingChess.png and /dev/null differ
diff --git a/instruction/aws-chess-server/securityGroup.png b/instruction/aws-chess-server/securityGroup.png
deleted file mode 100644
index 3d565a8c..00000000
Binary files a/instruction/aws-chess-server/securityGroup.png and /dev/null differ
diff --git a/instruction/aws-chess-server/service.sh b/instruction/aws-chess-server/service.sh
deleted file mode 100644
index 11c95f6b..00000000
--- a/instruction/aws-chess-server/service.sh
+++ /dev/null
@@ -1,85 +0,0 @@
-#!/bin/sh
-#
-# Chess
-#
-# chkconfig: - 64 36
-# Default-Start: 2 3 4 5
-# Default-Stop: 0 1 2 3 4 6
-# Required-Start:
-# description: Chess
-# processname: chess
-# pidfile: none
-# lockfile: /var/lock/subsys/chess
-
-# Source function library.
-. /etc/rc.d/init.d/functions
-
-# Source networking configuration.
-. /etc/sysconfig/network
-
-# Check that networking is up.
-[ "$NETWORKING" = "no" ] && exit 0
-
-USER="chess"
-APPNAME="chess"
-APPBIN="/usr/bin/chess"
-APPARGS="-config config.json"
-LOGFILE="/var/log/$APPNAME/error.log"
-LOCKFILE="/var/lock/subsys/$APPNAME"
-
-LOGPATH=$(dirname $LOGFILE)
-
-start() {
- [ -x $prog ] || exit 5
- [ -d $LOGPATH ] || mkdir $LOGPATH; chown $USER:$USER $LOGPATH
- [ -f $LOGFILE ] || touch $LOGFILE; chown $USER:$USER $LOGFILE
-
- echo -n $"Starting $APPNAME: "
- daemon --user=$USER "$APPBIN $APPARGS >>$LOGFILE &"
- RETVAL=$?
- echo
- [ $RETVAL -eq 0 ] && touch $LOCKFILE
- return $RETVAL
-}
-
-stop() {
- echo -n $"Stopping $APPNAME: "
- killproc $APPBIN
- RETVAL=$?
- echo
- [ $RETVAL -eq 0 ] && rm -f $LOCKFILE
- return $RETVAL
-}
-
-restart() {
- stop
- start
-}
-
-rh_status() {
- status $APPNAME
-}
-
-rh_status_q() {
- rh_status >/dev/null 2>&1
-}
-
-case "$1" in
- start)
- rh_status_q && exit 0
- $1
- ;;
- stop)
- rh_status_q || exit 0
- $1
- ;;
- restart)
- $1
- ;;
- status)
- rh_status
- ;;
- *)
- echo $"Usage: $0 {start|stop|status|restart}"
- exit 2
-esac
diff --git a/instruction/chess-tips/chess-tips.md b/instruction/chess-tips/chess-tips.md
deleted file mode 100644
index 6cb059c8..00000000
--- a/instruction/chess-tips/chess-tips.md
+++ /dev/null
@@ -1,302 +0,0 @@
-# Chess Tips
-
-This is a collection of tips that the TAs have compiled for solving common problems.
-
-# General - all phases
-
-## I don't have enough GitHub commits to pass the autograder
-
-Try to avoid this by following these rules of thumb:
-
-1. **Work in small chunks.** One common mistake is only committing when you hit a big milestone, like passing an entire test file - this is a sign that you could break the problem into smaller pieces. Commit when you hit small milestones, like finishing a function or passing a test.
-2. **Always set a clear goal for your next commit.** Something like "pass this test" or "create this piece of logic." Let the flow of regular commits drive your development process.
-3. Commits should **represent your changes in one short sentence.** If your commits are feeling hard to articulate because you don't remember all the things you changed, that's a good indicator that you should be committing more often.
-4. **Start working early.** The autograder has a requirement for number of days, so it'll flag you if you do everything on the same day.
-
-If you turn in your code without enough commits, the autograder will let you know that you need TA approval. We recommend that you bring your number of commits up to the required amount before coming in. You can make further changes to your code by focusing on code quality and organization: analyze your program structure and work on applying principles you're learning in class, like decomposition, abstraction, and encapsulation.
-
-# Phase 0 - Chess moves
-
-## These collections look identical to each other, but Java says they aren’t
-
-Look at the [specification](../../chess/0-chess-moves/chess-moves.md#object-overrides) for mentions of the `equals()` and `hashCode()` methods. It might also be worthwhile to implement a `toString()` method. Additionally, check the promotion piece (null in 99% of cases). Also, review the getters and setters for each class if that doesn’t work.
-
-## JUnit - No test events received
-
-Go into File -> Project Structure -> Modules -> Paths and select “Inherit project compile output path” for each module.
-
-## I can’t run any tests, there is no green button
-
-First, check if your `test/java` folder is highlighted green. If it isn’t highlighted green, right-click on the `java` folder, then select "Mark Directory As…" and select "Test Sources Root". Now that IntelliJ understands that this is a test folder, there should be an option to run the tests inside of it.
-
-If that doesn’t work, make sure that the shared folder is a module. You can check this by clicking File -> Project Structure. Then, in Project Structure select the Module option on the left. Here you can see a section of Modules, in which you should see shared, server, and client. If you only see chess or nothing at all, IntelliJ didn’t set it up right, so you should delete everything, reclone the repository, and set it up again.
-
-## I don't have enough GitHub commits to pass the autograder
-
-See [previous](/instruction/chess-tips/chess-tips.md#General---all-phases)
-
-# Phase 1 - Chess Game
-
-## toString
-
-If you are struggling to understand how the board is looking during your code, and especially when comparing it with the expected test cases, what you can do is override a toString to your code. This means that instead of printing ChessGame@12345, it will print out the variables, and you can have it print out a mock chess board so you can visually see and understand what the current layout of the chessBoard.
-
-## `static` keyword
-
-In most cases you should not be using the `static` keyword for your classes and methods. Static classes and variables mean that there will only be one single instance, making it into a global variable. If you know what you are doing, you can use static, but only use it if you can know what you are using it for. If your IDE is telling you to use static, you should see why it is thinking it should be static, and fix it because 90-100% of your code shouldn't be static.
-
-## Clone and Copy
-
-When in your `ChessGame.validMoves`, you may want to create a copy/clone of the ChessBoard so that you can make a piece move and see if you are still in check to know if that is a valid move or not. If you create a shallow copy, the ChessBoard will be the exact same, and will keep any changes you make. This needs to be a DeepCopy or clone so that it can be unique and different, so that if a chance happens on one instance, the other will stay the same. If you would like some explanations on how to incorporate clone and copy, look here for [copying objects](../copying-objects/copying-objects.md). One such method is to have ChessBoard implement Cloneable, then in the override clone method, you loop through the 2d ChessPiece array, and do `Arrays.copyOf` to copy the chess board row by row, then finally putting the 2d array into the cloned ChessBoard.
-
-## `==` vs `.equals()` comparison
-
-If you are trying to see if a king is in check by doing `endPosition == kingPosition`, the answer will always return false. Instead, you should use `endPosition.equals(kingPosition)`. To understand why Objects require `.equals()` instead, please refer to [Java Object Class](../java-object-class/java-object-class.md).
-
-## I don't have enough GitHub commits to pass the autograder
-
-See [previous](/instruction/chess-tips/chess-tips.md#General---all-phases)
-
-# Phase 2 - Server Diagram
-
-# Phase 3 - Web API
-
-## 5 Pro tips
-
-5 Pro tips for solving your own coding problems (#4 will surprise you):
-
-1. **Read the error messages.** The computer doesn’t lie, and it even tells you exactly which line caused the problem!
-2. **Read the specs.** The document is long, but it’s not redundant— it’s helpful. All the information has been carefully placed in there to help YOU as a student.
-3. **Search Discord.** It’s really to ask a new question, but first try to use the search feature to find a pre-existing answer.
-4. **Write your own tests.** Yes, it was convenient in the first phases when we had done that for you, but now we’re expecting you to do this yourself. The Phase 3 tests are intentionally less helpful than before. Rely on manual testing, or write your own tests from the beginning.
-5. **Appreciate the learning opportunity.** We (the instructors) designed this course to teach you new things. You are supposed to wrestle with new concepts and challenges, but it is possible. Think through the problems; you can do it!
-
-## Why is there no distinction between `Unauthorized` and `Unregistered` errors?
-
-> **Question:**
-> Would trying to sign in with an unregistered username generate a 401 error or a 500 error?
-> my guess is that it's a 401, since it was a user error, but calling it "Unauthorized" when it's more "Unregistered" doesn't feel quite right
-
-While the user isn't registered, you still want to return `Unauthorized`, otherwise you would be "leaking" information.
-
-For example, lets say you return `Unregistered` for users that don't exist and `Unauthorized` for users that do exist but just have the wrong password. If I'm a bad actor and I want to try and log in to someone else's account, if I get an `Unregistered` exception, that tells me that the account I'm trying to log into doesn't exist, and if I get an `Unauthorized` exception that means I have an account that exists, but I don't have the right password. So I could just spam different passwords at accounts I know exist, rather than waste my time with accounts that don't exist.
-
-Returning `Unauthorized` in both cases doesn't "leak" the information whether an account exists or not. Just think about when you try to log into somewhere, and if you put the wrong password, it usually says "wrong username/password" rather than just saying "wrong password."
-
-## "Variable is being received by server/tests as null” OR “Variable from the tests is arriving in my server as null”
-
-A variable in a request/result object is named incorrectly (casing matters with gson: authToken and authtoken are treated differently). Make sure that all class variable names in classes used for serialization exactly match the spec.
-
-Make sure the resources folder/directory is marked as the main resources root
-
-## SyntaxError: Unexpected token ‘<’, “
- The CS 240 Chess Server Web API is described below. Some of the APIs require a request body to be sent (like
- /user/login and /user/register), while others require an Authorization authToken
- (received at login). To view the required JSON format, click on a command below and look at the example request
- body. To try out an API, modify the request as needed, and press Send.
-
|
- [POST]
- /user
- |
- - Register a user If successful, an authorization authToken is returned. You - may use the authToken with future requests that require authorization. No authorization authToken is - required - to call this endpoint. - | -
|
- [POST]
- /session
- |
- - Log in a user If successful, an authorization authToken is returned. You - may - use the authToken with future requests that require authorization. No authorization authToken is required to - call this endpoint. - | -
|
- [DELETE]
- /session
- |
- - Logs out an authenticated user An authToken is required to call this - endpoint. - | -
|
- [GET]
- /game
- |
- - Lists all the games in the database This API does not take a request body. - The response JSON lists all the games, including the board. An authToken is required to call this endpoint. - | -
|
- [POST]
- /game
- |
- - Create a new Chess Game The request body must contain a name for the game. - The response JSON contains the ID of created game, or if failed, an error message describing the reason. An - authToken is required to call this endpoint. - | -
|
- [PUT]
- /game
- |
- - Join a Chess Game The request body must contain the game ID and a color. An - authToken is required to call this endpoint. - | -
|
- [DELETE]
- /db
- |
- - Clear ALL data from the database This includes users and all game data. No - authorization authToken is required. - | -
The document has moved here.
- -``` - -This will return the HTML page located at the URL. You can see the details of the HTTP connection and request by including the `-v` parameter. This is helpful for debugging HTTP headers and security interactions. - -```sh -➜ curl -v byu.edu - -* Connected to byu.edu (128.187.16.184) port 80 (#0) -> GET / HTTP/1.1 -> Host: byu.edu -> User-Agent: curl/7.87.0 -> Accept: */* -> -< HTTP/1.1 301 Moved Permanently -< Date: Thu, 03 Aug 2023 16:52:23 GMT -< Server: Apache/2.5.37 (Red Hat Enterprise Linux) OpenSSL/1.1.1k -< Location: https://www.byu.edu/ -< Expires: Thu, 03 Aug 2023 17:52:23 GMT -< Content-Length: 228 -< Content-Type: text/html; charset=iso-8859-1 -< - - -... -``` - -If you want to make a Curl request that explicitly provides the HTTP method, headers, or body, you can use the following syntax. - -| Purpose | Syntax | Example | -| ------- | --------- | ---------------------------------- | -| Method | -X method | -X PUT | -| Header | -H header | -H "content-type:application/json" | -| Body | -d body | -d '{"name":"berners-lee"}' | - -Putting this all together in a single request would look like the following if you were trying to create a new chess game using a server running locally. - -```sh -➜ curl -X POST localhost:8080/game -d '{ "gameName": "speed"}' -H "Authorization:607b0857" -``` - -Take some time to get familiar with Curl. You can use it to test your server, programmatically make batch requests, or to probe and debug how other web services work. - -> [!NOTE] -> -> Note that when running under Windows Powershell `curl` is mapped to the `Invoke-WebRequest` command. You don't want to use that. Instead type `curl.exe` to actually access Curl. - -## Videos - -- 🎥 [cURL: Client for URLs (9:17)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=16506fca-9d4b-4f5e-89ad-b185015d13e5) - [[transcript]](https://github.com/user-attachments/files/17737197/CS_240_cURL_Client_for_URLs_Transcript.pdf) -- 🎥 [cURL for Chess (4:24)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=07ab3fff-d972-45b1-9f9f-b185015fdf9f) - [[transcript]](https://github.com/user-attachments/files/17737202/CS_240_cURL_for_Chess_Transcript.pdf) -- 🎥 [cURL Alternatives (5:45)](https://byu.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=10dd7fb8-74c3-4b05-a2db-b18501615517) - [[transcript]](https://github.com/user-attachments/files/17737211/CS_240_cURL_Alternatives_Transcript.pdf) diff --git a/instruction/db-jdbc/db-jdbc.md b/instruction/db-jdbc/db-jdbc.md deleted file mode 100644 index 606c5b10..00000000 --- a/instruction/db-jdbc/db-jdbc.md +++ /dev/null @@ -1,357 +0,0 @@ -# Relational Databases - JDBC - -🖥️ [Slides - JDBC](https://docs.google.com/presentation/d/12XS7en64-oQYivKayGyNGueWphiL6mm5) - -🖥️ [Lecture Videos](#videos) - -Now that we have covered what relational databases are and how to use SQL to interact with them, it is time to discuss how to use SQL from a Java program. Java uses a standard interface library called Java Database Connector (JDBC). This library provides you with classes to connect to a database, execute SQL queries, and process the results. - -We also discuss using the popular open source relational database software MySQL. You will install a MySQL server in your development environment and use it as the persistent data store for your chess program. - -To actually create a connection to your database you must first download the MySQL JDBC driver jar. You can then use the standard JDK JDBC classes as shown in the following example: - -```java -import java.sql.DriverManager; - -public class DatabaseExample { - public static void main(String[] args) throws Exception { - try (var conn = DriverManager.getConnection("jdbc:mysql://localhost:3306", "root", "monkeypie")) { - conn.setCatalog("pet_store"); - - try (var preparedStatement = conn.prepareStatement("SELECT id, name, type from pet")) { - try (var rs = preparedStatement.executeQuery()) { - while (rs.next()) { - var id = rs.getInt("id"); - var name = rs.getString("name"); - var type = rs.getString("type"); - - System.out.printf("id: %d, name: %s, type: %s%n", id, name, type); - } - } - } - } - } -} -``` - -## Database Connectors - -The JDK `java.sql` package contains interfaces and abstract classes for connecting to a RDBMS. However, you need to download a specific database connector that implements the interfaces for the RDBMS that you are using. For this course, we use the `mysql.connector`. Here is an example of using IntelliJ to import the MySQL connection into a project. - - - -Most modern connector packages don't require you to initialize the connector. The act of including it in your classpath will do that automatically. Once you have installed the package for your project you are good to create connections with it. - -## Obtaining a Connection - -With the database specific connector installed, you are ready to use the JDK `DriverManager` class to get a connection to the database specified by a given URL. The DriverManager inspects the URL and passes the connection request over appropriate database connector. In the example below, the DriverManager will pass the request to the MySQL connector that registered itself when the connector package was loaded. - -If the connector exists, and the RDBMS specified by the URL is available then you will get back a connection object. - -Note that we use the Java `try-with-resource` syntax so that the connection will be released as soon as the try block exits. This is important, because the number of available connections is limited by the database server. If you don't release connections then any request for new connections will fail and you will no longer be able to use the database. - -```java - -Connection getConnection() throws SQLException { - return DriverManager.getConnection("jdbc:mysql://localhost:3306", "root", "monkeypie"); -} - -void makeSQLCalls() throws SQLException { - try (var conn = getConnection()) { - // Execute SQL statements on the connection here - } -} -``` - -## Creating Databases and Tables - -Once you have a connection you can use it to create databases and tables. It is a good idea to create Java code that create your databases and tables if they don't exist either when your application starts up, or with an explicit initialization operation. This allows you to define all of the infrastructure that your code depends upon in the code that actually uses the infrastructure. That way you can always assume that the required databases and tables exist instead of managing and initializing that infrastructure as some sort of external manual executed process. - -We can fully configure a theoretical pet store application by doing the following. - -1. Get a connection to the RDBMS. -1. Create the pet store database if it doesn't exist. -1. Create the pet table if it doesn't exist. - -```java -void configureDatabase() throws SQLException { - try (var conn = getConnection()) { - var createDbStatement = conn.prepareStatement("CREATE DATABASE IF NOT EXISTS pet_store"); - createDbStatement.executeUpdate(); - - conn.setCatalog("pet_store"); - - var createPetTable = """ - CREATE TABLE IF NOT EXISTS pet ( - id INT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - type VARCHAR(255) NOT NULL, - PRIMARY KEY (id) - )"""; - - - try (var createTableStatement = conn.prepareStatement(createPetTable)) { - createTableStatement.executeUpdate(); - } - } -} -``` - -We execute SQL statements by first creating a `PreparedStatement`. You can think of a prepared statement as a SQL statement template. In the above code we create two prepared statements. One to create the database and one to create the table. Both of the statements are created with hard coded strings that represent the desired SQL to execute. - -In the same way that you had to use a try-with-resource block to close our connection, you also need to make sure you close the prepared statement when we are done with it. - -Make sure you note the use of the `setCatalog` call. We call this after we have made sure that the pet store database is created. Setting the catalog tells the connection that all subsequent calls should be done in the context of the given database. In the example, we are creating the `pet` table in the context of the `pet_store` database. - -## Inserting Data - -Once you have created your database and tables, you can now start inserting data. The following is an example of how to insert a new row in the `pet` table. - -```java -int insertPet(Connection conn, String name, String type) throws SQLException { - try (var preparedStatement = conn.prepareStatement("INSERT INTO pet (name, type) VALUES(?, ?)", RETURN_GENERATED_KEYS)) { - preparedStatement.setString(1, name); - preparedStatement.setString(2, type); - - preparedStatement.executeUpdate(); - - var resultSet = preparedStatement.getGeneratedKeys(); - var ID = 0; - if (resultSet.next()) { - ID = resultSet.getInt(1); - } - - return ID; - } -} -``` - -This code starts by creating a new prepared statement that contains the `INSERT` command. Note however that instead of concatenating the pet name and type directly into the command, we parameterize the prepared statement by putting question marks (`?`) into the statement syntax. We then supply the values of the parameter with the `setString` method on the prepared statement. - -The first parameter to setString tells the position of the parameter to set and the second parameter is the value to set. This corresponds to the position of the question marks in the prepared statement. - -```java -preparedStatement.setString(1, name); -``` - -There are `set` methods for all the SQL types (e.g. `setInt`, `setDate`, `setBoolean`, ...). Make sure you use the one that conforms with the schema of the table field you are populating. - -Once you have created the statement and populated the parameters you then execute the statement by calling `executeUpdate`. If this doesn't throw an exception then the data was successfully inserted. - -### Generating Primary Keys - -Our insertPet function does one more thing. If you look at the table schema that we defined when we created the pet table, you will see that we set the `id` column to `AUTO_INCREMENT` the value. - -```sql -CREATE TABLE IF NOT EXISTS pet ( - id INT NOT NULL AUTO_INCREMENT, - name VARCHAR(255) NOT NULL, - type VARCHAR(255) NOT NULL, - PRIMARY KEY (id) -) -``` - -We use this to let the database handle the assignment of the primary key for the pet record. That means that we don't have to provide that as a field when when insert a new pet, but we do potentially want to know what ID was generated by the database. To get the generated value we specify the parameter `RETURN_GENERATED_KEYS` when we create the prepared statement. We then call `getGeneratedKeys` after we called `executeUpdate`. This returns an iterator, but we are only interested in the first value and so we advance the iterator with `next` and the call `getInt` to get the new ID - -```java -var resultSet = preparedStatement.getGeneratedKeys(); -var ID = 0; -if (resultSet.next()) { - ID = resultSet.getInt(1); -} -``` - -### Protecting Against SQL Injections - - - -> Source: _Randall Munroe. Exploits of a Mom. xkcd. (CC BY-NC 2.5)_ - -Using the `set` functions on a prepared statement helps prevent against what is know as a SQL injection. A SQL injection allows an attacker to inject unexpected SQL syntax into a statement. Consider the case where we simplify the `insertPet` function to the following. - -```java -void insertPet(String name) throws SQLException { - var conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/pet_store?allowMultiQueries=true", "root", "monkeypie"); - - var statement = "INSERT INTO pet (name) VALUES('" + name + "')"; - System.out.println(statement); - try (var preparedStatement = conn.prepareStatement(statement)) { - preparedStatement.executeUpdate(); - } -} -``` - -This makes the code smaller and would actually work fine in the normal case. The problem occurs when someone supplies the following name: - -```java -name = "joe','cat'); DROP TABLE pet; -- "; -``` - -This will result in execution the following SQL. This first inserts a bogus pet record, and then deletes the entire pet table. - -```sql -INSERT INTO pet (name, type) VALUES('joe','cat'); DROP TABLE pet; -- 'rat') -``` - -In addition to using the database connection prepared statements properly you also want to sanitize any input that comes from a user to make sure it only contains patterns that you expect. A more secure insert pet method would look like the following. - -```java -void insertPet(String name) throws SQLException { - var conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/pet_store", "root", "monkeypie"); - - if (name.matches("[a-zA-Z]+")) { - var statement = "INSERT INTO pet (name) VALUES(?)"; - try (var preparedStatement = conn.prepareStatement(statement)) { - preparedStatement.setString(1, name); - preparedStatement.executeUpdate(); - } - } -} -``` - -This does the following to help prevent a SQL injection. - -1. Validates the input is of the expected format. -1. Uses the prepared statement `set` functions which also validates the format. -1. Does not allow multiple statements to execute in a single `executeUpdate` request by removing the `allowMultiQueries` from the connection string. - -## Updates - -You can update data using JDBC code similar to what you used for inserting data. You just need to change the SQL statement that you execute. - -```java -void updatePet(Connection conn, int petID, String name) throws SQLException { - try (var preparedStatement = conn.prepareStatement("UPDATE pet SET name=? WHERE id=?")) { - preparedStatement.setString(1, name); - preparedStatement.setInt(2, petID); - - preparedStatement.executeUpdate(); - } -} -``` - -Make sure that you include a `WHERE` clause in your statement. Otherwise you will update every row in the table. - -## Deleting Data - -You delete data by specifying a `DELETE` SQL statement along with a `WHERE` clause that describes the rows to delete. - -```java -void deletePet(Connection conn, int petID) throws SQLException { - try (var preparedStatement = conn.prepareStatement("DELETE FROM pet WHERE id=?")) { - preparedStatement.setInt(1, petID); - preparedStatement.executeUpdate(); - } -} -``` - -If no clause is specified then all of the table data will be deleted. However, it is usually more efficient to use a `TRUNCATE tableNameHere` statement instead. - -## Queries - -In order to get things out of a database you need to call the `executeQuery` method on the prepared statement. The `executeQuery` method returns a `java.sql.ResultSet` object that represents the resulting rows that match the query. - -You can parameterize the `SELECT` statement with a `WHERE` clause, or you can leave out the clause if you want to return all rows in the table. - -```java -void queryPets(Connection conn, String findType) throws SQLException { - try (var preparedStatement = conn.prepareStatement("SELECT id, name, type FROM pet WHERE type=?")) { - preparedStatement.setString(1, findType); - try (var rs = preparedStatement.executeQuery()) { - while (rs.next()) { - var id = rs.getInt("id"); - var name = rs.getString("name"); - var type = rs.getString("type"); - - System.out.printf("id: %d, name: %s, type: %s%n", id, name, type); - } - } - } -} -``` - -When you call the result set `next` method it will advance the result to the next row. If it ever returns `false` then it means you have read all the matching rows. You can read the fields of the row with the result set `get` methods. There are methods for all of the basic SQL types. Make sure that the fields you getting are represented in the fields that your `SELECT` statement requested. - -Make sure that your wrap the result set returned from the query with a `try-with-resource` block so that you release the resources associated with the result once you are done with them. - -## Text and Blob Types - -Sometimes you want to store large text or binary data in the database. In MySQL the `text` type can represent large textual sequences, and the `blob` type represents large binary sequences. These can be as large as 4 gigabytes. Blob and text data is not searchable, but it is convenient to associate it with some other indexed fields. For example, a commonly used pattern called a `key-value` store, associates a single key field with a blob field. This basically uses the database as a big persistent map. - -Another example of storing large text data comes from the chess application. With chess, you need to store your game board and make it searchable using the game ID. One way to do this is to serialize your board to JSON and then place the JSON data in a `text` field. - -We can demonstrate how to do this using a simple pet record that has a name, a type, and a list of friends. - -```java -record Pet(String name, String type, String[] friends) {} -``` - -We can create a SQL table schema that matches this structure by including field for the friend array that has type `longtext`. The `longtext` type can represent text strings that are up to 4 gigabytes and so that will be plenty of room for all the pet's friends. - -```java -CREATE TABLE IF NOT EXISTS pet ( - name VARCHAR(255) DEFAULT NULL, - type VARCHAR(255) DEFAULT NULL, - friends longtext NOT NULL -); -``` - -We then create a method that inserts a pet into the database. This serializes the friend field using the Gson `toJson` method, and uses a SQL INSERT statement to put it in the database. - -```java -void insertPet(Connection conn, Pet pet) throws SQLException { - try (var preparedStatement = conn.prepareStatement("INSERT INTO pet (name, type, friends) VALUES(?, ?, ?)")) { - preparedStatement.setString(1, pet.name); - preparedStatement.setString(2, pet.type); - - // Serialize and store the friend JSON. - var json = new Gson().toJson(pet.friends); - preparedStatement.setString(3, json); - - preparedStatement.executeUpdate(); - } -} -``` - -And we can read it back out again by reversing the process. - -```java -Collection