JDBC
Java语言连接数据库
JDBC是SUN指定的一套接口(interface)。
JDBC编程六步
第一步:注册驱动
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这是属于进程之间的通信,重量级的,是用完之后需要关闭)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行sql语句
第五步:处理查询结果集(只有当第四步执行的是select语句的时候才会处理查询结果集)
第六步:释放资源(使用完资源之后需要关闭资源,Java和数据库之间属于进程间的通信,开启之后需要关闭)
import java.sql.*;
public class jdbcTester {
public static void main(String[] args) {
try{
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployee");
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch (SQLException e){
e.printStackTrace();
}
}
}
import java.sql.*;
public class jdbcTester {
public static void main(String[] args) throws SQLException {
Connection conn = null;
Statement statement = null;
try{
//1、注册驱动
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);
//2、获取连接
String url = "jdbc:mysql://localhost:3306/myemployee";
String use = "root";
String password = "123";
conn = DriverManager.getConnection(url,use,password);
//3、获取数据库操作对象
statement = conn.createStatement();
//4、执行sql
String sql = "insert into emp (name,salary) values (aa,1000)";
//专门执行DML语句(insert,delete,update),返回值为影响数据库中的记录条数
int count = statement.executeUpdate(sql);
//5、处理查询结果集
}catch (SQLException e){
e.printStackTrace();
}finally {
//6、释放资源
try{
if(conn!=null) {
conn.close();
}
}catch (SQLException e){
e.printStackTrace();
}
try{
if(statement!=null){
statement.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
SQL注入
安全隐患
导致SQL注入的原因
//1、注册驱动
//数据库中的账户密码
String userName = "zfq";
String password = "123123";
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://loaclhost:3306/myemployee","root","123");
//3、获取数据库操作对象
Statement statement = conn.createStatement();
/*
登录时实际输入的账号密码
zfq
zfq' or '1'='1
*/
//4、执行sql
String sql = "select * from t_user where userName = '"+userName+"' and password = '"+password+"'";
ResultSet rs = statement.executeQuery(sql);//此时将sql发送给DBMS(数据库管理系统)进行编译。
//完成sql语句的拼接,正好将用户提供的‘非法信息’编译进去,导致原sql语句的含义被扭曲,进而实现了sql注入
#此时执行的sql语句
select * from t_user where userName = 'zfq' and password = 'zfq' or '1'='1';
输入的密码被当做sql语句的一部分后被执行
用户输入的信息中心含有sql语句的关键字,这些关键字参与了sql语句的编译过程。
解决sql注入
只要用户提供的信息不参与SQL语句的编译过程,就可以解决此问题。
想要用户信息不参与SQL语句的编译过程,就必须使用java.sql.PreparedStatement接口
PreparedStatement接口
继承了Statement接口;
原理:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”;
public interface PreparedStatement extends Statement
PreparedStatement:预编译的数据库操作对象
Statement:数据库操作对象
//数据库中的账户密码
String userName = "zfq";
String password = "123123";
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://loaclhost:3306/myemployee","root","123");
//3、获取预编译的数据库操作对象
//先写sql语句的框架,其中一个?代表一个占位符,每个?接受一个值,不能加单引号。
String sql = "select * from t_user where userName = ? and password = ?";
//程序执行到此,会发送SQL语句框架给DBMS,然后DBMS对SQL语句预先编译。
PreparedStatement ps = conn.prepareStatement(sql);
//给占位符?传值(第一个问号下标为1,第二个问号下标为2……)
ps.setString(1,userName);
ps.setString(2,newPassword);
ResultSet rs = statement.executeQuery();//执行
PreparedStatement与Statement
Statement存在SQL注入的问题,Preparedstatement解决了SQL注入的问题。
Statement编译一次执行一次,PreparedStatement编译一次执行多次,PreparedStatement效率高一些。
PreparedStatement会在编译阶段做类型的安全检查。
PreparedStatement使用较多,Statement只在极少数情况下才会使用。
Statement支持SQL注入,当需要进行SQL语句拼接时就需要使用Statement。
PreparedStatement实现增删改
Connection conn = null;
PreparedStatement ps = null;
try{
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployee","root","123");
//获取预编译的数据库操作对象
//#######insert######
String sql = "insert into emp (name,salary) values (?,?)";
ps = conn.prepareStatement(sql);
ps.setString(1,"sss");
ps.setInt(2,12000);
//#######insert######
//#######update######
String sql = "update emp set salary = ? where name = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,12000);
ps.setString(2,"sss");
//#######update######
//#######delete######
String sql = "delete form emp where name = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"sss");
//#######delete######
//执行sql语句
int count = ps.executeUpdate();
System.ouot.println(count);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(ps != null){
ps.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn != null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
JDBC事务机制
JDBC中的事务默认是自动提交的——只要执行一条DML语句就会自动提交一次。
connection.setAutoCommit(boolean)
验证自动提交
public class JDBC{
public static void main(String[] args){
Connection conn = null;
PrepareStatement ps = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployee","root","123");
String sql = "delete from emp where name = ?";
ps = conn.prepareStatement(sql);
ps.setString("111");
int count = ps.executeUpdate();
System.out.println(count);
}catch(Exception e){
e.printStackTrace();
}finally{
try{
if(ps!=null){
ps.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn!=null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
手动提交
idea批量编辑:alt+shift+insert
//三行代码
conn.setAutoCommit(false);//关闭自动提交
conn.commit();//手动提交
conn.rollback();//抛出异常后需要回滚
#建一个新的表
drop table if exists t_act;
create table t_act(
actno bigint,
balance double(7,2) #7表示有效数字的个数,2表示小数位的个数
);
insert into t_act(actno,balance) values (111,20000);
insert into t_act(actno,balance) values (222,20000);
public class JDBC{
public static void main(String[] args){
Connection conn = null;
PrepareStatement ps = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployee","root","123");
//开启手动提交
conn.setAutoCommit(false);
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
ps.setDouble(1,10000);
ps.setInt(2,222);
count = ps.executeUpdate();
System.out.println(count);
//程序能执行到这里说明没有异常,可以提交
//提交commit事务
conn.commit();
}catch(Exception e){
//如果发生异常则进行回滚
if(conn != null){
conn.rollback();
}
e.printStackTrace();
}finally{
try{
if(ps!=null){
ps.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn!=null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
JDBC工具类的封装
工具类中的构造方法都是私有的。
public class DBUtil{
private DBUtil(){
}
//静态代码块在类加载时执行并且只执行一次
static{
try{
Class.forName("com.mysql.jdbc.Driver");
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection() throws SQLException{
return DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployee","root","123");
}
public static void close(Connection conn,Statement ps,ResultSet rs){
try{
if(rs!=null){
rs.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(ps!=null){
ps.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn!=null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
悲观锁和乐观锁
乐观锁:低并发,认为就算是在并发的情况下,也很少修改数据。
悲观锁:高并发,认为修改数据很频繁,不值得去进行无用的计算。(执行效率慢)
行级锁(悲观锁)
事务必须排队,数据被锁住,不能并发。
for update
#普通
select name from emp where job = 'manager';
#行级锁
select name from emp where job = 'manager' for update;
表示当前事务没有结束的时候,满足 job = ‘manager’ 的每一行的数据都不能改动。
乐观锁
事务不需要排队,支持并发,只不过需要一个版本号。
多线程并发,都可以对数据进行修改。但是每行数据后面有一个版本号。
当一个线程修改了这一行数据之后,版本号进行更新;当另一个线程修改完这行数据后准备提交时发现版本号变了,这个线程就会进行回滚。