Java基础笔记(JDBC)

JDBC(Java DataBase Connectivity),它是java程序访问数据库的标准 接口
Java访问数据库并不是直接通过TCP连接访问数据库,而是使用JDBC接口访问。而JDBC接口通过JDBC驱动实现真正的数据库访问。JDBC的接口是由JDK
定义并内置在JDK中,而具体的数据库驱动是由厂商提供的,实际上JDBC的驱动就是一个jar包。
由此可看出使用JDBC的好处是:

  • 各数据库厂商使用相同的接口,Java代码不需要针对不同的数据库进行开发
  • java程序编译期只需要引入 java.sql.*,不依赖具体的数据库jar包
  • 可随时替换底层数据库,只需更换jar包,而访问数据库的代码不变

1 JDBC获取数据库链接

获取数据库链接时,需要三个基本的字符串,分别是:

  • JDBC的URL,它是由数据库厂商定义的,例如MySQL的URL是 jdbc:mysql:// 加数据库所在服务器的地址及端口号
  • 数据库的用户名及密码

然后就可以使用 DriverManganer.getConnection(String url, String userName, String password); 获取数据库链接了。

// JDBC链接的URL
String JDBC_URL = "jdbc:mysql://localhost:3306/test"; // test表示访问数据库的名字
String JDBC_USER = "root";
String JDBC_PASSWORD = "password";
Connection conn = DriverManager.getConnection(JDBC_URL,JDBC_USER,JDBC_PASSWORD);
// 访问数据库。。
// 关闭数据库
conn.close();

2 JDBC执行查询语句

JDBC执行查询流程:

  • 创建数据库链接
  • 创建查询语句对象 Statement(不推荐)或 PrepareStatement 对象,并执行其 executeQuery 方法
  • 查询的结果是 ResultSet,通过 while 循环它,调用形如 getString('name') 形式的方法获取相应的数值

eg:

// 获取数据库链接
public static Connection getConnection() throws SQLException {
	Connection connection = DriverManager.getConnection(JDBCurl, userName, pw);
	return connection;
}

// 获取全部学生列表
public static List<Student> getAllStudents() throws SQLException {
	// 获取连接
	try (Connection connection = getConnection()) {
		// 创建 prepareStatement 对象
		try (PreparedStatement preparedStatement = connection.prepareStatement("select * from students")) {
			// 执行语句,获取查询结果二维表
			try (ResultSet resultSet = preparedStatement.executeQuery()) {
				List<Student> students = new ArrayList<>();
				// 循环 resultSet,获取记录,使用类似 JSONfast 的形式获取具体信息
				while (resultSet.next()) {
					Long id = resultSet.getLong("id");
					Long class_id = resultSet.getLong("class_id");
					String name = resultSet.getString("name");
					Student student = new Student(name, class_id, id);
					students.add(student);
				}
				return students;
			}
		}
	}
}

// 根据班级获取学生列表
public static List<Student> getStudentsByClass(String classId) throws SQLException {
	// 获取连接
	try (Connection connection = getConnection()) {
		// 创建 prepareStatement 对象
		try (PreparedStatement preparedStatement = connection
				.prepareStatement("select * from students where class_id = ?")) {
			// preparedStatement 对象的替代索引从 1 开始
			preparedStatement.setObject(1, classId);
			// 执行语句,获取查询结果二维表
			try (ResultSet resultSet = preparedStatement.executeQuery()) {
				List<Student> students = new ArrayList<>();
				// 循环 resultSet,获取记录,使用类似 JSONfast 的形式获取具体信息
				while (resultSet.next()) {
					Long id = resultSet.getLong("id");
					Long class_id = resultSet.getLong("class_id");
					String name = resultSet.getString("name");
					Student student = new Student(name, class_id, id);
					students.add(student);
				}
				return students;
			}
		}
	}
}

// 获取学生总数
public static long getStudentsCount() throws SQLException {
	// 获取连接
	try (Connection connection = getConnection()) {
		// 创建 prepareStatement 对象
		try (PreparedStatement preparedStatement = connection
				.prepareStatement("select count(*) count from students")) {
			// 执行语句,获取查询结果二维表
			try (ResultSet resultSet = preparedStatement.executeQuery()) {
				Long count = (long) 0;
				// 循环 resultSet,获取记录,使用类似 JSONfast 的形式获取具体信息
				while (resultSet.next()) {
					count = resultSet.getLong("count");
				}
				return count;
			}
		}
	}
}

3 JDBC执行更新、插入、删除语句

JDBC执行更新、插入、删除语句与执行查询语句的流程一致,不同在于 preparedStatement 对象的执行是调用 executeUpdate 方法,其返回结果是更新、插入、删除数据的个数。
当执行插入语句时,往往需要获取自增主键的数值,此时就需要在创建 preparedStatement 对象的时候再传入一个常量 Statement.RETURN_GENERATED_KEYS,表示需要返回数据库生成的主键。
eg:

// 更新id=1的学生的name
public static int updateStudent() throws SQLException {
	try (Connection connection = getConnection()) {
		try (PreparedStatement preparedStatement = connection
				.prepareStatement("update students set name = 'Seiei' where id = 1")) {
			int num = preparedStatement.executeUpdate();
			return num;
		}
	}
}

// 增加Student
public static int insertStudent() throws SQLException {
	Student newStudent = new Student("Taka", (long) 4, null);
	try (Connection connection = getConnection()) {
		// 创建preparedStatment 对象时,添加 Statement.RETURN_GENERATED_KEYS 常量
		try (PreparedStatement preparedStatement = connection.prepareStatement(
				"insert students (name, class_id) values (?, ?)", Statement.RETURN_GENERATED_KEYS)) {
			preparedStatement.setObject(1, newStudent.name);
			preparedStatement.setObject(2, newStudent.class_id);
			// 执行语句
			int num = preparedStatement.executeUpdate();
			// 获取自增主键
			try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {
				while (resultSet.next()) {
					// 使用索引的形式获取返回的自增主键
					System.out.println("返回的自增主键是:" + resultSet.getLong(1));
				}
			}
			return num;
		}
	}
}

// 删除Student
public static int deleteStudent() throws SQLException {
	try (Connection connection = getConnection()) {
		// 创建preparedStatment 对象时,添加 Statement.RETURN_GENERATED_KEYS 常量
		try (PreparedStatement preparedStatement = connection
				.prepareStatement("delete from students where id = 5")) {
			// 执行语句
			int num = preparedStatement.executeUpdate();
			return num;
		}
	}
}

4 数据库事务

参考文章《spring的事务传播行为及事务隔离级别》《spring的@Transactional注解详细用法》

假如要模拟一个转账的情况,A转账100块给B,化作成数据库操作就是A的存款减少100,B的存款增加100,一共两个SQL操作语句,假如A存款SQL操作执行成功,而B的存款增加SQL操作没有执行或执行不成功,此时应用程序的逻辑出现了不一致的情况,此时就需要使用数据库事务了。

数据库事务是指:若干SQL语句构成的一个操作序列,使用数据库事务可以保证多个SQL语句全部执行或全部不执行。
数据库事务具有ACID特性,即:

  • Atomicity:原子性,指数据库事务虽然本身是由多个SQL语句构成,但它是一个原子操作,执行要么全不成功,要么全不失败
  • Consistency:一致性,指一个事务在开始前和结束后,数据库数据是完整的,不存在冲突和数据不一致
  • Isolation:隔离性,指多个事务并发执行的时候,事务之间是隔离的,一个事务不应该影响其他事务
  • Durability:持久性,指事务一旦执行成功,这个事务对数据库所作的更改会持久的保存在数据库中,并且不会被回滚

4.1 数据的隔离级别

事务的隔离级别它是由数据库允许多个连接同时执行事务引起的,即比如在Java程序中,当一个线程操作数据库连接的时候,另一个线程可能也在使用一个数据库链接更新数据,它们之间就可能遇到隔离级别的问题。

脏读:一个事务读取了被另一个事务改写但还未提交的数据时。如果这些数据被回滚,那么之前的事务读取的到数据就是无效的。

如下图所示,事务A执行了 update 语句,但还没有提交事务,而事务B读取了该没有提交的更新数据,而此时如果事务A回滚了,那事务B读取的数据就是 脏数据

脏读

非重复读:在同一事务中,多次读取同一数据返回的结果有所不同(读到另一个事务提交的更新的数据)。

如下图所示,事务B在两条 select 语句中返回的结果不一致,这是因为在两条语句执行之间,事务A将数据给 update 了。

在这里插入图片描述

幻读:一个事务读取几行记录后,另一个事务插入了一些记录(也可以删除),幻读就发生了。在后来的查询中第一个事务就会发现有些原来没有的记录。

如下图所示,事务B读取数据两次都是返回空数据,而使用 update 数据的时候却返回修改成功,再次执行 select 语句的时候返回数值。

在这里插入图片描述

为了避免这些问题,事务定义四种管理级别,分别是:

  • 读未提交READ_UNCOMMITED):允许读取还未提交的改变了的数据。可能导致脏读、幻读、不可重复读。

  • 读已提交READ_COMMITED):允许在并发事务已经提交后读取。可防止脏读,但幻读、不可重复读仍可能发生。

  • 可重复读REPEATABLE_READ):对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏读、不可重复读。但幻读仍可能发生。

  • 可串行化SERIALIZABLE):完全服从ACID的隔离级别,确保不发生脏读、幻读和不可重复读。他在所有的隔离级别中是最慢的,毕竟要完全锁住在事务中涉及的数据表。

  • Default:使用了后端数据库默认的隔离级别(spring中的选择项,也是 isolation 属性的默认值,Mysql默认采用 REPEATABLE_READ 隔离级别,Oracle默认采用 READ_COMMITED 隔离级别)。

级别类型脏读非重复读幻读
Read Uncommitted(读未提交)会出现会出现会出现
Read Committed(读已提交)会出现会出现
Repeatable Read(可重复读)会出现
Serializable(可串行化)

管理级别越高,数据库执行时所加的锁就越多,能够同时并发执行的事务就越少。

4.2 创建JDBC事务

创建JDBC事务:

  • Connection 对象中调用 setTranscationTsolation 方法传入事务级别
  • Connection 对象调用 setAutoCommit(false) 表示打开事务
  • 执行完SQL语句,执行 commit 方法提交事务
  • 出错时,调用 rollback 方法回滚事务
  • 最后事务提交后,调用 setAutoCommit(true)close 关闭事务

eg:

// 更新
static void updateName(Connection connection, String name, long id) throws SQLException {
	try (PreparedStatement preparedStatement = connection
			.prepareStatement("update students set name = ? where id = ?")) {
		preparedStatement.setObject(1, name);
		preparedStatement.setObject(2, id);
		preparedStatement.executeUpdate();
	}
}

public static void main(String[] args) throws SQLException {
	Connection connection = null;
	try {
		// 获取连接
		connection = getConnection();
		// 设置事务级别
		connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
		// 开启事务
		connection.setAutoCommit(false);
		// 执行sql
		updateName(connection, "curry", (long) 3);
		updateName(connection, "james", (long) 4);
		// 提交事务
		connection.commit();
	} catch (Exception e) {
		// 事务回滚
		e.printStackTrace();
		connection.rollback();
	} finally {
		if (connection != null) {
			// 关闭事务
			try {
				connection.setAutoCommit(true);
				connection.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

5 链接池

线程池可以服用一个线程,这样就能避免反复创建线程的开销,提高运行效率,同样地使用JDBC链接池可以复用JDBC连接 Connection,避免反复创建新连接,提高运行效率。
JDBC连接池它的接口定义在 javax.sql.DataSource 这个接口中,JDK只提供连接池的定义,具体实现需要引入一些开源包。

在连接池获取到 Connection 对象后,也是要使用 try (resource) {} 这样的方式去操作 Connection 对象,此时操作完关闭 Connection 对象时,并没有真正的关闭 Connection 对象,而是将其释放到连接池中。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值