1 Introduction & Overview

This article is based on an update to this article, updating some of the technology stacks to be more relevant to actual needs, and fixing some bugs.

This is a front-end Android+ back-end Java/Kotlin through Servelt background database (MySQL) interaction detailed steps and source code implementation, technical stack:

  • Androidbasis
  • nativeJDBC+ nativeServlet
  • Tomcat+MySQL(Docker)

Of course, a lot of Java backend development today uses Spring Boot instead of native servlets, so using Spring Boot for implementation is another article for me.

Although the Spring Boot implementation is very simple, the underlying principles are better understood using native servlets. In addition, this article is a basic tutorial, many steps will be more detailed and attached with the diagram, good nonsense do not say, the body begins.

2 the environment

  • Android Studio 4.1.2
  • IntelliJ IDEA 2020.3
  • MySQL 8.0.23
  • Tomcat 10.0
  • Docker 20.10.1
  • The serverCentOS 8.1.1911

3 Environment Preparation

3.1 IDETo prepare

Install Android Studio+IDEA on the official website, which is omitted.

3.2 MySQL

3.2.1 Installation Overview

MySQL refers to MySQL Community unless otherwise specified.

MySQL provides exe installation package on Windows:

DMG installation packages are available under macOS:

You can download it here.

In Linux, MySQL can be installed in the following ways:

  • Software package installation (apt/apt-get,yum,dnf,pacmanEtc.)
  • Download the package and install it
  • Source code compilation and installation
  • DockerThe installation

Among them, the relatively easy installation method is Docker installation and software package installation, followed by the compressed package installation, especially do not recommend the source installation (of course, if you like the challenge can refer to the author of a compilation installation 8.0.19 and compilation installation 8.0.20).

3.2.2 The installation starts

Here, the author chose to use Docker installation in the local test, and the steps can be seen here.

In addition, for the server, you can also use Docker installation, if the use of software package installation, here to the author’s CentOS8 as an example, other systems for reference as follows:

  • Fedroa
  • RedHat
  • Ubuntu

3.2.2.1 Download and Install the software

Add warehouse:

sudo yum install https://repo.mysql.com/mysql80-community-release-el8-1.noarch.rpm
Copy the code

Disable the default MySQL module (CentOS8 includes a default MySQL module, so you cannot use the repository installed above) :

sudo yum module disable mysql
Copy the code

Installation:

sudo yum install mysql-community-server
Copy the code

3.2.2.2 Start the Service and View the Initial Password

Start the service:

systemctl start mysqld
Copy the code

To view temporary passwords:

sudo grep 'temporary password' /var/log/mysqld.log
Copy the code

Enter a temporary password to log in:

mysql -u root -p
Copy the code

Change password:

alter user 'root'@'localhost' identified by 'PASSWORD'
Copy the code

3.2.2.3 Creating External Access Users

You are not advised to directly access the root user in Java. Instead, you need to create a user with the corresponding permission to access the root user, which is omitted for convenience.

3.3 Tomcat

3.3.1 localTomcat

Tomcat is easy to install and can be downloaded directly from the official website:

Extract:

Tar ZXVF - apache tomcat - 10.0.0, tar, gzCopy the code

Go to the bin directory and run startup.sh:

cdApache tomcat - 10.0.0 / bin/startup. ShCopy the code

Local access localhost:8080:

That counts as a success. For Windows users, you can download the file and run startup.bat to access localhost:8080.

3.3.2 rainfall distribution on 10-12 serverTomcat

The server can be installed directly using wget:

Wget HTTP: / / https://downloads.apache.org/tomcat/tomcat-10/v10.0.0/bin/apache-tomcat-10.0.0.tar.gzCopy the code

However, this speed is very slow, it is recommended to download to local and then use SCP upload:

SCP apache tomcat - 10.0.0. Tar. Gz [email protected]: /Copy the code

Run startup.sh to access public IP address :8080 and check whether the file is decompressed.

4. Build database and build table

4.1 the users table

The MySQL script used here is as follows:

CREATE DATABASE userinfo;
USE userinfo;
CREATE TABLE user
(
    id          INT     NOT NULL    PRIMARY KEY   AUTO_INCREMENT,
    name        CHAR(30)    NULL,
    password    CHAR(30)    NULL
)
Copy the code

4.2 the import

mysql -u root -p < user.sql
Copy the code

5 Back-end

Since this is a very basic tutorial, let’s start by creating the project.

5.1 Creating a Project + Guide Library

Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise: Java Enterprise:

2020.3 IDEA adds the function of selecting libraries more humanized than before. By default, servlets are selected. If you need other libraries, you can choose them by yourself.

Another thing to note is that JavaEE has been renamed JakartaEE, so you can select JakartaEE here:

Fill in the corresponding package name and select the location:

After creating the Servlet package, I encountered an error and could not find the corresponding Servlet package:

Select update Central Repository in Settings:

The created directory looks like this:

We then add dependencies, including:

  • MySQL
  • Jackson
  • Lombok

Add to pom.xml (note the version, you can check here for different MySQL versions) :

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
</dependency>
Copy the code

That completes the first step.

5.2 structure

The project structure is as follows:

  • Persistence layer operations:Dao
  • Entity class:User
  • Response body:ResponseBody
  • ServletLayer:SignIn/SignUp/Test
  • Tools:DBUtils
  • Start the class: No, because inWebRunning on the server

Create files and directories:

5.3 DBUtils

Native JDBC fetch connection utility class:

package com.example.javawebdemo.utils;

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

public class DBUtils {
    private static Connection connection = null;

    public static Connection getConnection(a) {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            final String url = "JDBC: mysql: / / 127.0.0.1:3306 / the userinfo";
            final String username = "root";
            final String password = "123456";
            connection = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return connection;
    }

    public static void closeConnection(a) {
        if(connection ! =null) {
            try {
                connection.close();
            } catch(SQLException e) { e.printStackTrace(); }}}}Copy the code

Focus on these four lines:

Class.forName("com.mysql.cj.jdbc.Driver");
String url = "JDBC: mysql: / / 127.0.0.1:3306 / the userinfo";
String username = "root";
String password = "123456";
Copy the code

Note the difference between MySQL8’s registration driver and the older version:

Class.forName("com.mysql.jdbc.Driver");
Copy the code

5.4 User

Three fields + @getter:

package com.example.javawebdemo.entity;

import lombok.Getter;

@Getter
public class User {
    private final String name;
    private final String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password; }}Copy the code

5.5 Dao

Database operation layer:

package com.example.javawebdemo.dao;

import com.example.javawebdemo.entity.User;
import com.example.javawebdemo.utils.DBUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class Dao {
    public boolean select(User user) {
        final Connection connection = DBUtils.getConnection();
        final String sql = "select * from user where name = ? and password = ?";
        try {
            final PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getName());
            preparedStatement.setString(2, user.getPassword());
            ResultSet resultSet = preparedStatement.executeQuery();
            return resultSet.next();
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        } finally{ DBUtils.closeConnection(); }}public boolean insert(User user) {
        final Connection connection = DBUtils.getConnection();
        final String sql = "insert into user(name,password) values(? ,?) ";
        try {
            final PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, user.getName());
            preparedStatement.setString(2, user.getPassword());
            preparedStatement.executeUpdate();
            returnpreparedStatement.getUpdateCount() ! =0;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        } finally{ DBUtils.closeConnection(); }}}Copy the code

Two operations:

  • Query: The system returns if the user existstrue, otherwise,false
  • Insert: Adds a user

Note the use in insert operationsexecuteUpdate()Insert and use at the same timegetUpdateCount() ! = 0Judge the result of insertion rather than use it directly

return preparedStatement.execute();
Copy the code

In general:

  • select:executeQuery().executeQuery()returnResultSetRepresents the result set, savedselectStatement execution result, matchnext()use
  • delete/insert/updateUse:executeUpdate().executeUpdate()Returns an integer representing the number of rows affected, i.edelete/insert/updateThe number of lines modified, fordrop/createOperation returns0
  • create/dropUse:execute().execute()Is returned if the first result isResultSetObject is returnedtrueIf the first result is an update count or there is no resultfalse

So in this case

return preparedStatement.execute();
Copy the code

Must return false, cannot directly determine whether the insert was successful.

5.6 response body

Add a response body class to set the return code and data:

package com.example.javawebdemo.response;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class ResponseBody{
    private Object data;
    private int code;
}
Copy the code

5.7 Servlet

  • SingInClass to handle logins, callsJDBCCheck whether the database has a corresponding user
  • SignUpClass is used to process the registration of theUserAdd to the database
  • TestTo test theServlet, returns a fixed string

On the first SignIn. Java

package com.example.javawebdemo.servlet;

import com.example.javawebdemo.dao.Dao;
import com.example.javawebdemo.entity.User;
import com.example.javawebdemo.response.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/sign/in")
public class SignIn extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json; charset=utf-8");

        String name = req.getParameter("name");
        String password = req.getParameter("password");

        Dao dao = new Dao();
        User user = new User(name,password);
        ObjectMapper mapper = new ObjectMapper();
        ResponseBody body = new ResponseBody();

        if (dao.select(user)) {
            body.setCode(200);
            body.setData("success");
        } else {
            body.setCode(404);
            body.setData("failed"); } mapper.writeValue(resp.getWriter(), body); }}Copy the code

Note:

  • @WebServletDefinition:Servlet(It is possible to leave this comment out but it needs to be inweb.xmlManual definition inServlet), the default property isvalueSaid,ServletThe path
  • Code:HttpServletRequest/HttpServletResponseAll set upUTF8(although not necessary in this case because there are no Chinese characters)
  • Get parameters:request.getParameterGets the parameters from the request. The parameters passed in are key values
  • Write response body: useJacksonThat will beresponse.getWriterAnd the response body is passed in, and then handed inmapper.writeValueWrite the response body

Here is signup.java, much of the code is similar:

package com.example.javawebdemo.servlet;

import com.example.javawebdemo.dao.Dao;
import com.example.javawebdemo.entity.User;
import com.example.javawebdemo.response.ResponseBody;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/sign/up")
public class SignUp extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");
        resp.setContentType("application/json; charset=utf-8");

        String name = req.getParameter("name");
        String password = req.getParameter("password");
        Dao dao = new Dao();
        User user = new User(name,password);
        ResponseBody body = new ResponseBody();
        ObjectMapper mapper = new ObjectMapper();
        if (dao.insert(user)) {
            body.setCode(200);
            body.setData("success");
        } else {
            body.setCode(500);
            body.setData("failed"); } mapper.writeValue(resp.getWriter(), body); }}Copy the code

Test the Servlet:

package com.example.javawebdemo.servlet;

import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

@WebServlet("/test")
public class Test extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.getWriter().print("Hello, Java Web"); }}Copy the code

5.8 run

To run Tomcat, select Tomcat Server in the run configuration:

Set the Tomcat root directory:

Then, after Deployment + is selected, select the second one that is equipped with exploded (although the first one is not impossible, although the first one is usually published to the remote version in the FORM of a WAR, while the second one directly copies all the files into the webapps directory in the current format and supports hot Deployment in debug mode) :

In addition, you can change the path to a relatively simple path, convenient operation:

Debug (run without hot deployment) :

Localhost :8080/demo (IDEA should be automatically opened)

Test will appear under the access path:

Now that the back-end is done, let’s deal with the Android side.

6 Androidend

6.1 Creating a Project

6.2 Dependencies/Permissions

Dependencies are as follows:

implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.1'
Copy the code

Add build. Gradle to build.

buildFeatures{
    viewBinding = true
}
Copy the code

ViewBinding is the view binding function. Previously, findViewById was used to obtain the corresponding component, and then there was Butter Knife. Now Butter Knife is out of date, and View Binding is recommended.

Add network permissions to androidmanifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Copy the code

You also need to add HTTP support, because this is a sample Demo will not use HTTPS, but the current version of Android does not support it by default, so you need to add in

:

android:usesCleartextTraffic="true"
Copy the code

6.3 Project Structure

Four files:

  • MainActivityCore:Activity
  • NetworkSettingsRequest:URL, constants,
  • NetworkThread: Network request thread
  • ResponseBody: request body

6.4 ResponseBody

package com.example.androiddemo;

public class ResponseBody {
    private int code;
    private Object data;

    public int getCode(a) {
        return code;
    }

    public Object getData(a) {
        returndata; }}Copy the code

Response body, one return code field + one data field.

6.5 NetworkSettings

package com.example.androiddemo;

public class NetworkSettings {
    private static final String HOST = "192.168.43.35";
    private static final String PORT = "8080";
    public static final String SIGN_IN = "http://"+ HOST +":"+PORT + "/demo/sign/in";
    public static final String SIGN_UP = "http://"+ HOST +":"+PORT + "/demo/sign/up";
}
Copy the code

Set HOST to your own Intranet IP address. Do not use localhost/127.0.0.1.

You can use IP addr, ifconfig, and ipconfig to view your Intranet IP address:

6.6 NetworkThread

package com.example.androiddemo;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;

public class NetworkThread implements Callable<String> {
    private final String name;
    private final String password;
    private final String url;

    public NetworkThread(String name, String password, String url) {
        this.name = name;
        this.password = password;
        this.url = url;
    }

    @Override
    public String call(a){
        try {
        	// Open the connection
            HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
            // Splice data
            String data = "name="+ URLEncoder.encode(name, StandardCharsets.UTF_8.toString())+"&password="+URLEncoder.encode(password,StandardCharsets.UTF_8.toString());
            // Set the request method
            connection.setRequestMethod("POST");
            // Allow input/output
            connection.setDoInput(true);
            connection.setDoOutput(true);
            // Write data (i.e. send data)
            connection.getOutputStream().write(data.getBytes(StandardCharsets.UTF_8));
            byte [] bytes = new byte[1024];
            // Get the returned data
            int len = connection.getInputStream().read(bytes);
            return new String(bytes,0,len,StandardCharsets.UTF_8);
        } catch (IOException e) {
            e.printStackTrace();
            return ""; }}}Copy the code

The thread class that sends the network request implements the Callable

interface because it is an asynchronous thread, which means that it returns String data. The main thread can get the return value by blocking with GET ().

6.7 MainActivity

package com.example.androiddemo;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.androiddemo.databinding.ActivityMainBinding;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.concurrent.FutureTask;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;
    private final ObjectMapper mapper = new ObjectMapper();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
    }

    public void signIn(View view){
        String name = binding.editTextName.getText().toString();
        String password = binding.editTextPassword.getText().toString();
        FutureTask<String> signInTask = new FutureTask<>(new NetworkThread(name,password,NetworkSettings.SIGN_IN));
        Thread thread = new Thread(signInTask);
        thread.start();
        try{
        	//get gets the thread return value, deserialized to ResponseBody with ObjectMapper
            ResponseBody body = mapper.readValue(signInTask.get(),ResponseBody.class);
            // Determine the prompt according to the return code
            Toast.makeText(getApplicationContext(),body.getCode() == 200 ? "Login successful" : "Login failed",Toast.LENGTH_SHORT).show();
        }catch(Exception e){ e.printStackTrace(); }}public void signUp(View view){
        String name = binding.editTextName.getText().toString();
        String password = binding.editTextPassword.getText().toString();
        FutureTask<String> signUpTask = new FutureTask<>(new NetworkThread(name,password,NetworkSettings.SIGN_UP));
        Thread thread = new Thread(signUpTask);
        thread.start();
        try{
            ResponseBody body = mapper.readValue(signUpTask.get(),ResponseBody.class);
            Toast.makeText(getApplicationContext(),body.getCode() == 200 ? "Registration successful" : "Registration failed",Toast.LENGTH_SHORT).show();
        }catch(Exception e){ e.printStackTrace(); }}}Copy the code

So viewBinding, in onCreate:

super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Copy the code

Use the ActivityMainBinding static method to obtain a binding. Note that the ActivityMainBinding class name is not fixed.

6.8 Resource Files

Two:

  • activity_main.xml
  • strings.xml

The differences are as follows without going into details:


      
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textViewName"
        android:layout_width="45dp"
        android:layout_height="38dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="92dp"
        android:text="@string/name"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editTextName"
        android:layout_width="300dp"
        android:layout_height="40dp"
        android:layout_marginStart="64dp"
        android:layout_marginTop="84dp"
        android:autofillHints=""
        android:inputType="text"
        app:layout_constraintLeft_toLeftOf="@id/textViewName"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="LabelFor" />

    <TextView
        android:id="@+id/textViewPassword"
        android:layout_width="45dp"
        android:layout_height="36dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="72dp"
        android:text="@string/password"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@id/textViewName" />

    <EditText
        android:id="@+id/editTextPassword"
        android:layout_width="300dp"
        android:layout_height="40dp"
        android:layout_marginStart="64dp"
        android:layout_marginTop="72dp"
        android:autofillHints=""
        android:inputType="textPassword"
        app:layout_constraintLeft_toLeftOf="@id/textViewPassword"
        app:layout_constraintTop_toTopOf="@id/editTextName"
        tools:ignore="LabelFor" />

    <Button
        android:id="@+id/buttonSignUp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="56dp"
        android:layout_marginTop="32dp"
        android:onClick="signUp"
        android:text="@string/signUp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewPassword"
        tools:ignore="ButtonStyle" />

    <Button
        android:id="@+id/buttonSignIn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="36dp"
        android:layout_marginEnd="52dp"
        android:onClick="signIn"
        android:text="@string/signIn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editTextPassword"
        tools:ignore="ButtonStyle" />
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code
<resources>
    <string name="app_name">AndroidDemo</string>
    <string name="name">The user name</string>
    <string name="password">password</string>
    <string name="signUp">registered</string>
    <string name="signIn">The login</string>
</resources>
Copy the code

7 test

7.1 Local Tests

First run the Java Web side, which should automatically open the following interface:

After adding test:

Run the Android terminal, enter a non-existent user name or password first, prompting login failure, then register, and login success:

Check the back-end database as follows:

7.2 Deployment Test

Ensure that the user name and password of the local database are the same as those of the server. There are corresponding tables and libraries

Add a

to pom.xml before deploying the Java Web side:

On the toolbar on the right, select Clean, then Compile, and finally Package:

The reason for doing this is that if a file is updated, packaging does not package the updated file back into it, so the original bytecode file needs to be cleaned up first, and then compiled and packaged.

A demo.war will appear under target after completion:

SCP (or some other tool) uploads to the server and moves to Tomcat’s Webapps (assuming the server’s IP is 8.8.8.8 for the sake of illustration) :

SCP demo. War 8.8.8.8 / XXX# After connecting to the server over SSH
cp demo.war /usr/local/tomcat/webapps
Copy the code

Start Tomcat:

cd /usr/local/tomcat/bin
./startup.sh
Copy the code

After startup, you can see a new demo folder in webapps:

Visit 8.8.8.8/ Demo to see the local test page. Android NetworkSettings HOST: 8.8.8.8

Server database:

8 Precautions

Notes are trivial and a little too many, so I started a separate blog post, here.

If you have any other questions, please leave a comment.

Nine source

Java+Kotlin is provided in two languages:

  • Github
  • Yards cloud
  • CODE.CHINA

If you think the article looks good, please like it.

At the same time, welcome to pay attention to wechat public number: Lingzhi Road.