JDBC
一、来一个JDBC程序
Java Database Connectivity也就是java数据库连接,就是用Java来连接数据库进行一些操作。
假如有这样一张表:
-- 创建数据库learjdbc:
DROP DATABASE IF EXISTS learnjdbc;
CREATE DATABASE learnjdbc;
-- 创建表students:
USE learnjdbc;
CREATE TABLE students (
id BIGINT AUTO_INCREMENT NOT NULL,
NAME VARCHAR(50) NOT NULL,
gender TINYINT(1) NOT NULL,
grade INT NOT NULL,
score INT NOT NULL,
PRIMARY KEY(id)
) ENGINE=INNODB DEFAULT CHARSET=UTF8;
-- 插入初始数据:
INSERT INTO students (NAME, gender, grade, score) VALUES ('小明', 1, 1, 88);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小红', 1, 1, 95);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小军', 0, 1, 93);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小白', 0, 1, 100);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小牛', 1, 2, 96);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小兵', 1, 2, 99);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小强', 0, 2, 86);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小乔', 0, 2, 79);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小青', 1, 3, 85);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小王', 1, 3, 90);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小林', 0, 3, 91);
INSERT INTO students (NAME, gender, grade, score) VALUES ('小贝', 0, 3, 77);
我们现在用jdbc连接它,查询里面的数据
首先要导包:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
直接上代码:
public class JDBCTest01 {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 用户信息和url
String url = "jdbc:mysql://localhost:3306/learnjdbc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8";
String username = "root";
String password = "123456";
// 3. 连接成功 connection 对象 代表数据库
Connection connection = DriverManager.getConnection(url, username, password);
// 4. 执行sql对象 statement 执行sql的对象
Statement statement = connection.createStatement();
// 5. 执行sql的对象去执行sql,可能存在结果,查看返回结果
String sql = "SELECT * FROM students";
ResultSet resultSet = statement.executeQuery(sql);
System.out.println("学号\t\t" + "姓名\t\t" + "性别\t\t" + "年级\t\t" + "分数\t\t");
while (resultSet.next()) {
System.out.print(resultSet.getObject("id") + "\t\t");
System.out.print(resultSet.getObject("name") + "\t\t");
System.out.print(((resultSet.getInt("gender") == 0) ? "男\t\t" : "女\t\t") );
System.out.print(resultSet.getObject("grade") + "\t\t");
System.out.print(resultSet.getObject("score") + "\t\t");
System.out.println();
}
// 6. 释放连接,耗资源,用完就释放掉
resultSet.close();
statement.close();
connection.close();
}
}
url:
协议://主机地址:端口号/数据库名?参数1&参数2&参数3&
例如 连接mysql的url:jdbc:mysql://localhost:3306/learnjdbc?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
Connection对象:代表数据库,也可以通过这个对象设置一些参数,如
Connection connectiono = DriverManager.getConnection(url, username, password);
// 关闭自动提交
connection.setAutoCommit(false);
// 事务回滚
connection.rollback();
// 事务提交
connection.commit();
等等……
Statement对象:执行sql语句的对象,可以通过此对象执行sql语句并返回一些值,如:
Statement statement = connection.createStatement();
statement.executeQuery(String sql);
// 执行查询语句,返回一个ResultSet对象,就是结果。
statement.executeUpdate(String sql);
// 执行增、删、改语句,并返回受影响行数。
statement.execute(String sql);
// 执行sql语句,并返回是否成功。
ResultSet对象:获取查询的结果,可以使用它的get方法获得数据,如:
ResultSet resultSet = statement.executeQuery(sql);
resultSet.next(); // 移动到下一个数据
resultSet.getObject("id"); // 获得id字段的数据,返回Object对象
resultSet.next(); // 移动到下一个数据
resultSet.getString("id"); // 获得id字段的数据,返回String对象
// 类似的还有
resultSet.getInt("id"); // 获得id字段的数据,返回一个int
// 等等
二、Statement对象实现CRUD
Statement对象,用于向数据库发送sql语句,用它实现CRUD,只需要记住两个方法:
- executeQuery(sql) 查询
- executeUpdate(sql) 更新(增,删,改)
刚刚我们的第一个jdbc程序有很多改进的地方,连接数据库我们每次只需要修改一些参数就可以了,所以我们可以写一个工具类来提高代码复用性。
首先,我们可以把驱动加载,url,username,password写在一个配置文件中,以后我们只需要更改配置文件就可以了。
# 根据个人情况设置
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/learnjdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
password=123456
然后写一个工具类读取文件,提供建立连接和释放资源的功能:
public class JdbcUtils {
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
// 使用这个类时会自动加载静态代码块
static {
try {
// 从classpath中加载db.properties文件,返回一个流
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
// 获取文件中的值
Properties prop = new Properties();
prop.load(in);
driver = prop.getProperty("driver");
url = prop.getProperty("url");
username = prop.getProperty("username");
password = prop.getProperty("password");
// 加载驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
// 建立连接,获取数据库对象
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 释放资源
public static void release(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
然后写一个主类进行操作:
public class JDBCTest02 {
// 为了方便查看效果,可以定义一个打印表的方法
static void printTable(ResultSet resultSet, Statement statement) throws SQLException {
resultSet = statement.executeQuery("select * from students");
System.out.println("学号\t\t" + "姓名\t\t" + "性别\t\t" + "年级\t\t" + "分数\t\t");
while (resultSet.next()) {
System.out.print(resultSet.getObject("id") + "\t\t");
System.out.print(resultSet.getObject("name") + "\t\t");
System.out.print(((resultSet.getInt("gender") == 0) ? "男\t\t" : "女\t\t") );
System.out.print(resultSet.getObject("grade") + "\t\t");
System.out.print(resultSet.getObject("score") + "\t\t");
System.out.println();
}
}
public static void main(String[] args) {
// 需要用到的三个对象
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 利用工具类建立连接
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
// 打印表查看效果
printTable(resultSet, statement);
// 增
int num = statement.executeUpdate("insert into students(`name`,`gender`,`grade`,`score`) values ('王五', 1 , 2, 80)");
if (num > 0) {
System.out.println("插入成功");
}
// 打印表查看效果
printTable(resultSet, statement);
// 删
int num1 = statement.executeUpdate("delete from students where id=4");
if (num1 > 0) {
System.out.println("删除成功");
}
// 打印表查看效果
printTable(resultSet, statement);
// 改
int num2 = statement.executeUpdate("update students set `name`='修改' where id=1");
if (num1 > 0) {
System.out.println("修改成功");
}
// 打印表查看效果
printTable(resultSet, statement);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 最终释放资源
JdbcUtils.release(connection, statement, resultSet);
}
}
}
三、性能更好更安全的PreparedStatement
比statement多了个sql预处理,可以防止sql注入,性能也更好。
还是刚才那张表,直接上代码:
public class JDBCTest04 {
public static void main(String[] args) throws SQLException {
Connection connection;
PreparedStatement preparedStatement;
ResultSet resultSet;
// 利用刚刚写的工具类建立连接
connection = JdbcUtils.getConnection();
// sql预处理,参数用英文 ? 代替
String sql = "update students set `name` = ? where `id` = ?";
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数代表第几个问号,第二个参数代表问号的内容
preparedStatement.setString(1, "我的性能更好");
preparedStatement.setInt(2, 1);
// 执行修改语句
int num = preparedStatement.executeUpdate();
if (num > 0) {
System.out.println("修改成功");
}
// sql预处理,和之前一样,用英文问号占位
sql = "select * from students where id = ?";
preparedStatement = connection.prepareStatement(sql);
// 查询id为1的记录
preparedStatement.setInt(1, 1);
resultSet = preparedStatement.executeQuery();
// 获取此记录的name字段
resultSet.next();
System.out.println(resultSet.getString("name"));
// 释放资源
JdbcUtils.release(connection, preparedStatement, resultSet);
}
}
效果杠杠滴:
四、JDBC操作事务
JDBC连接数据库后,就可以在Java中通过一些方法写sql代码,操作事务也就是在Java中执行操作事务的sql代码。
现在有这么一张表
我们现在用jdbc操作事务来实现转账:
public class JDBCTest05 {
public static void main(String[] args) throws SQLException {
// 建立连接
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
connection = JdbcUtils.getConnection();
// 切换到相应的数据库
preparedStatement = connection.prepareStatement("use test");
preparedStatement.execute();
try {
// 关闭自动提交,会自动开启事务
connection.setAutoCommit(false);
// 进行转账
preparedStatement.executeUpdate("update `account` set `money` = `money` - 500 where `name` = 'A'");
preparedStatement.executeUpdate("update `account` set `money` = `money` + 500 where `name` = 'B'");
// 事务提交
connection.commit();
} catch (Exception e) {
// 如果失败就回滚
connection.rollback();
} finally {
// 释放资源,恢复默认自动提交
connection.setAutoCommit(true);
JdbcUtils.release(connection, preparedStatement, resultSet);
}
}
}
结果:
五、连接池
我们已经学过线程池,因为线程开启关闭开销大,耗资源,所以大佬们使用池化技术搞出了线程池,提前就准备好了一些资源,开启线程时,直接连接准备好的资源。
数据库连接池也是一样的道理。
比较出名的数据库连接池
DBCP、C3P0、Druid。
这里说一下dbcp和c3p0。
首先导入依赖,有maven的直接复制:
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
沿用之前手写工具类的思路,配置文件:
# 登录信息
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306
username=root
password=123456
# 初始化连接
initialSize=10
# 最大连接
maxActive=50
# 最大空闲
maxIdle=20
# 最小空闲
minIdle=5
# 超时等待
maxWait=60000
# 建立连接时附带的属性
connectionProperties=useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
# 自动提交事务开
defaultAutoCommit=true
# 设置隔离级别
defaultTransactionIsolation=READ_UNCOMMITTED
编写工具类:
public class DBCPUtils {
// 创建数据源
private static DataSource dataSource = null;
static {
// 获取数据
InputStream in = JDBCTest03.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
try {
properties.load(in);
// 创建数据源
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 根据数据源的信息获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 释放资源
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
做个查询试试:
public class JDBCTest06 {
public static void main(String[] args) throws SQLException {
// 建立连接
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
connection = DBCPUtils.getConnection();
// 选择数据库
preparedStatement = connection.prepareStatement("use test");
preparedStatement.execute();
// 查询
resultSet = preparedStatement.executeQuery("select * from account");
resultSet.next();
System.out.println(resultSet.getString("name"));
// 释放连接
DBCPUtils.release(resultSet, preparedStatement, connection);
}
}
结果:
成功!
C3P0也是一样的道理
-
导入依赖
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
-
配置文件(可选)
可以配置c3p0-config.xml文件、c3p0.properties文件,xml文件更直观而且可以提供多个数据源服务
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- 默认配置 --> <default-config> <property name="user">root</property> <property name="password">123456</property> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/learnjdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> </default-config> <!-- 命名配置 --> <named-config name="MySQL"> <property name="user">root</property> <property name="password">123456</property> <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> </named-config> </c3p0-config>
-
编写工具类
思路还是一样的,但比DBCP简单多了
public class C3P0_Utils{ private static DataSource dataSource = null; // 读取配置 static { dataSource = new ComboPooledDataSource("MySQL"); } // 连接 public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } // 释放资源 public static void release(ResultSet resultSet, Statement statement, Connection connection) { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
-
测试:
依然是之前的代码,不过这次用c3p0连接
public class JDBCTest06 { public static void main(String[] args) throws SQLException { // 建立连接 Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; connection = C3P0_Utils.getConnection(); // 选择数据库 preparedStatement = connection.prepareStatement("use test"); preparedStatement.execute(); // 查询 resultSet = preparedStatement.executeQuery("select * from account"); resultSet.next(); System.out.println(resultSet.getString("name")); // 释放连接 C3P0_Utils.release(resultSet, preparedStatement, connection); } }
可以看出,仍然查到了A
总之,要连接数据库,本质都是一样的。