JDBC
jdbc本质就是一套接口,程序员不需要关心数据库的具体品牌,只需要面向jdbc接口写代码。
驱动
所有的数据库驱动都是以jar包的形式存在,jar包中有许多.class文件(驱动就是实现jdbc接口的实现类)。
JDBC编程
- 注册驱动(告诉java程序,即将连接的是那个品牌的数据库)
- 获取连接(表示jvm的进程和数据库进程之间的通道打开了。使用完要关闭)
- 获取数据库操作对象(创建执行数据库的对象)
- 执行sql语句(DQL,DML。。。)
- 处理查询结果集
- 释放资源(使用完资源之后一定要关闭,java和数据库属于进程间的通信,开启之后一定要关闭)
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCTest{
public static void main(String [] args){
Connection conn=null;
Statement stmt=null;
try{
//注册驱动
Driver driver=new com.mysql.jdbc.Driver();//父类型引用指向子类对象
DriverManager.registerDriver(driver);
//注册驱动的第二中方法;因为参数是一个字符串,字符串可以写到.properties文件中。不需要接受返回值是因为我们只想用它的类加载动作。
//Class.forName("com.mysql.jdbc.Driver");
//获取连接
String url="jdbc:mysql://192.168.151.80:3306/userDb";
String user="root";
String password="root";
conn=DriverManager.getConnection(url,user,password);
System.out.println("数据连接对象"+conn);
//获取数据库操作对象(Statement专门执行SQl语句的)
stmt=conn.createStatement();
//执行sql
String sql="";
}
catch(SQLException e){
e.printStackTrace();
}finally{
//释放资源
try{
if(stmt!=null){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(conn!=null){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
//实际开发中会将连接数据库的信息写在配置文件中,不会直接写在java程序代码中
//使用资源绑定器绑定属性配置文件
ResourceBundle bundle=ResourceBundle.getBundle("jdbc")
String driver=bundle.getString("driver");
String url=bundle.getString("url");
String user=bundle.getString("user");
String password=bundle.getString("password");
//配置文件jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.151.80:3306/userDb
user=root
password=root
SQL注入
原因:用户输入的信息中含有sql语句的关键词,并且这些关键词参与sql语句的编译过程,导致sql语句的原来意思被曲解进而达到sql注入。
解决SQL注入:只要用户提供的信息不参与sql语句的执行就可以了,即使用户提供的信息包括sql关键词但没有参与到编译就不起作用了。
要想用户提供的信息不参与编译就要使用到java.sql.PreparedStatement;
PreparedStatement接口继承了Statement;PreparedStatement属于预编译数据库操作对象,在用户提供信息之前将sql语句进行了预编译。即使用户提供的信息含有sql关键字但不参与sql编译所以不起作用。
PreparedStatement ps=null;
//在获取数据库操作对象是使用PreparedStantement
//sql语句,问号代表一个占位符用于接收用户输入的值
String sql="select * from 表名 where loginName=? and loginPwd=?";
//程序执行到此处会将sql语句进行预编译,使用的是PrepareStatement方法
ps.conn.PrepareStatement(sql);
//给占位符赋值,数字1代表第一个问号;因为jdbc下标是从1开始的。
ps.setString(1,loginName);
ps.setString(2,loginPwd);
Statement和PreparStatement对比
- Statement存在sql注入问题,PreparStatement不存在sql注入问题;
- Statement是编译一次执行一次,PreparStatement是编译一次可以执行N次,效率较高;
- PreparStatement在编译阶段会进行类型的安全检查;
并不是在以后不使用Statement而是在需要进行sql注入时使用Statement
jdbc事务
jdbc的事务是自动提交的,只要执行一条sql语句就会自动提交一次;
解决jdbc事务自动提交:
//在获取连接后关闭事务自动提交,开启事务
conn.setAutoCommit(false);
//执行完sql语句后进行提交,事务的提交
conn.commit;
//如果有错进行事务回滚
conn.rollback();
封装jdbc工具类
public class DButil{
private DButil(){}
//静态代码块在类加载时执行,并且只执行一次
static{
try{
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
}catch(Exception e){
e.printStackTrace();
}
}
//获取数据库连接
public static Connection getConnection() throws SQLexception{
return DriverManager.getConnection("jdbc:mysqk://localhost:3306/userDb","root","root");
}
//释放资源
public static void close(Connection conn,Statement ps,ResultSet rs){
//结果集
if(rs!=null){
try{
rs.close();
}catch(SQLexception e){
e.printStackTrace();
}
}
//数据库操作对象
if(ps!=null){
try{
ps.close();
}catch(SQLexception e){
e.printStackTrace();
}
}
//数据库连接对象
if(conn!=null){
try{
conn.close();
}catch(SQLexception e){
e.printStackTrace();
}
}
}
}
悲观锁/行级锁:在sql语句后加上for update;将查询的语句进行加锁其它无法进行更改
乐观锁:指线程1访问后会在数据一个版本号,线程2访问后并进行更改会将版本号更改,当线程1再次访问时数据以被更改太它会回滚到以前的版本;