一、JDBC
JDBC是一套Java连接数据库的标准。标准就是接口,接口定了标准(规则 == 抽象的方法),接口没有具体的实现。具体的实现由数据库厂商提供的驱动来实现。也就是说,JDBC指定了规范,但是要连接MySQL数据库,得使用的是MySQL数据体提供的驱动程序。又比如,要连接Oracle数据库,得使用Oracle数据库提供的驱动程序来实现。总结:驱动才是对JDBC的具体的实现。
二、JDBC初体验
1.如何引入第三方类库(.jar文件)
2.使用jdbc来连接数据库
// 1.注册数据库的驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// DriverManager.registerDriver(new com.oracle.jdbc.Driver());
// 2.获得连接数据库的连接对象
String url = "jdbc:mysql://localhost:3306/db_190302";// 地址
// http://www.baidu.com
String user = "root";
String password = "123456";
// Connection对象就是一个连接对象
Connection conn = DriverManager.getConnection(url, user, password);
// 3.获得一个能够执行sql语句的Statement对象
Statement stmt = conn.createStatement();
// 4.执行sql语句
String sql = "select * from emp";
// 5.获得查询生成的结果集
ResultSet rs = stmt.executeQuery(sql);
// 6.遍历结果集
while (rs.next()) {
System.out.println(rs.getObject(1) + "," + rs.getObject(2) + "," + rs.getObject(3) + "," + rs.getObject(4)
+ "," + rs.getObject(5) + "," + rs.getObject(6));
}
//7.释放连接
conn.close();
stmt.close();
rs.close();
三、JDBC详解
JDBC是一套规范,这套规范定义在java.sql包下。是规范了java程序连接数据库的步骤(标准):
注册驱动
获得连接对象
获得执行sql语句的对象 Statement
执行sql语句获得结果集对象 ResultSet
操作结果集对象
关闭资源
1.注册驱动
DriverManager.registDriver(new com.mysql.jdbc.Driver)——这种方式驱动会被注册两遍,因为在Driver类中有一个静态代码块(随着类的加载而被执行)中已经将此驱动注册。
因此使用这种方式注册驱动: Class.forName("com.mysql.jdbc.Driver ");——只加载该类,让该类的静态代码块被执行,做注册驱动的效果。
2.获得连接对象
1)getConnection(String url,String user,String password)
(1)url和uri的区别
url: 统一资源定位符 帮助我们定位一个资源,是一个资源的路径。
http://www.baidu.com
ftp://29.38.29.11
jdbc:msyql://29.38.44.11:3306/mydb
uri:
d:/1.jpg
c:/meinv.avi
(2)mysql中的url该怎么写
写法一:
jdbc:mysql://数据库所在主机的ip地址:数据库的端口号/具体的数据库名
写法二:
jdbc:mysql:// 数据库所在主机的ip地址:数据库的端口号/具体的数据库名?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
写法三:
jdbc:mysql:///具体的数据库名
这种方式默认使用 localhost:3306
(3)user
用户名,默认是root
(4)password
密码
2)getConnection(String url)
String url = "jdbc:mysql:///db_190302?user=root&password=123456"
3)getConnection(String url,Properties p)
Properties对象是一个属性(键值对)集对象,可以从静态的配置文件中获得已有的属性集。
Properties p = new Properties();
p.setProperty("user", "root");
p.setProperty("password", "123456");
Connection conn = DriverManager.getConnection(url, p);
升级版:
Properties从静态配置文件中获得配置的好处在于: 如果配置的信息有修改,那么只要修改配置文件即可,而不用修改程序本身。
3.Statement对象
通过Connetion的createStatement()方法来获得。
这是一个执行sql语句的对象。
1) ResultSet executeQuery(String sql)
执行查询: 是用于执行select这一类的sql语句的。
返回一个封装着此次查询产生结果的对象。
2) executeUpdate(String sql)
执行:增删改的sql语句, insert delete update
3)boolean execute(String sql)
可以执行select也可以执行DML(增删改)或DDL
如果执行的select的sql语句,返回的是true
如果执行的是DML或DDL,返回的是false
根据返回的布尔值,调用不同的方法来获得结果,如果是true,调用getResultSet来获得查询得到的结果集,如果是false,调用getUpdateCount()获得受影响的行数
4.JDBC工具类的设计
public class MyJDBCUtil {
static String url;
static String user;
static String password;
//复用性!
static {
//随着类的加载就注册驱动
Properties p = new Properties();
try {
//1.获得静态配置文件中的信息
p.load(new FileInputStream("db.properties"));
String driverClass = p.getProperty("driverClass");
//2.给参数赋值
url = p.getProperty("url");
user = p.getProperty("user");
password = p.getProperty("password");
//3.注册驱动
Class.forName(driverClass);
} catch (ClassNotFoundException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 释放资源的方法
* @param conn 连接对象
* @param stmt 执行sql语句的对象
* @param rs 结果集对象
* @throws SQLException
*/
public static void releaseResource(Connection conn,Statement stmt,ResultSet rs) throws SQLException {
if(rs!=null) {
rs.close();
}
if(stmt!=null) {
stmt.close();
}
if(conn!=null) {
conn.close();
}
}
/**
* 获得连接对象的方法
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, password);
}
/**
* 执行任意类型的sql语句的方法
* @param sql 增删改查都可以
* @throws IOException
* @throws FileNotFoundException
*/
public static void showResult(String sql) throws Exception {
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
//2.执行sql语句 获得结果
boolean b = stmt.execute(sql);
ResultSet rs = null;
if(b) {
//sql是select
//就会有结果集
rs = stmt.getResultSet();
//
System.out.println("执行select语句,结果集遍历如下:");
while(rs.next()) {
System.out.println(rs.getObject(1)+","+rs.getObject(2));
}
}else {
//sql是增删改或ddl
int rows = stmt.getUpdateCount();
System.out.println("执行DML或DDL语句,受影响的行数为"+rows);
}
//3.释放资源
if(rs!=null) {
rs.close();//如果rs是null,直接调用close方法会报空指针异常
}
stmt.close();
conn.close();
}
}
5.ResultSet对象
当执行select的查询语句时将会获得结果集,结果集被封装在ResultSet对象中
1) next()
遍历ResultSet中的数据的循环条件,刚开始位于第一行之前,第一次调用时就来到了第一行,以此类推
2)如何获得每一行中每一列的数据
(getInt getString getDate getDouble getXXX ...)
||
getObject
这些方法可以传两种参数
int columnindex ==> 传列的索引,索引从1开始
String columnlable ==> 传列名
while(rs.next()) {
// 部门编号(int) 部门名称(String) 部门地址(String)
//目前处于第一行
/*int deptno = rs.getInt(1);
String dname = rs.getString("dname");
String loc = rs.getString(3);
System.out.println(deptno+"\t"+dname+"\t"+loc);*/
Object deptno = rs.getObject(1);
Object dname = rs.getObject(2);
Object loc = rs.getObject(3);
System.out.println(deptno+"\t"+dname+"\t"+loc);
}
四、SQL注入问题
案例:
模拟登陆验证
步骤:
一、向数据库中存入用户名和密码
二、获得用户输入的用户名和密码
三、去数据库验证此用户名和密码是否正确
1)从工具类中获得数据库的连接对象。
2)获得执行sql语句的Statement对象
3)执行sql获得返回结果
sql怎么写? 如果输入的用户名和密码与数据库中某一条记录的用户名和密码完全相同。则输入正确;否则输入错误。
4)判断rs.next()的结果,如果是true 就表示登录成功,否则登录失败。
1.SQL注入问题的出现
我们发现当输入这样的数据时,参数会被作为sql语句本身来执行,而非是纯粹的参数,因此这种情况非常危险,这种情况也称为sql注入问题。
2.解决SQL注入问题
通过PreparedStatement对象来解决。在创建PreparedStatement对象时会先把sql语句预编译进体内。
然后再为sql语句中占位符“?”赋值。
那么此时,为?赋值的参数内容,只能作为参数本身,而不会影响到sql语句。这样就防止了sql注入问题。
注意: 设置完pstmt对象的参数后,还要执行。
五、事务
案例:
小陈和小林
小陈问小林借钱,去银行转账。 ATM转账 小林-1000 ===||||===> 小陈+1000
如果在转出 和转入 两条sql操作中出现了问题,那么就会导致转出成功,但转入失败。这是不应该发生的。
因此通过事务来解决这样的问题。
1.什么是事务
事务是sql中最小的操作单位。默认情况下,一条sql语句是一个单独的事务。当一个事务被执行,就会立即生效。因此我们可以让多条sql语句成为一个单独的事务,当事务被执行,多条sql语句同时生效,也可以多条sql语句同时不生效。
2.事务的操作——如何让多条sql语句成为一个单独的事务
1)开启事务
在MySQL中: start transaction;
2)结束事务
(1)提交 commit; 让开启事务到提交事务之间的所有sql语句都生效
(2)回滚 rollback; 让开启事务到回滚事务之间的所有sql语句都不生效。让事务回滚到开启事务的那一刻。(也可以设置回滚点,让事务回滚到指定回滚点。)
3.在jdbc中操作事务
conn.setAutoCommit(false) ==>开启事务
结束事务:
- 提交事务: conn.commit()
- 回滚事务: conn.rollback();
/**
* 实现转账的方法
* @param out_account 转出账号
* @param in_account 转入账号
* @param amount 转账金额
* @return 是否成功
* @throws Exception
*/
public static boolean transfer(String out_account, String in_account, double amount) throws Exception {
//获得连接对象
Connection conn = MyJDBCUtil.getConnection();
//开启事务
conn.setAutoCommit(false);
try {
//转出
String sql = "update tb_account set amount=amount-? where name=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setDouble(1,amount);
pstmt.setString(2, out_account);
pstmt.executeUpdate();
int i=10/0;
//转入
String sql1 = "update tb_account set amount=amount+? where name=?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setDouble(1, amount);
pstmt1.setString(2, in_account);
pstmt1.executeUpdate();
conn.commit();//提交事务
} catch (Exception e) {
conn.rollback();//只要出现异常。就回滚!
return false;//转账失败
}
return true;//转账成功
}
扩展:
/**
* 实现转账的方法,无论什么情况转账都会成功!
* @param out_account 转出账号
* @param in_account 转入账号
* @param amount 转账金额
* @return 是否成功
* @throws Exception
*/
public static boolean transferPro(String out_account, String in_account, double amount) throws Exception {
//获得连接对象
Connection conn = MyJDBCUtil.getConnection();
//开启事务
conn.setAutoCommit(false);
Savepoint sp = null;
try {
//转出
String sql = "update tb_account set amount=amount-? where name=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setDouble(1,amount);
pstmt.setString(2, out_account);
pstmt.executeUpdate();
//=========设置了存档点===============
sp = conn.setSavepoint();
int i=10/0;
//转入
String sql1 = "update tb_account set amount=amount+? where name=?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setDouble(1, amount);
pstmt1.setString(2, in_account);
pstmt1.executeUpdate();
conn.commit();//提交事务
} catch (Exception e) {
//conn.rollback();//只要出现异常。就回滚!
conn.rollback(sp);
//继续做转账
//转入
String sql1 = "update tb_account set amount=amount+? where name=?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setDouble(1, amount);
pstmt1.setString(2, in_account);
pstmt1.executeUpdate();
conn.commit();//提交事务 把存档点之前的sql提交,同时提交此处的sql
return true;//转账成功
}
return true;//转账成功
}
六、事务的特点 ACID
1.原子性
事务是一个最小的操作单元,不可再被分割,也就是说事务中的sql语句要么同时生效,要么同时不生效。
2.一致性
事务中的数据在事务提交之前,或者回滚以后,是一致的。
3. 持久性
事务一旦提交,对数据的影响是持久的(写入到磁盘中)。
4.隔离性
一个事务,在并发访问的情况下,不同隔离级别会出现不同效果。
七、事务的隔离级别
一个事务,在并发访问的情况下,不同隔离级别会出现不同效果。
并发: 在同一时刻,有多个客户顿在操作同一张表。
1.read uncommitted
一个事务中读到了另一个事务并未提交的结果。这种隔离级别就会导致: 脏读、不可重复读、虚读(幻读)。
2.read committed——oracle的默认隔离级别
一个事务中能读到另一个事务已经提交的结果,但是不能读到另一个事务没有提交的结果。这种隔离级别解决了脏读问题。但出现了不可重复读和幻读的问题,
所谓不可重复读,就是不能重复读,一重复读数据就不一样。
所谓的幻读,在一个事务中,两次读到的数据的条数不相同。
3.repeatable read——mysql的默认隔离级别
一个事务中可以重复的读,每次读到的数据都是一样的,无论其他事务对数据进行怎样的操作(添加数据、修改数据、提交事务)。当前事务每次读到的数据内容都不会有变化。
这种隔离级别解决了: 脏读、不可重复读、幻读。
4.serializable 串行化
最严苛的隔离级别。将并行变成串行。效率非常低,没有使用场景。