MySQL驅動

      在〈MySQL驅動〉中尚無留言

前言

在此要先聲明一點,Java 或 Android 最好不要直接連線資料庫,因為資料庫的帳密必需寫在app裏,而app一旦被反組譯,資料庫的帳密就全被看光光了。

正確的作法是 app 使用 HttpUrlClient 將資料利用 post 方式傳送給 Web Server,再由 Web Server 寫入資料庫。Web Server可以使用php/asp.net/jsp 撰寫。

上述的方法是比較麻煩,除了要學會寫手機/Java程式,還要學會架設Web Server,並且要學會 php 或 jsp,工程浩大啊。但請聽小弟一言,千萬別使用下面的方法,除了安全性問題外,最重要的是,在手機寫資料庫連線,就必需處理網路突然斷線等莫名奇妙的例外,閃退的機會大增,也會讓整個 app 在日後的維護上變的更加困難。本人可是有著切身之痛,慎之。

所以,下面的方法,看看就好。

Connector/J

Java要與資料庫連線, 需要取得各家資料庫的驅動程式. 各家資料庫都會將其驅動包裝成jar檔, 如MSSQL有jtds.jar及官方的jar. MySQL則為 mysql-connector-java-5.1.47-bin.jar. 可由如下網址下載
https://dev.mysql.com/downloads/connector/j/5.1.html

Android與Database Server直接連線

因為Android 與Database Server直接連線有如下的問題, 所以大部份網路上搜尋到的資訊都是錯誤的. 請小心驗証. 底下的說明及程式碼, 都是經過本人驗証且實作出來的.

網路上一直有人說, Android 是不允許直接跟資料庫伺服器直接連線的, 不論是MSSQL還是MySQL, 其實這是真的. 原因很簡單, 因為要與伺服器連線, 就需要在連線字串中要寫明登入資料庫伺服器的帳號及密碼.
而Android的apk, 是很容易被反組譯(decompile)的, 一旦被反組譯成功, 帳號密碼就馬上現型,駭客就可以用這組帳號密碼直接控制資料庫伺服器.

關於這點, 本人的專案解決方式, 是先用PHP寫一個簡單登入網頁接收Android送入的會員帳號密碼(非登入資料庫的帳密). 然後在PHP連線資料庫查詢此組帳密是否為合法的會員資料. 如果是, 再由PHP傳送伺服器的帳密給Android. Android再用此帳密連入資料庫.

android_mysql1

好了, 這樣就解決帳密被駭的問題了. 不過如果會員之中有人就是駭客的話, 因為伺服器會返回登入資料庫的帳密給Android, 那麼駭客就可以自行隨便撰寫一支程式(用android, php, asp.net都可以)取得帳密. 所以這招是無法防止內部的會員.

目前MySQL也正式發佈了8.0的版本, 如果是使用8.0的Connector/J, 在Android中是禁止直接與伺服器連線的. 所以如果真的有此需求, 就需改為5.7的Connector/J. 5.7的Connector/J可以控制MySQL Server 8.0

總之, Android 與資料庫的溝通, 最好是透過Web server來操作. 所以底下的說明, 採用直接連線的方式, 只是為了方便測試用, 並非商業用途.

若要嚴格的說, 其實Java也不可以直接跟伺服器連線的, 因為Java也很容易被反組譯回去. 但即然各家伺服器都推出 Java的驅動程式, 想必這市場也是有其需求的啦.

載入Connector/J

將上述的 mysql-connector-java-5.1.47-bin.jar copy到Android專案下的libs目錄, 然後對此jar按右鍵/Add as library.
add as library後, build.grnadle會自動增加如下藍色的設定.
另外為了使用Java 8 的Lambda語法, 請手動加入紅色部份.

apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.asuscomm.mahaljsp.mysqltest"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    //底下是為了Lambda的語法, 請手動加入
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //底下是Add as library後自動加入的
    implementation files('libs/mysql-connector-java-5.1.47-bin.jar')
}

Java代碼

1. 先使用 Class.forName(“com.mysql.jdbc.Driver”) 將驅動載入

2. 使用DriverManager.getConnection()取得Connection物件. 請注意, 此物件的取得, 不可使用UI執行緒, 否則無法連線成功, 會發生SQLException, Could not create connection to database server. 這跟Java不一樣, Java可於UI中執行此段程式碼.

3. 使用conn.createStatement()取得Statement物件 stmp. 此部份也一定要在新的執行緒中執行, 在Java中也是如此.

4. 利用stmt(SQL語法).executeQuery() 取得查詢的結果, 結果會放入ResultSet物件rs. 然後再操作rs即可取得個欄位的資料. 此部份也一定要在新的執行緒中執行, 在Java中也是如此.

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try {
            Class.forName("com.mysql.jdbc.Driver");
        }
        catch(ClassNotFoundException e){Log.d("Thomas", e.getMessage());}
        new Thread(()->{
            try {
                Connection conn=DriverManager.getConnection("jdbc:mysql://ip/mydatabase?useSSL=false","account","password");
                Statement stmt = conn.createStatement();
                ResultSet rs = stmt.executeQuery("select * from machine");
                while (rs.next()) {
                    String s = String.format("%s,%s,%s", rs.getInt("mc_id"), rs.getString("mc_name"), rs.getString("mc_hosiery"));
                    Log.d("Thomas", s);
                }
            } catch (SQLException e) {
                Log.d("Thomas", e.getMessage());
            } catch (Exception e) {
                Log.d("Thomas", e.getMessage());
            }
        }).start();
    }
}

Mysql 5.1以上開始會有SSL的警告, 所以需在網址最後加入useSSL=false

權限

Android與資料庫連線, 需取得網路權限, 所以請在AndroidManifest.xml新增如下權限

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

Java 與MySQL 8 連線字串

Java與 MySQL 8 的連線字串不太一樣. 首先, 載入的驅動變更成 com.mysql.cj.jdbc.Driver. 原本舊的已被捨棄.

另外在連線字串中, 最好加入serverTimezone的選項

try {
    Class.forName("com.mysql.cj.jdbc.Driver");
}
catch(ClassNotFoundException e){}
Connection conn=DriverManager.getConnection("jdbc:mysql://ip/mydatabase?serverTimezone=CST&user=帳號&password=密碼");

混淆

若使用mysql driver並將apk混淆時, mysql 的public 方法會找不到, 所以需在 proguard-rules.pro檔案新增如下設定

-dontwarn com.mysql.**
-keep public class com.mysql.** {*;}
-ignorewarnings

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *