Android interacts with background databases via the Web

Update 2020.06.23

1 Background

Develop an app to interact with the background database, based on MySQL+native JDBC+Tomcat, without using DBUtils or JDBC framework, pure underlying JDBC implementation.
In the future, we will gradually change to the Spring framework, optimize MySQL, further deploy Tomcat and so on. Now the project is just starting, there are still a lot of things we don't understand, we need to slowly...
These days, I have stepped on a lot of pits, exaggerating points are really stepping on the author unconscious, I hope to help others less stepping on pits...

2 Development Environment

  • Local Win
  • Server CentOS 7
  • Android Studio 3.5.1
  • IntelliJ IDEA 2019.02
  • MySQL 8.0.17
  • Tomcat 9.0.26

3 Prepare the environment

Tell me about the installation of MySQL and Tomcat.

3.1 Install MySQL

This is the newer version of MySQL.
The server system is CentOS.
Other system installations can be found here:

CentOS is installed using the yum command:

3.1.1 Download and install mysql

sudo yum localinstall https://repo.mysql.com//mysql80-community-release-el7-1.noarch.rpm
sudo yum install mysql-community-server

3.1.2 Start the service and view the initialization password

sudo service mysqld start
sudo grep 'temporary password' /var/log/mysqld.log

3.1.3 Change Password

First log in using root:

mysql -u root -p

Enter the password you saw in the previous step, and then use alter to change the password:

alter mysql.user 'root'@'localhost' identified by 'password';

Note that the new version of MySQL cannot use passwords that are too weak.
If the following prompt appears:

That means the password is too weak. Use a stronger password.

3.1.4 Allow external access

use mysql;
update user set host='%' where user='root';

This can be modified to suit your needs. Host='%'means that all ip logins are allowed or specific IPS can be set. If host='%' is used, a new user is recommended to configure the appropriate permissions.

3.1.5 Configure firewall (optional)

In general, you need to open a response port in the firewall configuration of the corresponding cloud vendor, as shown in the following figure:




Where the authorized object can be changed according to its needs, 0.0.0/0 means that all IPS are allowed.

3.2 Install Tomcat

3.2.1 Download and upload to server

Go first Official Web Download, upload files to the server after downloading:


The author uses the scp command, Use inexperienced stamps to see here

scp apache-tomcat-xxxx.tar.gz username@xx.xx.xx.xx:/

Change to your user name and ip.

3.2.2 Decompression

ssh connects to the server, then moves to/usr/local and uncompresses:

mkdir /usr/local/tomcat
mv apache-tomcat-xxxx.tar.gz /usr/local/tomcat
tar -xzvf apache-tomcat-xxx.tar.gz

3.2.3 Modify the default port (optional)

Modify conf/Server.xmlFiles, generally only need to be modified

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />

Port 8080 in, modify this port.
Change it if you want.
I'm so lazy that I won't change it.

3.2.4 Startup

Run under the bin directoryStartup.sh:

cd bin
./startup.sh

3.2.5 Test

Browser input:

Server IP:Port

If present:

Success.

3.2.6 Start-up (optional)

Recommended configuration boot-up, modification/etc/Rc.localFile, add:

sh /usr/local/tomcat/bin/startup.sh

This is modified according to your Tomcat installation path, specifying theStartup.shYes.

4 Building library and table

Create a user table, which simplifies the operation (well, I like to be lazy) without creating a new user without authorization.
This is an example of logging in locally with root, create and authorize users as appropriate.

4.1 CreationUser.sql

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

4.2 Import to Database

mysql -u root -p < user.sql

5 rear part

5.1 Create Project

Select Web Application:

5.2 Add Dependent Library

Create a directory called lib:

Add two jar packages (the jar packages provide download links at the end):

  • mysql-connector-java-8.0.17.jar
  • javax.servlet-api-4.0.1.jar


Open Project Structure:

Modules --&gt; + --&gt; JARs or directories:

Select the two jar packages under the newly created lib:

Check, apply:

5.3 Creating packages and classes

Total 4 packages

  • com.servlet: Used to process requests from the front end, includingSignUp.java,SignIn.java
  • com.util: The main function is database connection, includingDBUtils.java
  • com.entity: Entity class, containingUser.java
  • com.dao: Classes that operate on user classes, includingUserDao.java

5.4 DBUtils

Connect to the database class, pure underlying jdbc implementation, note the driver version.

public class DBUtils {

    private static Connection connection = null;
    public static Connection getConnection()
    {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            String url = "jdbc:mysql://127.0.0.1:3306/Database name ";
            String usename = "Account number";
            String password = "Password";
            connection = DriverManager.getConnection(url,usename,password);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
        return connection;
    }

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

The main function is to get the connection and close the connection.

String url = "jdbc:mysql://127.0.0.1:3306/Database name ";
String usename = "Account number";
String password = "Password";

These lines are modified according to your user name, password, server ip, and library name.
Note that the registration-driven statements used above MySQL 8.0 are:

Class.forName("com.mysql.cj.jdbc.Driver");

The old version was:

Class.forName("com.mysql.jdbc.Driver");

5.5 User

The User class is simpler by comparing three fields to getter,setter:

public class User {
    private int id;
    private String name;
    private String password;
    //Three getter s and three setter s
    //...
}

5.6 UserDao

public class UserDao {
    public boolean query(User user)
    {
        Connection connection = DBUtils.getConnection();
        String sql = "select * from user where name = ? and password = ?";
        try {
            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 add(User user)
    {
        Connection connection = DBUtils.getConnection();
        String sql = "insert into user(name,password) values(?,?)";
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1,user.getName());
            preparedStatement.setString(2,user.getPassword());
            preparedStatement.executeUpdate();
            return preparedStatement.getUpdateCount() != 0;
        }
        catch (SQLException e)
        {
            e.printStackTrace();
            return false;
        }
        finally {
            DBUtils.closeConnection();
        }
    }
}

This is mainly a query and add operation, which returns true if the user exists in the query operation, or false if it does not.
Use executeUpdate() and getUpdateCount() in add operations!= 0. Note that you cannot use them directly

 return preparedStatement.execute();

To replace

preparedStatement.executeUpdate();
return preparedStatement.getUpdateCount() != 0;

At first glance, there seems to be nothing wrong. That night when the author tested, the problem was serious. The android side shows that registration failed, but the database side insert s in...........
Well, if you say too much, it's all tears, or if you don't use the function skillfully.

  • Generally, select uses executeQuery(), which returns a ResultSet, representing the result set, saving the execution result of the select statement, and working with next()
  • delete,insert,update uses executeUpdate(),executeUpdate() returns an integer indicating the number of rows affected, that is, delete,insert,update modified rows, for drop, the create operation returns 0
  • create,drop uses execute(), and the return value of execute() is as follows:
    • Returns true if the first result is a ResultSet object
    • Returns false if the first result is an update count or no result

So in this case

 return preparedStatement.execute();

It must have returned false, so insert on this side of the database goes in, but the front-end shows that registration failed (this bug was found really long...)

5.7 SignIn and SignUp

The SingIn class of the servlet package handles logins and calls JDBC to see if the database has a corresponding user.
The SignUp class handles registration and adds User to the database.
HTTPS connection is currently used and HTTPS support will be considered later.

SignIn.java The following:

@WebServlet("/SignIn")
public class SingIn extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException,ServletException
    {
        this.doPost(httpServletRequest,httpServletResponse);
    }

    @Override
    protected void doPost(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws IOException, ServletException
    {
        httpServletRequest.setCharacterEncoding("utf-8");
        httpServletResponse.setCharacterEncoding("utf-8");
        httpServletResponse.setContentType("text/plain;charset=utf-8");//Set the corresponding type to html, encoding utf-8

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

        UserDao userDao = new UserDao();
        User user = new User();
        user.setName(name);
        user.setPassword(password);

        if(!userDao.query(user))//If the query fails
        {
            httpServletResponse.sendError(204,"query failed.");//Set 204 Error Code and Error Information
        }
    }
}
@WebServlet("/SignIn")

The first is the @WebServlet annotation, which means that this is a servlet named SignIn that can be used to map the servlet to the url. If you do not add this annotation here, you will need to add it in the WEB-INF directoryWeb.xmlAdd a < servlet-mapping>, also known as a servlet mapping.

Next, set the response type and encoding:

httpServletResponse.setContentType("text/plain;charset=utf-8");//Set the corresponding type to html, encoding utf-8

HttpServletRequest.getParameterThe (String name) method means that the corresponding parameter is obtained from the name:

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

Here are SignUp.java:

@WebServlet("/SignUp")
public class SignUp extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws IOException,ServletException
    {
        this.doPost(httpServletRequest,httpServletResponse);
    }

    @Override
    protected void doPost(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse) throws IOException,ServletException
    {
        httpServletRequest.setCharacterEncoding("utf-8");
        httpServletResponse.setCharacterEncoding("utf-8");//Set encoding to prevent Chinese scrambling
        httpServletResponse.setContentType("text/plain;charset=utf-8");//Set the corresponding type to html, encoding utf-8

        String name = httpServletRequest.getParameter("name");//Get parameters from name
        String password = httpServletRequest.getParameter("password");//Get parameters from password

        UserDao userDao = new UserDao();
        User user = new User();
        user.setName(name);
        user.setPassword(password);

        if(!userDao.add(user)) //If add fails
        {
            httpServletResponse.sendError(204,"add failed.");//Set 204 Error Code and Error Information
        }
    }
}

5.8 Add servlet s toWeb.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <servlet>
        <servlet-name>SignIn</servlet-name>
        <servlet-class>com.servlet.SingIn</servlet-class>
    </servlet>

    <servlet>
        <servlet-name>SignUp</servlet-name>
        <servlet-class>com.servlet.SignUp</servlet-class>
    </servlet>
</web-app>

To add the Servlet you just createdWeb.xml, add child elements < servlet-name> and < servlet-class>:

  • < servlet-name> is the name of the Servlet, preferably the same as the class name
  • < servlet-class> is the location of the Servlet class

If the @WebServlet("/xxxx") annotation is not added to the Servlet class, you will need toWeb.xmlAdd:

<servlet-mapping>
    <servlet-name>SignIn</servlet-name>
    <url-pattern>/SignIn</url-pattern>
</servlet-mapping>

Where < servlet-name> and < servlet> the child elements in < servlet-name> have the same value, < url-pattern> is the path to access.

5.9Hello.htmlTest File

Add one last called Hello.html HTML test file.

<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <title>Welcome</title>
    </head>
    <body>
        Hello web.
    </body>
</html>

6 Package and Publish

The IDEA used by the author, See here for Eclipse packaging.

6.1 Project Structure->Artifacts->Web Application:Archive


6.2 Create directories and add modules

Modify the name and create the WEB-INF directory and subdirectory classes:

Select classes, add Module Output, and select your own web project:

6.3 Add Dependent Libraries and Other Files

Add a JAR package, select the lib directory and add the JAR package file:
(The lib folder is blocked. Don't care about the details....)

Then addHello.htmlandWeb.xml,Web.xmlIn the WEB-INF directory,Hello.htmlOutside WEB-INF:

6.4 Packaging

Build-&gt;Build Artifacts:

6.5 Upload Test

The packaged.war file is uploaded to the server's Tomcat webapps directory:

scp ***.war username@xxx.xxx.xxx.xxx:/usr/local/tomcat/webapps

Note changing to your own webapps directory.
After Tomcat starts, type in the browser

Server IP: Port/Project/Hello.html

The author tested it locally for convenience:

7 Android side

7.1 New Project


7.2 MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button signin = (Button) findViewById(R.id.signin);
        signin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String name = ((EditText) findViewById(R.id.etname)).getText().toString();
                String password = ((EditText) findViewById(R.id.etpassword)).getText().toString();
                if (UserService.signIn(name, password))
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "Login Successful", Toast.LENGTH_SHORT).show();
                        }
                    });
                else {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "Logon Failure", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        });

        Button signup = (Button) findViewById(R.id.signup);
        signup.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String name = ((EditText) findViewById(R.id.etname)).getText().toString();
                String password = ((EditText) findViewById(R.id.etpassword)).getText().toString();
                if (UserService.signUp(name, password))
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "login was successful", Toast.LENGTH_SHORT).show();
                        }
                    });
                else {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "login has failed", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        });
    }
}

Nothing to say, just bind events for two Button s and set two Toast prompts.

7.3 UserService.java

public class UserService {
    public static boolean signIn(String name, String password) {
        MyThread myThread = new MyThread("http://Local intranet IP:8080/cx/SignIn",name,password);
        try
        {
            myThread.start();
            myThread.join();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }

        return myThread.getResult();
    }

    public static boolean signUp(String name, String password) {
        MyThread myThread = new MyThread("http://Local intranet IP:8080/cx/SignUp ", name, password";
        try
        {
            myThread.start();
            myThread.join();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return myThread.getResult();
    }
}

class MyThread extends Thread
{
    private String path;
    private String name;
    private String password;
    private boolean result = false;

    public MyThread(String path,String name,String password)
    {
        this.path = path;
        this.name = name;
        this.password = password;
    }
    @Override
    public void run()
    {
        try {
            URL url = new URL(path);
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setConnectTimeout(8000);//Set connection timeout
            httpURLConnection.setReadTimeout(8000);//Set Read Timeout
            httpURLConnection.setRequestMethod("POST");//Set the request method, post

            String data = "name=" + URLEncoder.encode(name, "utf-8") + "&password=" + URLEncoder.encode(password, "utf-8");//Set up data
            httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");//Set Response Type
            httpURLConnection.setRequestProperty("Content-Length", data.length() + "");//Set Content Length
            httpURLConnection.setDoOutput(true);//Allow Output
            OutputStream outputStream = httpURLConnection.getOutputStream();
            outputStream.write(data.getBytes("utf-8"));//Write data
            result = (httpURLConnection.getResponseCode() == 200);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean getResult()
    {
        return result;
    }
}
MyThread myThread = new MyThread("http://Intranet IP:8080/cx/SignUp ", name, password";
MyThread myThread = new MyThread("http://Intranet IP:8080/cx/SignIn",name,password);

These two lines are replaced with their own ip, which can be viewed with ipconfig or ifconfig for intranet ips, and with the change of the default port.
The path is:

Port/web project name/Servlet name

The web project name is set when you re-type the war package, and the Servlet name is set inWeb.xmlThe < servlet> child element < servlet-name> settings are consistent with the @WebServlet() comment in the source code.

Another thing to note is the threading problem, which requires a new thread to connect to http.

7.4 activity_main.xml

The front-end page section is simple, just two button s to validate functionality.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent"
    android:orientation="vertical"
    >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="User name"
            />
        <EditText
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:id="@+id/etname"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Password"
            />
        <EditText
            android:layout_width="300dp"
            android:layout_height="60dp"
            android:id="@+id/etpassword"
            />
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="120dp"
            android:layout_height="60dp"
            android:text="register"
            android:id="@+id/signup"
            />
        <Button
            android:layout_width="120dp"
            android:layout_height="60dp"
            android:text="Sign in"
            android:id="@+id/signin"
            />
    </LinearLayout>
</LinearLayout>

8 Tests

8.1 Registration Test

Enter username and password at will

View the database:

8.2 Logon Test

9 Notes

9.1 Database User Name and Password

The user name and password of the database must be set correctly or an exception will be thrown as shown below:

This error may also occur when loading a driver error, so make sure the lib directory is correct and the JAR package version is correct when typing into a WAR package.
Also, since this is the underlying implementation of JDBC, note that handwritten SQL statements cannot be wrong.
Never be like the author:

9.2 Network Permission Issues

Need to AndroidManifest.xml Add network permissions:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

9.3 Firewall issues

Server will generally have corresponding web interface configuration, of course, you can also manually configure iptables.
Modify/etc/sysconfig/iptables

vim /etc/sysconfig/iptables

Add to

-A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT
-A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT

Restart iptables:

service iptables restart

9.4 HTTP considerations

Since starting with Android P, google defaults to require an encrypted connection, which uses HTTPS, the use of HTTP connections is prohibited.
The following exceptions occur when using HTTP connections:

W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted
java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy

Two suggestions:

  • Use HTTPS
  • Modify Default AndroidManifest.xml Make it allow HTTP connections

Create a new folder xml under res and create a folder called network_Security_Config.xmlFile, the contents of which are as follows

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

And then AndroidMainfest.xml Join in:

<application
 android:networkSecurityConfig="@xml/network_security_config"
/>

that will do
Another way is to AndroidManifest.xml Add a sentence directly

<application 
android:usesCleartextTraffic="true"
/>

9.5 Thread Problem

Starting with Android 4.0, networking can no longer be operated by the main thread, in case the network is bad, it will get stuck, so the operation related to networking needs to open a new thread, and can not be operated by the main thread.

9.6 AVD Issues


This bug has been found by the author for a long time, HTTP connection is OK, server is OK, database is OK, front-end code is OK, then StackOverflow, found that it is AVD problem...

Simply put, unloading APP and restarting AVD succeeded.....

10 Source and JAR Package

10.1 JAR Package

Other versions are available Here Search for downloads.

10.2 Source

11 Last

Author Xiao Bai, what is wrong, please correct, comment on the author will reply well.

12 Reference Sites

1.Android interacts with the Mysql database through the Web server
2.Android High Version Networking Failed
3.IDEA Deployment Web Project
4.ExcuteQuery, executeUpdate and execute of PreparedStatement
5.preparedstatement execute() operation succeeded!But return false
6.HttpServletResponse(1)
7.HttpServletResponse(2)
8.HttpServletRequest
9.HttpUrlConnection
10.java.net.socketexception

If you find the article interesting, you are welcome to comment on it.

At the same time, you are welcome to pay attention to the public number of WeChat: Fanling Road.

Keywords: Android MySQL Database xml

Added by _will on Tue, 23 Jun 2020 20:10:09 +0300