引入
听说其实Android直接连接数据库的情况是比较少的,出于安全和内存都不建议,一般是连接服务器,通过服务器操作数据库。
但毕竟还是有必要掌握这项技能(其实是已经记不清前几天是为什么要写这么个东西了,纠缠几天下来已然混乱)
这几天这是焦头烂额,每一步都会卡一下,网上的博客也是看了不少,没能很直接解决问题(虽然最后的结果也是令我哭笑不得)
可能出现的问题:
JDBC URL中的IP地址或主机名错误
本地DNS服务器无法识别JDBC URL中的主机名
JDBC URL中的端口号丢失或错误
数据库服务器关闭
数据库服务器不接受TCP/IP连接
数据库服务器已用完连接
Java和DB之间的某些东西阻止了连接,例如防火墙或代理可以尝试以下操作:
测试ip地址能否ping通
刷新DNS或使用JDBC URL中的IP地址
根据MySQL DB的my.cnf进行验证
启动数据库服务
验证是否在没有–skip-networking选项的情况下启动mysqld
重新启动数据库并相应地修改代码,以便最终关闭连接
禁用防火墙和/或配置防火墙/代理以允许/转发端口
注意别忘了给app添加internet权限<uses-permission android:name="android.permission.INTERNET"/>
以上是在其他地方看到简单总结的一些要点。
然后要开始来讲故事了。。
Mysql的远程登录授权
Mysql需要在允许远程登录的情况下才能被其他主机访问。像在android中就不可能可以本地登录吧,无论是在实体机中还是虚拟机中。
这里顺便记录一下一些虚拟机中主机的IP地址:
Google模拟器
emulator-5554
开发机地址: [net.eth0.gw]: [10.0.2.2]
模拟器本机地址: [net.gprs.local-ip]: [10.0.2.15]
夜神模拟器
127.0.0.1:62001
开发机地址: [dhcp.eth1.gateway]: 172.17.100.2
模拟器本机地址: [dhcp.eth1.ipaddress]: [172.17.100.15]
逍遥模拟器
127.0.0.1:21503
开发机地址: [dhcp.eth1.gateway]: [10.0.3.2]
模拟器本机地址: [dhcp.eth1.ipaddress]: [10.0.3.15]
开放3306端口
查看3306端口情况1
2$ netstat -an | grep 3306
tcp6 0 0 :::3306 :::* LISTEN
说明3306只是监听本地,拒绝了其他IP访问,mysql默认状态下是不开放对外访问功能。那么开放端口。
打开etc/mysql/my.cnf(也有人说是/etc/mysql/mysql.conf.d/mysqld.cnf),找到bind-address = 127.0.0.1(大概47行)加 # 注释即可。
- 其实可能是因为我是tar二进制直接安装的缘故,没有找到mysql.cnf,但后来也是可以连接上emmm
1 | 连接成功时3306的状态如下 |
MySQL授权
启动mysql.server,进入账户1
2
3
4$ sudo ./mysql.server start
Starting MySQL
[ ok ..
$ mysql -uroot -p
然后问题来了。其他IP的访问授权方式有两种,授权法 和 改表法。注意Mysql5和Mysql8的授权方法有所不同,主要是语法上的改变。
授权法
1 | grant all on 数据库名.表名 to 'root'@'%' identified by 'password'; |
以上在Mysql5下使用,也是网上查到的99%的方法。然而在Mysql8中会报语法错误
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to
your MySQL server version for the right syntax to use near ‘identified by ‘psw’’ at line 1
这个问题也折腾了我一下午,最后是求助了学长才意识到版本的问题。
Mysql8 下的授权方法如下,没错就是没有identified部分。
而且需要 先创建一个用户,在对其进行授权,否则直接grant会报错 You are not allowed to create a user with GRANT。1
2
3
4
5
6
7
8# 先创建用户
create user 'root'@'%' identified by 'pwd';
# 再进行授权
grant all on *.* to 'root'@'%' with grant option;
# 权限刷新
flush privileges;
注意最后 刷新权限。
改表法
其实最后我是通过这个方法完成的。1
2
3
4
5
6mysql> use mysql;
mysql> update user set host='%' where user='root';
Query OK, 1 row affected (0.63 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> flush privileges;
Query OK, 0 rows affected (0.21 sec)
如上,进入mysql数据库,查看user,host可看到root用户的host是localhost。修改该项为%,刷新权限。
我是使用idea集成的数据库工具测试连接,连接成功。
Android/Java数据库连接
导入数据库驱动JDBC
到官网下载获取JDBC的jar包,常见的版本是mysql-connector-java-5.1.47.jar,较新的版本是mysql-connector-java-8.0.13。(有空了会试着把这两个上传)。
导包方式:复制jar到项目的libs目录下
右键jar选择Add as libs
或者直接在app的build.gradle中添加
implementation files(‘libs/mysql-connector-java-5.1.47.jar’)
主要代码
1 | // 动态加载类 |
注:
新建线程
Android不支持在转线程执行网络请求(耗时操作)会有UnsupportedOperationException。
注意包括stmt.executeQuery(str) 方法也是联网操作,也要放在多线程中执行
版本问题
5.0和8.0驱动包有几个地方不同
加载8.0驱动的时候报错
Loading class
'com.mysql.jdbc.Driver'. This is deprecated. The new driver class is 'com.mysql.cj.jdbc.Driver'
. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.提示,8.0包的驱动名应该改为 com.mysql.cj.jdbc.Driver
建立连接时报错
Establishing SSL connection without server’s identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn’t set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to ‘false’. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
mysql8.0是不需要建立ssl连接,需要使用useSSL=false手动关闭。即URL改为 jdbc:mysql://mysql.数据库所在主机IP:3306/表名?useSSL=false
- 8.0驱动包只能兼容minSdkVersion = 26,我的测试机SdkVersion是24的,所以我只能用5.0的驱动包
一些报错
- serverTimezone=UTC,可以指定时区(非必要)
- 数据库或表明出错会有报错:No address associated with hostname,Unknown database
- 用户名或密码错误:Access denied for user (using password: YES)
- 网络原因(无权限,无网络连接,在主线程执行):com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server.
- 注意在适当位置关闭相关资源流
1 | // java |
1 | // android |
我碰到的问题:
以下内容非重点
- Communications link failure
- The last packet sent successfully to the server was 0 milliseconds ago.The driver has not received any packets from the server.
这两个报错贯穿了我两天的debug过程,也大概是android连接Mysql最常见的报错
查到的都是超时相关问题,超时回收机制云云。该问题是程序运行过程中使用的连接池不知道连接被回收了所以报出的异常,解决方案大概是 修改连接池配置 或 修改mysql空闲超时时间配置,否则默认是8小时。
但这是数据库连接中断的问题,我连都连不上。这大年初一的,就是熬夜掉头发,想起这一年的漫漫debug之路,忍不住捏了把汗。
我不抱希望修改了如下URL,然后气急败坏的跑去和我妹看电视。1
jdbc:mysql://localhost:3306/table?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&failOverReadOnly=false
半晌,就在我回来继续时候,看到logout上终于打上了数据库的内容,欣喜若狂,扯着一脸问号的妹妹条了半天广场舞。
接下来的事令我不知该哭还是该笑,倒不是程序又不能跑了,是就在我将上面的指令一个一个删去,尝试找到罪归祸首时,发现已经是把代码恢复到原来状态了,app还是十分乖巧的连上了数据库,即jdbc:mysql://ip:3306/table。
我:?????
刚刚到底发生什么了,难道还有打开某个开关以后他就默认开启之类的操作?
无论如何,总算是成功了。老泪纵横。
愿天下码农和八哥终成眷属。。TAT