JDBC(Java Database Connectivity,Java数据库连接)是一组独立于任何数据库管理系统(DBMS)的API,声明在java.sql
和javax.sql
包中。它由SUN公司提供的一套接口规范,各个数据库厂商根据该规范提供具体的实现类,这些实现类的集合构成了数据库驱动JAR包。
1. 数据库管理系统
常见的数据库管理系统(DBMS)包括:MySQL、Oracle、DB2、SQL Server等。
2. 接口概述
Java中的接口可以包含以下内容:
- 公共的静态常量:由
public static final
修饰的变量。 - 公共的抽象方法:未实现的方法,需要具体的实现类来提供方法体。
- 默认方法:使用
default
关键字修饰的方法,可以在接口中提供默认的实现。 - 静态方法:使用
static
关键字修饰的方法,可以在接口中定义静态方法。
3. JDBC技术的组成
JDBC技术主要包含两个部分:
- Java API(
java.sql
和javax.sql
包):为了保证项目代码的可移植性和可维护性,SUN公司制定了Java程序连接各种数据库的统一接口规范。无论连接哪一种DBMS软件,Java代码都可以保持一致性。 - 数据库厂商提供的驱动JAR包:由于各个数据库厂商的DBMS软件内部实现各有不同,具体的SQL操作(增、删、改、查)只有数据库厂商最为清楚,因此接口规范的实现由各个数据库厂商自行完成。
4. JDBC的作用
使用JDBC技术,可以通过Java代码对数据库中的数据进行增、删、改、查等操作。
【重点】JDBC连接的步骤
JDBC连接数据库的步骤通常包括以下五个部分:
1. 注册驱动
- 引入驱动JAR包:将数据库驱动的JAR包复制到项目的
lib
目录中。 - 添加到构建路径:将驱动JAR包添加到项目的Build Path中。
- 加载驱动类:通过
Class.forName("com.mysql.cj.jdbc.Driver")
方法,将驱动类加载到内存中。
// 加载MySQL驱动类
Class.forName("com.mysql.cj.jdbc.Driver");
注意事项:对于新版的JDBC驱动,可以省略这一步,驱动程序会自动注册。但为了兼容性,建议手动加载。
2. 获取Connection连接对象
通过驱动管理器获取数据库连接对象:
// 获取数据库连接
String url = "jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username = "yourUsername";
String password = "yourPassword";
Connection conn = DriverManager.getConnection(url, username, password);
- DriverManager的作用:用于获取
Connection
对象。如果成功获取到Connection
,则表示已经与数据库建立了连接。 - URL的作用:标识数据库的位置,告诉JDBC程序要连接的数据库。URL的语法如下:
jdbc:mysql://ip地址:端口号/数据库名?参数名=参数值
例如:
jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false
其中,各参数的含义如下:
-
- useUnicode=true:表示使用Unicode字符集,允许使用中文等非ASCII字符。
- characterEncoding=utf8:指定字符编码方式为UTF-8。
- useSSL=false:禁用SSL连接,避免因为未配置SSL证书而导致连接失败。
Connection对象的常用方法
- StatementcreateStatement():创建用于发送简单SQL语句的Statement对象。
- PreparedStatementprepareStatement(String sql):创建用于发送预编译SQL语句的PreparedStatement对象。推荐使用。
- CallableStatementprepareCall(String sql):创建用于调用存储过程的CallableStatement对象。
- void setAutoCommit(boolean autoCommit):设置事务是否自动提交。如果设置为false,则需要手动提交事务。
- void commit():提交事务。
- void rollback():回滚事务。
注意事项:在实际开发中,通常使用PreparedStatement
替代Statement
,以提高性能并防止SQL注入。
3. 创建Statement,执行SQL语句
1. 编写SQL语句
主要是DML(数据操作语言,如INSERT、UPDATE、DELETE)和DQL(数据查询语言,如SELECT)语句。
2. 创建Statement或PreparedStatement对象
使用Statement对象:
// 使用Statement对象
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM users WHERE id = " + userId;
ResultSet rs = stmt.executeQuery(sql);
使用PreparedStatement对象:
// 使用PreparedStatement对象
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
PreparedStatement preparedStatement = connection.prepareStatement(str5);
// 设置SQL语句中的第一个参数(?)的值为userId,这里的1表示参数的位置索引。
//1代表第一个?
preparedStatement.setString(1,"S1002");
preparedStatement.setInt(2,14);
preparedStatement.executeQuery();
两者的区别:
- 安全性:
Statement
直接拼接SQL语句,容易受到SQL注入攻击;PreparedStatement
使用参数占位符,能够防止SQL注入。 - 性能:
PreparedStatement
会预编译SQL语句,执行效率更高,尤其是在批量执行相似语句时。 - 可读性:
PreparedStatement
代码更清晰,参数绑定方式使代码更易维护。
建议始终使用PreparedStatement
替代Statement
。
3. 执行SQL语句
- 增、删、改操作:使用
executeUpdate()
方法,返回受影响的行数。 - 查询:调用executeQuery方法,Statement对象调用方法,需要传递SQL参数;PreparedStatement调用上述方法,不需要传递SQL参数;
- PreparedStatement批处理
-
- addBatch(String sql):把多条sql语句放到一个批处理中。
- executeBatch():向数据库发送一批sql语句执行。
使用Statement执行增删改:
Statement stmt = conn.createStatement();
String insertSql = "INSERT INTO users (name, email) VALUES ('张三', 'zhangsan@example.com')";
int rowsAffected = stmt.executeUpdate(insertSql);
使用PreparedStatement执行增删改:
String insertSql = "INSERT INTO users (name, email) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(insertSql);
pstmt.setString(1, "张三");
pstmt.setString(2, "zhangsan@example.com");
int rowsAffected = pstmt.executeUpdate();
- 查询操作:使用
executeQuery()
方法,返回ResultSet
结果集。
使用Statement执行查询:
Statement stmt = conn.createStatement();
String selectSql = "SELECT * FROM users WHERE id = " + userId;
ResultSet rs = stmt.executeQuery(selectSql);
使用PreparedStatement执行查询:
String selectSql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(selectSql);
pstmt.setInt(1, userId);
ResultSet rs = pstmt.executeQuery();
- PreparedStatement批处理操作:
-
- url添加rewriteBatchedStatements=true
- preparedStatement.addBatch();代替原本的修改查询操作
- preparedStatement.executeBatch();结束操作
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/studentinfodb?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&useSSL=false";
String str1="insert into student(sno,sname,ssex,sage,stel) values (?,?,?,?,?)";
Connection connection = DriverManager.getConnection(url, "root", "1234");
PreparedStatement preparedStatement = connection.prepareStatement(str1);
for (int i = 1; i <= 200; i++) {
preparedStatement.setString(1,"S100"+i);
preparedStatement.setString(2,"username_"+i);
preparedStatement.setString(3,"男");
preparedStatement.setInt(4,18);
preparedStatement.setInt(5,123456789);
preparedStatement.addBatch();//addBatch()代替executeUpdate()和executeQuery()
}
preparedStatement.executeBatch();
注意事项:使用PreparedStatement
时,SQL语句中的参数使用?
占位符,然后通过setXxx()
方法设置参数值。
4. 处理执行结果
1. 处理增、删、改的结果
executeUpdate()
方法返回一个整数,表示受影响的行数。
if (rowsAffected > 0) {
System.out.println("操作成功,影响了 " + rowsAffected + " 行数据。");
} else {
System.out.println("操作失败,未影响任何数据。");
}
2. 处理查询的结果
executeQuery()
方法返回一个ResultSet
对象,包含查询结果。
//1.查询返回一个ResultSet类的结果集对象
resultSet = statement.executeQuery(str4);
//resultSet.next()判断是否为空,返回布尔值
//返回一个数据 select count(1) from student
if(resultSet.next()){
//选择get什么取决于查出的是什么
//内容为查出表的列下标,从1开始
int dCount = resultSet.getInt(1);
System.out.println("dCount = " + dCount);
}
//2.查询返回一行数据 select * from student where sno='S1006'
if(resultSet.next()){
String sno = resultSet.getString(1);
String sname = resultSet.getString(2);
String ssex = resultSet.getString(3);
int sage = resultSet.getInt(4);
}
//3.查询返回的是一个表 select * from student
while(resultSet.next()){
//注意这里的是输出表的列名,如果起别名就是别名
String sno = resultSet.getString("sno");
String sname = resultSet.getString("sname");
String ssex = resultSet.getString("ssex");
int sage = resultSet.getInt("sage");
}
ResultSet对象的常用方法
- boolean next():移动光标到下一行,返回true表示有数据。
- String getString(int columnIndex) / String getString(String columnName):获取VARCHAR、CHAR等类型的数据。
- int getInt(int columnIndex) / int getInt(String columnName):获取INT类型的数据。
- float getFloat(int columnIndex) / float getFloat(String columnName):获取FLOAT类型的数据。
- java.sql.Date getDate(int columnIndex) / java.sql.Date getDate(String columnName):获取DATE类型的数据。
- boolean getBoolean(int columnIndex) / boolean getBoolean(String columnName):获取BOOLEAN类型的数据。
- Object getObject(int columnIndex) / Object getObject(String columnName):获取任意类型的数据。
注意事项:列索引从1开始,而不是0。建议使用列名获取数据,提高代码的可读性和维护性。
5. 释放资源
按照先开后关的原则,依次关闭ResultSet
、Statement
或PreparedStatement
、Connection
等资源。
// 关闭ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭Statement或PreparedStatement
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 关闭Connection
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
注意事项:为了避免资源泄漏,建议在finally
块中关闭资源,或者使用try-with-resources
语句。
补充注意事项
- 防止SQL注入:始终使用
PreparedStatement
或CallableStatement
来替代Statement
,避免在SQL语句中直接拼接用户输入的参数。 - 事务管理:对于涉及多个步骤的数据库操作,考虑使用事务,确保数据的一致性。记得在操作前设置
conn.setAutoCommit(false)
,操作完成后提交或回滚事务。 - 异常处理:所有的数据库操作都可能抛出
SQLException
,需要进行适当的异常处理。 - 数据库连接池:在实际开发中,频繁创建和关闭数据库连接会影响性能,建议使用数据库连接池(如Druid、C3P0等)来管理连接。
JDBC执行SQL事例
JDBC使用Statement
思路:
- 首先获取驱动
- 创建连接对象
- 获取Statement声明对象来操控数据库
- 使用statement对象操控数据库
- 关闭资源
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006'";
String str4="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno='S1006' ";
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
//为什么提到上面
//1.因为finally要关闭,如果不定义全局finally找不到connection对象
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
try {
//1.首先获取驱动
Class.forName(jdbc);
//2.创建连接对象
connection = DriverManager.getConnection(url, username, password);
//3.获取Statement声明对象来操控数据库
statement = connection.createStatement();
//4.使用statement对象操控数据库
//4.1增删改返回被影响的数据库行数
int i = statement.executeUpdate(str2);
System.out.println("被影响的行数"+i);
//4.2查询返回一个ResultSet类的结果集对象
resultSet = statement.executeQuery(str4);
//resultSet.next()判断是否为空,返回布尔值
//4.2.1返回一个数据 select count(1) from student
if(resultSet.next()){
//选择get什么取决于查出的是什么
//内容为查出表的列下标,从1开始
int dCount = resultSet.getInt(1);
System.out.println("dCount = " + dCount);
}
//4.2.1查询返回一行数据 select * from student where sno='S1006'
if(resultSet.next()){
String sno = resultSet.getString(1);
String sname = resultSet.getString(2);
String ssex = resultSet.getString(3);
int sage = resultSet.getInt(4);
}
//4.2.3查询返回的是一个表 select * from student
while(resultSet.next()){
//注意这里的是输出表的列名,如果起别名就是别名
String sno = resultSet.getString("sno");
String sname = resultSet.getString("sname");
String ssex = resultSet.getString("ssex");
int sage = resultSet.getInt("sage");
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally{
//注意:先打开的后关闭
//判断是否为空,因为有可能进入异常导致resultSet没有
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement!=null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
PreparedStatement和事务
注意:
- 手动开启事务后一定要再改为自动
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006'";
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
Connection connection=null;
PreparedStatement preparedStatement =null;
ResultSet resultSet=null;
try {
Class.forName(jdbc);
connection = DriverManager.getConnection(url, username, password);
//开启事务====>把自动改为手动
connection.setAutoCommit(false);
System.out.println("事务开始手动");
preparedStatement = connection.prepareStatement(str2);
preparedStatement.executeUpdate();
//开启第二条数据库操作
preparedStatement = connection.prepareStatement(str5);
//需要插入什么类型就set什么,下标为?的下标
preparedStatement.setString(1,"S1006");
preparedStatement.setInt(2,14);
//其余与statement一样,知识方法没有参数
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
String sno = resultSet.getString(1);
String sname = resultSet.getString(2);
String ssex = resultSet.getString(3);
int sage = resultSet.getInt(4);
}
//手动提交是事务---运行到这里代表没有异常,可以提交
connection.commit();
} catch (Exception e) {
//开启回滚---运行到这里代表有异常,进行回滚
connection.rollback();
throw new RuntimeException(e);
} finally{
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection!=null){
try {
//恢复事务自动提交
connection.setAutoCommit(true);
System.out.println("事务自动提交");
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
开始批处理
- url添加rewriteBatchedStatements=true
- preparedStatement.addBatch();代替原本的修改查询操作
- preparedStatement.executeBatch();结束操作
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/studentinfodb?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8&useSSL=false";
String str1="insert into student(sno,sname,ssex,sage,stel) values (?,?,?,?,?)";
Connection connection = DriverManager.getConnection(url, "root", "1234");
PreparedStatement preparedStatement = connection.prepareStatement(str1);
long l = System.currentTimeMillis();
for (int i = 1; i <= 200; i++) {
preparedStatement.setString(1,"S100"+i);
preparedStatement.setString(2,"username_"+i);
preparedStatement.setString(3,"男");
preparedStatement.setInt(4,18);
preparedStatement.setInt(5,123456789);
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
long l1 = System.currentTimeMillis();
System.out.println(l1-l);
preparedStatement.close();
connection.close();
DruidPooled连接池
步骤:
- 创建DruidDataSource连接池对象
- 为数据源设置参数[(1)设置基本参数]
- 设置连接数等参数
- 通过数据源获取Connection对象
- 关闭Connection资源,这里关闭,就相当于归还回池中
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
String str1="insert into student(sno,sname,ssex,sage,stel) values ('S1006','小二郎','男',14,321323)";
String str2="UPDATE student SET sname='小三郎' WHERE sno='S1006";
String str3="DELETE FROM student WHERE sno='S1006'";
String str4="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno='S1006' ";
DruidPooledConnection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet=null;
try {
//创建数据库连接池
DruidDataSource druidDataSource = new DruidDataSource();
//为数据源设置参数[(1)设置基本参数]
druidDataSource.setDriverClassName(jdbc);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
//(2)设置连接数等参数
druidDataSource.setInitialSize(5);//初始化几个连接对象
druidDataSource.setMinIdle(3);//最少保留几个
druidDataSource.setMaxActive(8);//最大几个
druidDataSource.setMaxWait(1000);//最多等待多久
//通过数据源获取Connection对象
connection = druidDataSource.getConnection();
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(str4);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
String sno=resultSet.getString(1);
String sname=resultSet.getString(2);
int sage=resultSet.getInt(4);
System.out.println(sno+sname+sage);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
resultSet.close();
preparedStatement.close();
//如果这里没有关闭,就相当于没有还,这里关闭,是还回池中
connection.close();
}
创建JDBC工具类
版本一
private static DruidDataSource druidDataSource;
static {
String jdbc="com.mysql.cj.jdbc.Driver";
String url="jdbc:mysql://localhost:3306/studentinfodb?useUnicode=true&characterEncoding=utf8&useSSL=false";
String username="root";
String password="1234";
druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(jdbc);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
//(2)设置连接数等参数
druidDataSource.setInitialSize(5);
druidDataSource.setMinIdle(3);
druidDataSource.setMaxActive(8);
druidDataSource.setMaxWait(1000);
}
public static Connection getConnection(){
DruidPooledConnection connection=null;
try {
connection = druidDataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
public static void releaseConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
版本二
缺点,pro文件的键值对名字需要和Druid包定义的一样
private static DataSource dataSource;
static {
Properties properties = new Properties();
try {
properties.load(JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
Connection connection=null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
public static void releaseConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/studentinfodb?
useUnicode=true&characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true
username=root
password=1234
initialSize=5
maxActive=10
minIdle=3
maxWait=1000
使用外部的QueryRunner
- 先导入Druid连接池的jar包
- 创建Druid连接池对象
- 给数据源设置参数(JDBC连接驱动,数据库url,username,password)
- 设置数据源的连接池参数(初始化连接数,最小连接数,最大连接数,最大等待时间等)
- 通过数据源获取connection连接对象
- 创建QueryRunner对象来代替原本的Statement对象和PreparedStatement对象
//获取QueryRunner对象
QueryRunner queryRunner = new QueryRunner();
//获取connection连接对象
Connection connection = JDBC_utils2.getConnection();
//使用update增删改
String insert_sql="insert into student(sno,sname,ssex,sage,stel) values ('S1006','小二郎','男',14,321323)";
int update = queryRunner.update(connection, insert_sql);
System.out.println("受影响行数"+update);
//返回一个数据
String str4="SELECT count(1) FROM student";
Long query = queryRunner.query(connection, str4, new ScalarHandler<>());
System.out.println("总数为"+query);
//返回一行数据
String str5="SELECT sno, sname, ssex, sage, stel, saddress FROM student WHERE sno = ? AND sage = ?";
Student s1006 = queryRunner.query(connection, str5, new BeanHandler<>(Student.class), "S1006", 14);
System.out.println(s1006);
//返回多行数据
String str6="select * from student";
List<Student> query1 = queryRunner.query(connection, str6, new BeanListHandler<>(Student.class));
query1.forEach(System.out::println);
//关闭connection连接对象
JDBC_utils2.releaseConnection(connection);
QueryRunner注意事项
queryRunner.query返回的是一个long类型
String str6="select count(1) from student";
Long query = queryRunner.query(connection, str6, new ScalarHandler<>());
QueryRunner对象原理
执行PreparedStatement需要先获取connection对象,以及sql语句,如果是query还需要返回结果集所以需要一个结果集类型,以及需要插入的值,由于插入个数不确定所以使用不定参数。方法内部通过数组的方式来在sql中插入值。
- 获取数据库连接:需要传入一个
Connection
对象。 - 准备SQL语句:需要传入一个SQL字符串。
- 设置参数:由于参数数量不确定,使用可变参数(Varargs)或参数数组。
- 执行语句:根据需求执行查询或更新操作。
- 处理结果集:对于查询操作,需要返回结果集,通常通过
ResultSetHandler
接口处理结果集。
public class MyQueryTool {
public <T> List<T> query(Connection connection, String sql, Class<T> beanClass, Object... params) {
QueryRunner runner = new QueryRunner();
try {
// 使用BeanListHandler将结果映射为对象
BeanListHandler<T> handler = new BeanListHandler<>(beanClass);
return runner.query(connection, sql, handler, params);
} catch (SQLException e) {
e.printStackTrace();
return null; // 根据需要处理异常
}
}
}