[arch Linux IDEA] 搭配MariaDB的JDBC
Java连接数据库(JDBC编程六步)
注意
本文以
MariaDB
为例,如果中途看到不懂的,又没有注明 “自行CSDN“ ,请继续往下看,后面会有解释在食用本文前,建议先看我的另一篇 文章 ,学习如何导入
mariadb-jdbc
的 jar 包MySQL
也可参考上文,两者仅仅是获取jar包的方法不同,请自行前往 官网 下载官方jdbc
的 jar 包
请根据自己安装的数据库版本下载对应的 jar 包,否则将无法连接到数据库
- 如果你的数据库是
MySQL
,且版本高于6.0,那么连接数据库时,请将本文中的org.mariadb.jdbc.Driver
改为com.mysql.cj.jdbc.Driver
- 如果你的数据库是
MySQL
,且版本低于6.0,那么连接数据库时,请将本文中的org.mariadb.jdbc.Driver
改为com.mysql.jdbc.Driver
- 如果你的数据库是
文中可能有但不限于术语错误、代码错误,请在下方留言,确认后我会尽快修改
准备工作
新建名为 JDBC 的项目
导包
见 注意 第二点
新建名为 mariadb 的 Module
在 mariadb 的 src 下新建两个类(class)
- connection01:用来写方法一
- connection02:用来写方法二
概念
该部分详细讲解JDBC编程的六步,并带你了解一些概念
Java程序员:调用者,面向JDBC接口编程
SUN公司:制定JDBC接口,具体实现类由各大厂商完成
数据库厂家:编写JDBC接口实现类,称为驱动
下标:JDBC中的下标都是从 1
开始
注册驱动
通知Java程序,要连接的是哪一个品牌的数据库
方法一
// 导包
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;public static void main(String[] args){// 一些下文要用到的东西,最重要的是为释放资源部分做铺垫Connection conn = null; // 连接对象Statement stmt = null; // 数据库操作对象ResultSet rs = null; // 结果集try {Driver driver = new org.mariadb.jdbc.Driver();DriverManager.registerDriver(driver); // 注册驱动} catch (SQLException e) {e.printStackTrace();}
}
第16行报错,将光标移到Driver()上,点击
Alt
+Enter
,点击第一个选项Add library 'mariadb-java-client-3.0.5.jar' to classpath
即可因为第17行有异常,所以使用
try{...}catch{...}
捕捉异常,之后步骤的代码都是写在try{...}
里面的我们通常把可能存在异常的代码写在
try{...}
里,catch{...}
捕捉这些异常,并且根据代码块里的代码处理异常,在这里,我们选择把异常输出到控制台e.printStackTrace();
如果
try{...}
里的代码有错误,就会直接跳出try{...}
,执行try{...}
后面的代码。就像swith
语句中遇到了break
一样
写出导包的代码,目的是方便大家了解这段代码中用到了哪些包,之后的代码中除非是没有导过的包,不然不会再写导包的代码了。实际上,IDEA会自动帮我们导包,如果你的IDEA不会自动导包,请自行百度如何开启该功能。
热芝士:你还可以通过键入下面一行代码,一次性导入
sql
下的所有包import java.sql.*;
方法二(推荐)
此处使用到反射,故推荐使用此方法注册驱动
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;public static void main(String[] args) {Connection conn = null;Statement stmt = null;ResultSet rs = null;try {// 这里注册驱动用到了反射,下文还会提到类加载,学完Java SE就知道啥是反射和类加载了,你也可以马上CSDNClass.forName("org.mariadb.jdbc.Driver");} catch (ClassNotFoundException e) { // 处理Class.forName()的异常e.printStackTrace();} catch (SQLException e) {e.printStackTrace(); // 解决之后代码中基于conn、stmt、rs的操作出现的异常}
}
因为第12行有异常,所以使用
try{...}catch{...}
捕捉异常我们可以把第14行到第18行改写成
} catch (Exception e) {e.printStackTrace(); }
获取连接
开启JVM进程与MySQL进程之间的通道
以连接本地数据库为例
String url = "jdbc:mariadb://localhost:3306/stu?serverTimezone=UTC";
String user = "root";
String passwd = "你的MariaDB密码";// 注意, DriverManager 提供了两种 getConnection() 方法,此处使用的是有三个参数的方法,两个变量的方法请自行了解
// 为什么 DriverManager 能提供两个同名的方法呢?这个就是方法重载,也请自行了解
conn = DriverManager.getConnection(url, user, passwd);
url
:统一资源定位符,包含通信协议、IP地址、端口号、资源名jdbc:mariadb
:通信协议localhost
:IP地址3306
:端口号stu
:资源名,要连接的数据库的名字serverTimezone=UTC
:Windows下使用高版本mysql数据库时,建议加上时区,避免执行SQL语句时出现问题
获取数据库操作对象
创建Statement对象,它是专门用来执行SQL语句
stmt = conn.createStatement();
执行SQL语句
DML
Stirng sql = "INSERT INTO VALUES ('000','Name','1997-07-01','M')";statement.executeUpdate(sql);
executeUpdate()
- 执行的
DML
语句,即INSERT
、DELETE
、UPDATE
- 返回值为
int
类型,表示当前语句执行后受影响的行数 - JDBC中的SQL语句不用加分号
DQL
Stirng sql = "INSERT INTO VALUES ('000','Name','1997-07-01','M')";// 用rs接收返回的结果集
rs = statement.executeQuery(sql);
executeQuery()
- 执行
DQL
语句,即SELECT
- 返回值为
ResultSet
类型,内容为查询结果
处理查询结果集
仅当执行的SQL语句是SELECT语句时才有这一步
// 上一步我们已经接收到了结果集,现在遍历结果集 rs ,查看查询到的结果
while (rs.next()) {System.out.println(rs.getString(1));
}
ResultSet
next()
- 将光标向下移动一行,返回值为Boolean
类型,下一行有数据返回true
,没有返回false
getString(String 字段名)
- 根据字段名
取出,无论该数据在表中是何种类型(VARCHAR
、CHAR
、INT
等等),返回值都为String
类型,正因如此,我们推荐使用该方法,以提高程序健壮性getString(int number)
- 返回结果集的第number
列,无论该数据在表中是何种类型(VARCHAR
、CHAR
、INT
等等),返回值都为String
类型以指定类型取出
- 指定类型应该与要取出的数据在表中的类型相同。例如,CHAR
不能用getDouble()
取出,而DOUBLE()
类型就可以executeQuery()
执行
DQL
语句,即SELETE
返回值为
ResultSet
类型,是一个结果集
释放资源
最后创建的进程最先释放
finally {try {if (rs != null) { // 如果rs不为空,才执行第4行rs.close();}} catch (SQLException e) {e.printStackTrace();}try {if (stmt != null) { // 如果stmt不为空,才执行第11行stmt.close();}} catch (SQLException e) {e.printStackTrace();}try {if (conn != null) { // 如果conn不为空,才执行第18行conn.close();}} catch (SQLException e) {e.printStackTrace();}
}
- 释放资源时,最先创建的最后关闭
- 此处将
stmt.close()
和conn.close()
分开写,是因为如果stmt
关闭异常,程序会自动跳出当前的try{...}catch{...}
,导致无法关闭conn
示例代码
以执行 DML 语句为例,你也可以尝试执行 DQL 语句
方法一和方法二的不同之处在注册驱动的方法,方法二使用类加载方法注册驱动,该方法较为常用
创建数据库
CREATE DATABASE stu;
创建表
CREATE TABLE stuinfo (`ID` CHAR(3) NOT NULL UNIQUE PRIMARY KEY,`Point` VARCHAR(10) NOT NULL,`Name` VARCHAR(5) NOT NULL,`Birth` DATE NOT NULL,`Gender` CHAR(1) NOT NULL
)DEFAULT CHARSET=utf8;
Java代码
方法一
import java.sql.*;public class connection01 {public static void main(String[] args) {Connection conn = null;Statement stmt = null;ResultSet rs = null;String url = "jdbc:mariadb://localhost:3306/stu?serverTimezone=UTC";String user = "root";String passwd = "你的MariaDB密码"; // 切记改成你的密码String sql = "INSERT INTO stuinfo VALUES ('001', '100', 'Yao', '1997-07-01', 'M')";// 因为第17、20、23、26行有异常,所以使用 try{...}catch{...} 抛出异常try {// 注册驱动Driver driver = new org.mariadb.jdbc.Driver();DriverManager.registerDriver(driver);// 获取连接conn = DriverManager.getConnection(url, user, passwd);// 获取数据库操作对象stmt = conn.createStatement();// 执行SQL语句stmt.executeUpdate(sql);} catch (SQLException e) { // 解决异常e.printStackTrace();} finally { // 释放资源try {if (rs != null) {rs.close();}} catch (SQLException e) {e.printStackTrace();}try {if (stmt != null) {stmt.close();}} catch (SQLException e) {e.printStackTrace();}try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}}}
}
- 运行程序后,如果出现报错,且报错在第20行,那么请看看你有没有把第10行改成你的密码
方法二(推荐)
import java.sql.*;public class connection02 {public static void main(String[] args) {Connection conn = null;Statement stmt = null;ResultSet rs = null;String url = "jdbc:mariadb://localhost:3306/stu?serverTimezone=UTC";String user = "root";String passwd = "你的MariaDB密码"; // 切记改成你的密码String sql = "INSERT INTO stuinfo VALUES ('002', '100', 'Yiu', '1997-07-01', 'M')";// 因为第16、19、22、25行有异常,所以使用 try{...}catch{...} 抛出异常try {// 注册驱动Class.forName("org.mariadb.jdbc.Driver");// 获取连接conn = DriverManager.getConnection(url, user, passwd);// 获取数据库操作对象stmt = conn.createStatement();// 执行SQL语句stmt.executeUpdate(sql);} catch (ClassNotFoundException e) { // 解决第16行的异常e.printStackTrace();} catch (SQLException e) { // 解决第19、22、25行的异常e.printStackTrace();} finally { // 释放资源try {if (rs != null) {rs.close();}} catch (SQLException e) {e.printStackTrace();}try {if (stmt != null) {stmt.close();}} catch (SQLException e) {e.printStackTrace();}try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}}}
}
- 运行程序后,如果出现报错,且报错在第19行,那么请看看你有没有把第10行改成你的密码
进阶
学完上文的代码,相信你已经能够连接到数据库并对其中的数据进行修改了,接下来的部分我们继续完善代码,解决一些问题
Properties文件
配置文件,文件类型为
*.properties
,进阶中会用到
为什么要用?
在实际开发中,不建议将数据库的连接信息写死在代码中,因为我们面对的客户可能不懂计算机编程,万一客户想更改数据库连接信息,总不能叫人家改代码吧,那用户体验可就差到家了。所以我们通常会将连接信息写在 *.properties
配置文件中,方便用户客户修改。
- 最好是能提供图形化界面,让客户通过该界面间接地修改
*.properties
配置文件,避免客户修改源文件,出现误删等问题
怎么用?
以JDBC为例
在 mariadb 的 src 下新建 File
,并命名为 jdbc.properties
,粘贴以下内容:
url = jdbc:mariadb://localhost:3306/stu?serverTimezone=UTC
user = root
passwd = 你的MariaDB密码 // 记得改成你的密码
用下面的代码替换掉原先的url、user、passwd
import java.util.ResourceBundle;ResourceBundle bundle = ResourceBundle.getProperties("jdbc");
String url = bundle.getString("url");
String user = bundle.getString("user");
String passwd = bundle.getString("passwd");
conn.DriverManager.getConnection(url, user, passwd);
SQL注入
用户输入的信息中含有SQL语句的关键字,并且这些关键字参与了SQL语句的编译过程,导致SQL语句的原意被扭曲,称为SQL注入
eg.查询学生信息
我们在 示例代码 的基础上查询班上学生的信息
// 接收学生信息
Scanner s = new Scanner(System.in);
String stuID = s.nextLine();// 拼接SQL语句
String sql = "SELECT * FROM stuinfo WHERE ID = '"+stuID+"'";// 获取预编译数据库操作对象
ps = conn.preparement(sql);// 执行
rs = ps.executeQuery();// 判断班上有没有这个同学,有输出 yes ,没有输出 no
if (rs.next()) {System.out.println("yes");
} else {System.out.println("no");
}
运行程序,输入以下示例信息
asdf' or '1' = '1'
这些信息在示例代码中没有被录入数据库,应该是查不到的,但程序运行的结果却是 yes
得到错误结果的原因是示例信息中的 or
关键字参与了SQL语句的拼接,得到以下SQL语句
SELECT * FROM stuinfo WHERE ID = "asdf" or '1' = '1'
or
表示或, '1' = '1'
恒成立,所以无论查询的信息存在与否,都会输出 yes
解决办法
使用预编译的数据库操作对象
// 接收学生信息
Scanner s = new Scanner(System.in);
String stuID = s.nextLine();PreparedStatement ps = null;
String sql = "SELECT * FROM stuinfo WHERE ID = ?";// 将sql传给DBMS进行预编译
ps = conn.prepareStatement(sql);// 给占位符传值,第一个参数为占位符下标,第二个参数为值
// 所以,我们还可以传递int类型的值,使用 setInt() 方法
ps.setString(1, stuID);rs = ps.executeQuery();
PreparedStatement
与Statement
的联系与区别PreparedStatement
继承自Statement
PreparedStatement
的执行效率高于Statement
-PreparedStatement
的SQL语句是固定写死的,它只需要编译一次,尽管后面有给占位符传值的步骤,但本质上SQL语句不会变化,也就不需要再编译
-Statement
的SQL语句每次都会变化,所以每次都需要进行编译,执行效率更低PreparedStatement
会在编译阶段做类型的安全检查- SQL语句中的
?
叫做占位符,接收一个值
总结
除非业务要求必须支持SQL注入,否则安全起见都使用 PreparedStatement
JDBC事务机制
只要执行任意一条DML语句,就会自动提交一次
实际编程中,JDBC的默认机制往往是我们不希望看到的
eg.银行转账
创建账户余额表
CREATE TABLE a_b (`Account` CHAR(1) NOT NULL UNIQUE PRIMARY KEY,`Balance` DOUBLE(7,2) NOT NULL
)DEFAULT CHARSET=utf8;
A账户有200元,B账户有0元
INSERT INTO a_b VALUES (001, 200), (002, 0);
正常情况下,以下代码就可以实现A账户转100元给B账户
PreparedStatement ps = null;
String sql = "UPDATE a_b SET Balance = ? where Account = ?";// 修改A账户的余额,我们叫它操作1好了
ps.setDouble(1, 100);
ps.setString(2, "A");
int count = ps.executeUpdate();// 修改B账户的余额,我们叫它操作2好了
ps.setDouble(1, 100);
ps.setString(2, "B");
count += ps.executeUpdate();System.ou.println(count); // 如果程序执行正常,输出的count应该等于2
但如果第4行到第10行间出现错误,就会导致修改余额异常
## 在Konsole中把A、B账户的余额修改为初始值
UPDATE a_b SET Balance = 200 WHERE Account = "A";
UPDATE a_b SET Balance = 0 WHERE Account = "B";
在第8行加入以下代码(制造异常,来演示JDBC自动提交机制
// 此处会产生空指针错误,程序跳出当前 try{...}
String s = null;
s.toString();
再次执行程序,我们就会发现,A账户的钱少了,B账户的钱却没有增加。
上面的这些代码中,操作1和操作2是两个事务,根据事务隔离性可知,操作1和操作2各自独立,它们的执行结果不会影响到彼此
解决办法
我们可以让操作1和操作2互相影响,即把操作1和操作2放到同一个事务中,利用事务的一致性,就可以让操作1和操作2要么同时成功,要么同时失败。
关闭JDBC的自动提交(第7行),改为手动提交(第24行),如果程序能执行到24行,那么就说明前面没有出现错误,提交事务,转账成功。并在最后判断是否提交成功,没成功就进行回滚(回滚应该理解为把事务里的待提交的SQL语句删掉,然后关闭事务)。
try {Class.forName("org.mariadb.jdbc.Driver");conn = DriverManager.getConnection(url, user, passwd);String sql = "UPDATE a_b SET Balance = ? where Account = ?";ps = conn.prepareStatement(sql);// 关闭自动提交,开启事务conn.setAutoCommit(false);// 修改A账户的余额ps.setDouble(1, 100);ps.setInt(2, 001);int count = ps.executeUpdate();// 此处会产生空指针错误,程序跳出当前 try{...} ,即程序将从第16行直接跳到第25行String s = null;s.toString();// 修改B账户的余额ps.setDouble(1, 100);ps.setInt(2, 002);count += ps.executeUpdate();System.out.println(count == 2 ? "转账成功" : "转账失败");// 手动提交,关闭事务conn.commit();
} catch (Exception e) {// 判断提交是否成功,失败就回滚if (conn != null) {try {conn.rollback(); // 清空操作1和操作2,关闭事务} catch (SQLException e) {e.printStackTrace();}}e.printStackTrace();
}
我想起以前看的一部喜剧,在外漂泊的主人公阿蔡读了同乡从老家带来书信后伤心极了,放声大哭。同乡问他怎么哭的那么伤心,阿蔡说信里写他的妈妈病得很严重,请了好多郎中来看病,都说他的妈妈的病要很多钱才能医好,否则命不久矣,可自己却拿不出钱来,想着自己不能尽孝道,所以才哭得那么伤心。同乡拿过信封说,这里面不是还有一张嘛,我出门的时候你妈妈还好好的,怎么可能命不久矣嘞。阿蔡拿过信封里的第二张信看后果然破涕为笑:好在后来遇到神医,阿蔡的妈妈才得治病好了。
如果字写小点,都写在一张信纸上,就不会闹出这样的笑话了。这和我们上面讲的情况还挺像的。
[arch Linux IDEA] 搭配MariaDB的JDBC相关推荐
- 数据库:MySQL和MariaDB的JDBC连接
目录 QUESTION:MySQL和MariaDB的JDBC连接? ANSWER: 区别与联系: MySQL: MariaDB: java连接mariaDB数据库的设置:(tomcat 8) 第一种方 ...
- Arch Linux 指南——安装基本系统
目录 Arch Linux 指南--安装基本系统 安装前准备 环境检查 分区 安装 配置基础系统 完成安装 Arch Linux 指南--安装基本系统 本文是自己安装 Arch Linux 的过程,记 ...
- archlinux php imagemagick,Arch Linux服务器启用Imagemagick PHP扩展
我将一些WordPress网站搬迁到了Arch Linux服务器.我在Arch Linux服务器上安装了Nginx. MariaDB以及PHP7.但是WordPress无法生成缩略图了.我查看了wp- ...
- Arch Linux图文安装教程(2022.08.01)
不算是原创,只是整理了一下.方便自己下次安装,如有补充,可在本帖留言. 参考文章:2022.5 archlinux详细安装过程 - 知乎 参考文章:Arch Linux 才是永远的神!安装图文详解,高 ...
- Arch Linux 安装简明流程
Arch Linux 安装简明流程 这是一篇为 GPT/EFI 引导 的电脑安装 Arch Linux(双系统)的中文简明流程,尽可能省略了可以省略的流程与文字以使得篇幅尽量短小,基本上基于 Arch ...
- Linux QQ(Ubuntu系、Debian系、红帽系、Arch Linux系)
Linux QQ ~~~~~~ 对于喜爱一些把Linux系列系统当做生活,工作最佳选择的时候,同时也要忍受同外界失去"联系"的痛苦.Linux下,目前在国内可供选择的社交 ...
- arch linux安装_如何从头开始安装Arch Linux
arch linux安装 by Andrea Giammarchi 由Andrea Giammarchi In this article, you'll learn how to install Ar ...
- 为什么应该安装使用 Arch Linux
Arch Linux 无疑是 Linux 高级用户最好的发行版之一.但是在安装 Arch 之前,您应该了解一些关于 Arch 的事情. 安装大多数 Linux 发行版时,您只需下载 ISO,创建可启动 ...
- arch Linux 安装完,无法通过 SSH 远程连接 root 用户问题
访问 arch Linux 主机的该文件 [root@eric-laptop ~]# vim /etc/ssh/sshd_config 对应注释部分后边补上下边三行: LoginGraceTime 1 ...
最新文章
- php实现调查结果百分比显示,Php文本游戏试图根据百分比做出结果
- win10用什么软件测试硬件,Win10系统下硬件设备检测工具的使用方法
- python语言程序设计——深入学习python
- 是否提交由npm 5创建的package-lock.json文件?
- 数学篇(三)向量的基本运算
- 计算机中用户权利和用户权限,揭秘:Win7系统用户和组权限说明
- 01 | 基础架构:一条 SQL 查询语句是如何执行的
- 双端队列 HDOJ 3530 Subsequence
- SAP Cloud for Customer Cloud Application Studio的一些使用技巧(持续更新)
- 【渝粤教育】国家开放大学2018年秋季 1317T社会工作行政(本) 参考试题
- linux 命令 kps,Linux顶级命令.内存使用情况
- IT程序员在北京可以选择哪些国企、央企以及研究所?
- java 快排_秋招|字节跳动Java后台已上岸,发个面经回馈牛油
- kubernetes挂载ceph rbd和cephfs的方法
- 药用计算机题目,医用计算机基础_超星尔雅_题库及答案
- 在输入框输入时限制输入框只能输入正整数以及两位小数正则表达式
- centos7下显卡型号查询
- Android app本地切换logo和名称
- linux脚本执行SQL文件创建表,shell脚本执行sql文件chrome安装
- Microsoft .NET Framework 4(独立安装程序)