在当前大数据时代,数据的流量已经越来越大,有时单单依靠内存来进行数据的处理已经不能满足需求了。因此在开发中,会不可避免地使用到数据库技术,JAVA自然也会支持对数据库操作的支持。
JDBC
JDBC(JAVA Database Connective,JAVA数据库连接技术)是由JAVA提供的一组与平台无关的数据库的操作标准,其本身由一组类和接口组成,并且在操作中将按照严格的顺序执行。
这样的表述表明JAVA的这种技术与平台无关,同时该技术规定了4中数据库操作的形式:
- JDBC-ODBC桥接技术:Windows中的开放数据库连接(Open Database Connectivity, ODBC)是微软提供的数据库编程接口。而JDBC-ODBC桥接技术则是先利用ODBC技术作为数据库的连接方式,然后利用JDBC进行ODBC的连接,以实现数据库操作。该方法使用性能较差,但不需要进行第三方开发包配置,使用较为方便。
- JDBC本地驱动:JDBC本地驱动是由不同的数据库厂商根据JDBC定义的操作标准实现各自的驱动程序,程序可以直接通过JDBC进行数据库的连接操作。该方法操作性好,但需要针对不同的数据库配置匹配对应的驱动程序。
- JDBC网络驱动:JDBC网络驱动将利用特定的数据库连接协议进行数据库的网络连接。此方法可以连接任一服务器的数据库,使用灵活,开发方便。
- JDBC协议驱动:JDBC协议驱动是利用JDBC提供的协议标准,将数据库的操作以特定的网络协议方式进行处理。
连接MySQL数据库
JAVA中数据库的相关操作在java.sql包中,该包中主要包含以下核心类和接口:
- java.sql.DriverManager类:提供数据库的驱动管理,主要负责获取数据库的连接对象
- java.sql.Connection接口:用于描述数据库的连接,可以用该接口关闭连接
- java.sql.Statement接口:数据库的操作接口,通过连接对象打开
- java.sql.PreparedStatement接口:数据库预处理操作接口,通过连接对象打开
- java.sql.ResultSet接口:数据查询结果描述,通过该接口获取查询结果
整体看来,JDBC的操作步骤主要分为四个步骤:
加载数据库驱动程序
所有的JDBC都是由各个不同的数据库厂商提供的数据库驱动程序,这些驱动程序都是以jar包的形式给出。而连接MySQL数据库首先要保证本地存在对应的jar包,要提前在官网下载,并将其路径配置到CLASSPATH变量中。
本文使用的MySQL版本为8.0.20。
通过DriverManager类建立连接
操作数据库首先要建立连接,连接需要:
- 连接地址
- 用户名
- 密码
然后使用DriverManager类进行连接:
static Connection getConnection(String url, String user, String password) // Attempts to establish a connection to the given database URL.
从上面定义看出,数据库连接使用Connection接口对象进行封装,因此只要有一个新Connection对象就表示要连接一次数据库。
利用Statement、PreparedStatement、ResultSet接口实现CRUD操作
Statement createStatement() // Creates a Statement object for sending SQL statements to the database.
PreparedStatement prepareStatement(String sql) // Creates a PreparedStatement object for sending parameterized SQL statements to the database.
释放资源
跟IO操作类似,数据库操作后最好显式释放资源。
import java.sql.*;
public class Demo {
private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://localhost:3306/myemployees?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "********";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
System.out.println(conn);
// CRUD
conn.close();
}
}
执行结果为:
com.mysql.cj.jdbc.ConnectionImpl@2f943d71
上面的代码先利用反射机制进行驱动程序的加载,然后建立了数据库连接,并进行了关闭,并没有实际的CRUD操作。
Statement
Statement接口主要用来进行数据库的CRUD操作,主要方法有:
ResultSet executeQuery(String sql) // Executes the given SQL statement, which returns a single ResultSet object.
int executeUpdate(String sql) // Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.
这里看段代码:
import java.sql.*;
public class Demo {
private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://localhost:3306/myemployees?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "********";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
Statement st = conn.createStatement();
String sql = "select * from departments";
ResultSet res = st.executeQuery(sql);
while(res.next()) {
int depID = res.getInt("department_id");
String depName = res.getString("department_name");
int magID = res.getInt("manager_id");
int locID = res.getInt("location_id");
System.out.println("DepID is:" + depID + ",depName is:" + depName + ",magID is:" + magID + ",locID is:" + locID);
}
sql = "UPDATE departments SET manager_id = 201 WHERE department_id = 10";
int len = st.executeUpdate(sql);
st.close();
conn.close();
}
}
数据的增删改其实逻辑都一样,只要sql语句写的没问题就OK。但是数据查询的结果是由ResultSet来接收的,而ResultSet的设计是按照数据类型的方式来保存返回数据的,这就要求需要针对数据库中每一列的属性进行分别获取。
上面的代码便是利用next判断是否截止,然后按照列属性逐个获取属性值,然后打印。
同时上边的代码只是为了说明一下使用方法,写sql语句并不是目的,在实际的开发中:
- 要避免select *查询,会占用过大的内存
- 要按照数据库的列顺序逐列获取属性值
- 每列数据只能按照顺序取一次
上面的代码还可以修改为:
while(res.next()) {
int depID = res.getInt(1);
String depName = res.getString(2);
int magID = res.getInt(3);
int locID = res.getInt(4);
System.out.println("DepID is:" + depID + ",depName is:" + depName + ",magID is:" + magID + ",locID is:" + locID);
}
这样的写法可以用于select name1,name2...这样的查询语句中,毕竟如果查询和属性值获取都要写重复的字符串有点多余,这样的写法可以按照列顺序进行逐个获取。
PreparedStatement
虽然StateMent能够用于CRUD,但是在进行insert操作时,需要进行字符串拼接操作,这个过程会很容易出错。
而PreparedStatement可以使用占位符sql语句来进行动态设置,这意味着字符串拼接的工作会被省略。
public interface PreparedStatement extends Statement
PreparedStatement prepareStatement(String sql) // Creates a PreparedStatement object for sending parameterized SQL statements to the database.
看下具体使用:
import java.sql.*;
public class Demo {
private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://localhost:3306/myemployees?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "********";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
Statement st = conn.createStatement();
String sql = "select * from departments";
ResultSet res = st.executeQuery(sql);
while(res.next()) {
int depID = res.getInt(1);
String depName = res.getString(2);
int magID = res.getInt(3);
int locID = res.getInt(4);
System.out.println("DepID is:" + depID + ",depName is:" + depName + ",magID is:" + magID + ",locID is:" + locID);
}
sql = "INSERT INTO departments(department_id, department_name, manager_id) VALUES (?,?,?)";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setInt(1,12);
pst.setString(2,"Tom");
pst.setInt(3,102);
int len = pst.executeUpdate();
st.close();
pst.close();
conn.close();
}
}
上面的代码就省略了字符串拼接,而是用?作为占位符,并在后续中赋值,执行sql语句,这样的代码就会整洁一点。
另外也不止查询才可以使用PreparedStatement,其它sql语句也可以使用该接口借助占位符实现sql语句。本文也不是主要介绍sql语句的,只是搞清楚JAVA中数据库操作的逻辑接口,复杂的sql就不赘述了。
批处理与事务
批处理
批处理是指一次性向数据库发出多条操作指令,然后一块执行。
在Statement和PreparedStatement接口中有关于批处理的操作:
// Statement
void addBatch(String sql) // Adds the given SQL command to the current list of commands for this Statement object.
int[] executeBatch() // Submits a batch of commands to the database for execution and if all commands execute successfully, returns an array of update counts.
// PreparedStateMent
void addBatch() // Adds a set of parameters to this PreparedStatement object's batch of commands.
下面的代码就实现了批处理:
import java.sql.*;
import java.util.Arrays;
public class Demo {
private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://localhost:3306/myemployees?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "********";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
Statement st = conn.createStatement();
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1001,12,13)");
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1002,12,14)");
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1003,12,15)");
int res[] = st.executeBatch();
System.out.println(Arrays.toString(res));
st.close();
conn.close();
}
}
事务
事务保证了数据操作的完整性。
JDBC支持事务处理操作,相关方法定义在Connection接口中:
void commit() // Makes all changes made since the previous commit/rollback permanent and releases any database locks currently held by this Connection object.
void rollback() // Undoes all changes made in the current transaction and releases any database locks currently held by this Connection object.
void setAutoCommit(boolean autoCommit) // Sets this connection's auto-commit mode to the given state.
示例代码为:
import java.sql.*;
import java.util.Arrays;
public class Demo {
private static final String DBDRIVER = "com.mysql.cj.jdbc.Driver";
private static final String DBURL = "jdbc:mysql://localhost:3306/myemployees?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASSWORD = "********";
public static void main(String[] args) throws Exception {
Class.forName(DBDRIVER);
Connection conn = DriverManager.getConnection(DBURL,USER,PASSWORD);
Statement st = conn.createStatement();
conn.setAutoCommit(false);
try {
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1004,12,13)");
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1005,12,14)");
st.addBatch("INSERT INTO departments(department_id, department_name, manager_id) VALUES (1006,12,15)");
int res[] = st.executeBatch();
System.out.println(Arrays.toString(res));
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
}
st.close();
conn.close();
}
}
在手动进行事务处理时,首先要取消自动提交,然后在try中执行数据操作,并进行手动提交,而在catch中捕获错误,并进行回滚处理,以保证数据的完整性。