JDBC(Java DataBase Connectivity)
是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。
不同的数据库管理系统厂商提供接口的实现类供开发者使用,将写好的
class
文件打包成
jar
包,称之为
驱动。这样一来,开发者不用关心不同厂商的数据库底层实现原理,下载不同的驱动,直接面向接口编程即可。这样做的好处是降低了程序的耦合度,提高了程序的扩展力。
1、JDBC编程步骤
- 注册驱动。即选择需要连接的数据库管理系统,如MySQL、SQL Server等;
- 获取连接
Connection
。即打开JVM
进程和数据库进程之间的通道; - 获取数据库操作对象
Statement
。用来将相关SQL
语句发送到数据库; - 执行
SQL
语句; - 处理查询结果集
ResultSet
。只有执行DQL
语句才会有此步骤; - 释放资源。
常用方法
接口 | 方法 | 作用 |
---|---|---|
DriverManager | static void registerDriver(Driver driver) | 注册给定的驱动程序 |
DriverManager | static Connection getConnection(String url, String user, String password) | 尝试建立与给定数据库URL的连接 |
Connection | Statement createStatement() | 创建数据库操作对象 |
Statement | ResultSet executeQuery(String sql) | 执行给定的select语句 |
Statement | int executeUpdate(String sql) | 执行给定的insert、update、delete语句 |
ResultSet | int getInt(String columnLabel) | 检索当前行中指定列名的整型值 |
ResultSet | String getString(int columnIndex) | 检索当前行中指定列的值,下标从1开始 |
ResultSet | String getString(String columnLabel) | 检索的当前行中指定列名的值 |
ResultSet | boolean next() | 将光标从当前位置向前移动一行 |
注释
int executeUpdate(String sql)
返回的值是sql
语句影响的数据条数;ResultSet executeQuery(String sql)
返回的结果集中,光标初始位置在查询结果的前一行;boolean next()
光标所在行无数据时返回false
。
1.1 示例
//IDEA中已经导入了mysql-connector-java-8.0.17.jar驱动
//package JDBC下有配置文件class.properties
//xzy01为MySQL下的一个数据库,8.0版本要在其后加上?...字符串
url=jdbc:mysql://localhost:3306/xzy01?useSSL=false&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT
user=root
password=123
package JDBC;
import java.sql.*;
import java.util.ResourceBundle;
public class TestJDBC {
public static void main(String[] args) {
Statement mysqlstatement = null;
Connection mysqlconnection = null;
try {
//1、注册MySQL驱动
Driver mysqldriver=new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(mysqldriver);
//2、获取连接,利用资源绑定器
ResourceBundle resource = ResourceBundle.getBundle("JDBC/connection");
String url = resource.getString("url");
String user = resource.getString("user");
String password = resource.getString("password");
mysqlconnection = DriverManager.getConnection(url, user, password);
//3、获取数据库操作对象
mysqlstatement = mysqlconnection.createStatement();
//4、执行DML语句
String sql = "insert into user(name,password) values('Alice','01')";
mysqlstatement.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//5、关闭资源
if (mysqlstatement != null) {
try {
mysqlstatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (mysqlconnection != null) {
try {
mysqlconnection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
说明
- user表结构如下:
mysql> desc user;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+
- 执行结果如下:
mysql> select * from user;
+----+-------+----------+
| ID | name | password |
+----+-------+----------+
| 1 | Alice | 01 |
+----+-------+----------+
- 注册MySQL驱动有更简便的方法:
Class.forName("com.mysql.cj.jdbc.Driver");
。该方法调用时默认实现该包下Driver
接口的静态代码块:DriverManager.registerDriver(new Driver())
,比较方便。
1.2 将代码封装成工具类
我们发现获取连接和释放资源代码过多, 故考虑将其封装在工具类JDBCUtil
中。
package JDBC;
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCUtil {
/**
* 私有化构造方法,防止实例化对象出来
* 工具类中的方法都是静态的,可直接调用
*/
private JDBCUtil() {
}
/**
* 静态代码块在类加载(调用类方法)时仅执行一次
* 即使多次连接时也只需要获取一次驱动
*/
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 将JVM连接到MySQL数据库
* @return 返回连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
ResourceBundle resource = ResourceBundle.getBundle("JDBC/connection");
String url = resource.getString("url");
String user = resource.getString("user");
String password = resource.getString("password");
return DriverManager.getConnection(url, user, password);
}
/**
* 关闭打开的资源通道
* @param c 连接对象
* @param s 数据库操作对象
* @param r 查询结果集
*/
public static void close(Connection c, Statement s, ResultSet r){
if(c!=null) {
try {
c.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(s!=null){
try {
s.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(r!=null){
try {
r.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
1.3 DAO封装
DAO(Data Access Object)
(数据访问对象)是一个面向对象的数据库接口,在实际开发中讲针对某张表的操作封装成对应的类,比如add()
方法表示向相应的表插入数据,可大大减少代码量。开发规则如下:
- DAO类命要求是
表名+Dao
; - DAO类所在的包名要求是
公司域名.dao
。
1.4 实体类(Entity)
当对查询方法(select)进行DAO封装时,需要返回的是查询结果,为了数据传输的方便,往往根据表结构创建对应的实体类,类命即表名。将查询到的每条结果转化为实体类对象,然后装箱返回。
2、SQL注入现象
假如有这么一条查询语句String sql="select * from user where name='"+loginname+"' and password='"+loginpassword+"'";
,模拟用户输入loginname
和loginpassword
之后在数据库中查询有无这样一个账号信息。如果这样输入的话:账号:Alice、密码:a' or 'a'='a
,即使密码没对,数据库也能查询出结果,这是为什么呢?
细心一点的话我们会发现把输入信息带入到查询语句后:select * from user where name='Alice' and password='a' or 'a'='a';
,很明显,输入的信息中含有SQL
关键词or
,参与到SQL
语句的编译,执行结果与预期不符,这就是SQL注入现象。
如果我们不想让用户提供的信息参与到SQL
语句的编译,可以利用java.sql Interface PreparedStatement
(表示预编译的SQL语句的对象)。
与此同时,对提供的SQL
语句编写也有了要求,在需要用户提供的信息处用?
表示占位符,然后预编译SQL
语句,给占位符赋值之后执行SQL
语句。
常用方法
接口 | 方法 | 作用 |
---|---|---|
Connection | PreparedStatement prepareStatement(String sql) | 创建预编译的数据库操作对象 |
PreparedStatement | void setString(int parameterIndex, String x) | 给占位符(索引从1开始)传值,要注意类型 |
PreparedStatement | ResultSet executeQuery() | 执行select查询语句 |
PreparedStatement | int executeUpdate() | 执行insert、update、delete语句 |
示例(记得打开MySQL服务)
package JDBC;
import java.sql.*;
import java.util.*;
public class UserLogin {
public static void main(String[] args) {
Map<String,String> loginme=getMessage();
boolean flag=checkUser(loginme);
if(flag)
System.out.println("登陆成功");
else
System.out.println("登录失败");
}
/**
* 收集用户登陆信息
* @return 以哈希表的方式存储用户名和密码
*/
static Map<String,String> getMessage(){
Map<String,String> loginme=new HashMap<>();
Scanner s=new Scanner(System.in);
System.out.print("用户名:");
String loginname=s.nextLine();
System.out.print("密码:");
String loginpassword=s.nextLine();
loginme.put("loginname",loginname);
loginme.put("loginpassword",loginpassword);
return loginme;
}
/**
* 在数据库中的user表中查询有无用户信息
* @param loginme 登陆信息
* @return 查询成功返回true,否则返回false
*/
static boolean checkUser(Map<String,String> loginme){
Connection mysqlconnection=null;
PreparedStatement mysqlPstatement=null;
ResultSet mysqlresult=null;
boolean flag=false;
try{
//注册驱动并获取连接
mysqlconnection=JDBCUtil.getConnection();
//预编译并执行语句
String loginname=loginme.get("loginname");
String loginpassword=loginme.get("loginpassword");
String sql="select * from user where name=? and password=?";
mysqlPstatement=mysqlconnection.prepareStatement(sql);
//给占位符传值,自动给字符串加''
mysqlPstatement.setString(1,loginname);
mysqlPstatement.setString(2,loginpassword);
//执行sql语句
mysqlresult=mysqlPstatement.executeQuery();
if(mysqlresult.next())
flag=true;
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtil.close(mysqlconnection,mysqlPstatement,mysqlresult);
}
return flag;
}
}
PreparedStatement
和Statement
相比,前者预编译一次可执行多次,效率较高;后者编译一次执行一次。当我们仅需要把值传入到SQL语句中时选择前者;当业务需要有SQL注入时选择后者。比如查询语句为select * from user order by name 字符串(表示升降序)
,用后者可以做到SQL语句拼接,让用户自己选择升序还是降序查询。
3、JDBC事务
和MySQL一样,JDBC默认的事务行为是执行一条SQL语句就自动提交一次。实际业务中,有时需要多条语句联合执行,即开启事务机制。
常用方法
接口 | 方法 | 作用 |
---|---|---|
Connection | void setAutoCommit(boolean autoCommit) | 将此连接的自动提交模式设置为给定状态 |
Connection | void commit() | 使自上次提交/回滚以来所做的所有更改都将永久性 |
Connection | void rollback() | 撤消在当前事务中所做的所有更改 |
4、悲观/乐观锁
悲观锁:有多个事务时,当其中一个事务的SQL语句最后含有for update
关键词时,代表给所选的数据加上了悲观锁,意味着其它事务在当前事务结束之前无法对上锁的数据操作。
乐观锁:事务对数据进行SQL语句操作时会拿到一个当前版本号,修改数据之后再次查看版本号。若版本号没有发生改变,意味着没有其它事务对数据进行修改,那么就提交当前事务并修改版本号,否则回滚当前事务。
欢迎评论区交流~👍