案例:
实现键盘输入数据与数据库中数据校验,如果一致,输出登录成功,否则登录失败
public class JDBCDemo02 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String username1 = scanner.next();
System.out.println("请输入密码");
String password1 = scanner.next();
ResourceBundle jdbc = ResourceBundle.getBundle("com/jy/demo/jdbc");
String driver = jdbc.getString("driver");
String url = jdbc.getString("url");
String user = jdbc.getString("user");
String password = jdbc.getString("password");
try {
Class.forName(driver);
connection = DriverManager.getConnection(url,user,password);
statement = connection.createStatement();
//字符串拼接,使得能够查找与键盘输入一致的数据
resultSet = statement.executeQuery("select * from test02 " +
"where username = '"+username1+"'and password = '"+password1+"'");
//resultSet != null判断的是resultSet容器是否存在
//resultSet.next()判断的是容器中是否有元素
if(resultSet != null && resultSet.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
resultSet.close();
statement.close();
connection.close();
}
}
}
案例中的if判断:
resultSet != null判断的是resultSet容器是否存在
resultSet.next()判断的是容器中是否有元素
注意:数据库中的每一张表,都与Java中的一个实体对应,table中的字段对应实体beans中的属性
通过jdbc读取数据,这些数据是以对象的形式被封装在结果集容器中,即resultSet
我们会将读取的数据封装成对象,然后将对象装进集合中(ArrayList/HashMap)
例如上面案例用此方法更合理:
public class JDBCDemo03 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
boolean flag = false;
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String u = scanner.next();
System.out.println("请输入密码");
String p = scanner.next();
ResourceBundle jdbc = ResourceBundle.getBundle("com/jy/demo/jdbc");
String driver = jdbc.getString("driver");
String url = jdbc.getString("url");
String user = jdbc.getString("user");
String password = jdbc.getString("password");
try {
Class.forName(driver);
connection = DriverManager.getConnection(url,user,password);
statement = connection.createStatement();
//字符串拼接,使得能够查找与键盘输入一致的数据
resultSet = statement.executeQuery("select * from test02");
List<User> userList = new ArrayList<>();
if(resultSet != null ){
while (resultSet.next()){
String un = resultSet.getString("username");
String pw = resultSet.getString("password");
User user1 = new User();
user1.setUsername(un);
user1.setPassword(pw);
userList.add(user1);
}
}
for (User user1 : userList) {
if(user1.getUsername().equals(u) && user1.getPassword().equals(p)){
flag = true;
System.out.println("登录成功");
}
}
} catch (Exception e) {
e.printStackTrace();
}
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
事务
数据库的事务是指一组sql语句组成的数据库逻辑处理单元,在这组的sql操作中,要么全部执行成功,要么全部执行失败。
在jdbc中只要执行了任意一条DML(增删改)语句,都会有一次事务的提交。
事务的原子性: 原子性是指事务的原子性操作,对数据的修改要么全部执行成功,要么全部失败,实现事务的原子性,是基于日志的Redo/Undo机制。
事务的一致性:一致性是指执行事务前后的状态要一致,可以理解为数据一致性。
事务的隔离性:隔离性侧重指事务之间相互隔离,不受影响,这个与事务设置的隔离级别有密切的关系。
事务的持久性:持久性则是指在一个事务提交后,这个事务的状态会被持久化到数据库中,也就是事务提交,对数据的新增、更新将会持久化到书库中。
事务的隔离级别:
Mysql中事务的隔离级别分为四大等级,读未提交(READ UNCOMMITTED)、读提交 (READ COMMITTED)、可重复读 (REPEATABLE READ)、串行化 (SERIALIZABLE)。
事务的一致性
如果出现异常,那么异常之前的语句会被执行,异常之后的不会,这就导致事务的不一致。
例如:A给B转账,那么A的余额会减少,B得余额会增加,可如果A部分语句执行后出现异常,B部分就不会被执行,导致A的余额减少,B的余额没变,这就是事务的不一致。
jdbc默认事务是关闭状态。
所以我们需要在获取连接后开启事务,使得connection.setAutoCommit(true); 默认为true,为true时它会将所有DML语句自动提交,所以需要关闭自动提交,开启手动提交(值设置为false)。手动提交的代码写在程序最后面(try语句块的最后面):调用connection.commit()
当出现异常,我们需要进行回滚,写在catch语句块里:
if(connection != null){
//回滚让事务回到执行前的状态
connection.rollback();
}
悲观锁与乐观锁:
乐观锁:
假设在多线程场景下,多个线程同时对一个数据库的表进行操作,如果多个线程去修改同一条数据,乐观锁的思想是对这种数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现)。
乐观锁可以不用开启事务的支持(采用自动提交的策略)
特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。
悲观锁:
悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。
特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高。
悲观锁一定要开始事务的支持(采用手动提交)
例如行级锁:
行级锁(又叫排它锁)
使用方式:查询sql语句后加上for update
加上行级锁后,被当前线程查询出的结果集,其他线程无法对结果集进行操作,直到当前事务提交。设置行级锁的同时需要开启事务。
在数据库中执行select … for update ,对数据库中的表或某些行数据进行锁表,在mysql中,如果查询条件带有主键,会锁行数据,如果没有,会锁表。
jdbc工具类
封装jdbc工具类的目的是避免重复,如jdbc的六步每次都要写,这是重复的代码。
1、其构造方法被私有化,不能实例化对象,可以直接调用里面的方法。
2、里面的方法为类级别(带static关键字),直接用类名调用。
工具类中的获取数据库连接的方法的异常选择抛出,不在工具类中处理异常。关闭资源方法的异常则可以选择抛出或者捕获。
/*
* 实现JDBC的工具类
*/
public class JDBCUtils {
private JDBCUtils(){};
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test?" +
"serverTimezone=GMT&characterEncoding=utf-8","root","root");
}
public static void getClose(ResultSet resultSet, Statement statement, Connection connection){
if(resultSet != null ){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(statement != null){
try{
statement.close();
}catch(SQLException ex){}
}
if(connection != null){
try{
connection.close();
}catch(SQLException ex){}
}
}
}