1、JDBC本质
- JDBC:Java DataBase Connectivity(Java语言连接数据库)
- JDBC的本质:JDBC是SUN公司制定的一套接口(interface)
java.sql.*; (这个软件包下有很多接口)
面向接口编程: 解耦合:降低程序的耦合度,提高程序的扩展力。 多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
- 思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样,Oracle数据库有自己的原理,MySQL数据库也有自己的原理,MS SqlServer数据库也有自己的原理,每一个数据库产品都有自己独特的实现原理
2、JDBC开发前的准备工作
先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中
classpath=.;D:\course\06-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
以上的配置是针对于文本编辑器的方式开发;使用IDEA工具的时候,不需要配置以上的环境变量,IDEA有自己的配置方式
3、JDBC编程六步
第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行SQL语句(DQL DML....)
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集)
第六步:释放资源(使用完资源之后一定要关闭资源,Java和数据库属于进程间的通信,开启之后一定要关闭)
4、JDBC代码实现
public class JdbcTest04 {
public static void main(String[] args) {
Connection connection = null;
Statement statement =null;
try {
//1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.测试连接
/*
url:协议 jdbc:mysql://
IP地址 localhost(127.0.0.1)
端口号 3306
数据库名称 employeemanagementsystem
*/
String url = "jdbc:mysql://localhost:3306/employeemanagementsystem";
String user = "root";
String password= "123";
connection = DriverManager.getConnection(url,user,password);
//3.创建数据库对象
statement = connection.createStatement();
//4.执行sql语句,executeUpdate方法返回值是受影响行数,用于增删改等操作
String sql ="insert into department(DEPARTMEN_ID,DEPARTMENT_NAME,DEPARTMEMT_DESCRIBE) values (7,'哈哈部','有关哈哈的部门')";
int count = statement.executeUpdate(sql);
//5.处理查询结果集(当为select语句时需要)
} catch (SQLException e) {
e.printStackTrace();
}finally {
//6.关闭资源,要遵循从小到大关闭
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
4.1 jdbc注册驱动第二种写法:
try {
//1.注册驱动
//常用,参数是一个字符串,字符串可以写到.properties文件中
Class.forName("com.mysql.jdbc.Driver");
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
4.2 从属性资源文件中读取连接数据库信息
4.2.1 .java文件部分内容
//通过绑定资源配置文件(jdbc.properties),来获取连接数据库所需的信息
ResourceBundle resourceBundle = ResourceBundle.getBundle("jdbc");
String driver = resourceBundle.getString("driver");
String url = resourceBundle.getString("url");
String user = resourceBundle.getString("user");
String password = resourceBundle.getString("password");
try {
Class.forName(driver);
Connection connection = DriverManager.getConnection(url,user,password);
Statement statement = connection.createStatement();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
4.2.2 .properties文件内容
# jdbc.properties文件内容:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/employeemanagementsystem
user=root
password=123
4.3 jdbc查询代码实现
Class.forName("com.mysql.jdbc.Driver");
Connection connection = null;
connection = DriverManager.getConnection(url,user,password);
Statement statement = connection.createStatement();
String sql = "select * from table_name";
//查询语句使用executeQuery方法
ResultSet rs = statement.executeQuery(sql);
//rs.next(),当结果为true,则表示有内容;getString的特点是:
//无论数据库中的数据类型是啥,都以String类型取出;jdbc所有下标都以1开始
while(rs.next()){
system.out.println(rs.getString("columns")); //也可以使用rs.getString(1),1代表的是第几列;
//存在的问题会混乱列(数据库中的列可能会交换顺序),不建议;columns不是表中的列名称(表中列名称可能会起别名),
//是查询结果集的列名称
}
5、idea配置驱动
1、在project或module中右键,Open Module Setting
2、libraries,点击+号,选中Java
3、找到jar包的位置
4、选择jar包所要添加的module
6、SQL注入
6.1 通过一个登录操作演示SQL注入
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Login {
public static Map<String,String> init(){
Map<String,String> map = new HashMap<>();
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名");
String user = input.nextLine();
System.out.println("请输入密码");
String password = input.nextLine();
map.put("user",user);
map.put("password",password);
return map;
}
public static boolean yanzheng(Map<String,String> loginxinxi){
Connection connection =null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/employeemanagementsystem";
String user ="root";
String password = "123";
String wUser = loginxinxi.get("user");
String wPassword = loginxinxi.get("password");
connection = DriverManager.getConnection(url,user,password);
statement = connection.createStatement();
String sql ="select * from login where LoginName='" +wUser +" ' and Password='"+wPassword+"'";
resultSet = statement.executeQuery(sql);
if (resultSet.next()){
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return false;
}
public static void main(String[] args) {
Map<String,String> loginxinxi = init();
boolean s = yanzheng(loginxinxi);
System.out.println(s ? "登陆成功" : "登陆失败");
}
}
- 当我们随便写一个用户名,密码随便写在后面加上** ' or '1' = '1**;加上这个后缀之后,随便一个用户名和密码都可以登录成功,原因是SQL语句被篡改成:
select * from login where LoginName=’sdwds’ and Password=’dsdsd ‘ or ‘1’ = ‘1’;1=1恒成立
6.2 解决SQL注入
- 通过preparedStatement对象,preparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值。
public class JdbcTest02 {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement ps =null;
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url ="jdbc:mysql://localhost:3306/employeemanagementsystem";
String user ="root";
String password = "123";
conn =DriverManager.getConnection(url,user,password);
String sql="select * from department where DEPARTMEN_ID=?";
ps = conn.prepareStatement(sql);
//给占位符赋值
ps.setString(1,"1");
rs = ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("DEPARTMEN_ID")+"\t"+
rs.getString("DEPARTMENT_NAME")+
"\t"+rs.getString("DEPARTMEMT_DESCRIBE"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (null != rs){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != ps){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != conn){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
当一条SQL语句已经编译运行了,当再次使用该语句(即该语句和原来的那条语句完全一模一样)时,将直接运行
6.3 preparedStatement和Statement的区别
1. preparedStatement会在编译阶段做类型检查
1. Statement存在SQL注入;preparedStatement解决了SQL注入问题
1. Statement编译一次执行一次;preparedStatement编译一次,可执行N次,preparedStatement效率较高一些
1. preparedStatement使用较多,但是statement也有需求,当需求里需要SQL注入时(比如升序降序)
6.4 jdbc查询代码完善
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
String url ="jdbc:mysql://localhost:3306/employeemanagementsystem";
String user = "root";
String password = "123";
connection = DriverManager.getConnection(url,user,password);
//3.获取预先操作数据库对象,预执行SQL语句
String sql = "select * from department where DEPARTMEN_ID=?";
preparedStatement = connection.prepareStatement(sql);
//4.给SQL语句赋值,调用executeQuery方法进行查询
preparedStatement.setInt(1,1);
resultSet = preparedStatement.executeQuery();
//5.处理查询结果集
if (resultSet.next()){
System.out.println(resultSet.getString("DEPARTMEN_ID")+"\t"
+resultSet.getString("DEPARTMENT_NAME")+"\t"
+resultSet.getString("DEPARTMEMT_DESCRIBE")+"\t");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//6.关闭资源
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
7. jdbc事务
7.1 jdbc事务自动提交机制
- jdbc事务是自动提交的,只要任意执行一条DML语句,则自动提交一次
- 代码演示:
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
String url ="jdbc:mysql://localhost:3306/bank";
String user = "root";
String password = "123";
connection = DriverManager.getConnection(url,user,password);
//3.获取预先操作数据库对象,执行第一条SQL语句
String sql = "update carddata set money=? where card_id=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1,5000);
preparedStatement.setInt(2,111);
preparedStatement.executeUpdate();
//执行第二条SQL语句
String sql2 = "update carddata set money=? where card_id=?";
//在下面这行代码中加一个断点,debug,会发现上一条语句执行完之后,数据库中的内容就已经修改了
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setDouble(1,5000);
preparedStatement.setInt(2,222);
preparedStatement.executeUpdate();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//6.关闭资源
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
7.2 jdbc事务代码实现:
- 步骤:
1、在SQL语句执行之前将自动提交机制变为手动提交机制
connection.setAutoCommit(false);
2、SQL语句执行之后,手动提交事务
connection.commit();
3、在catch语句块中手动回滚事务
connection.rollback();
- 代码实现:
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
String url ="jdbc:mysql://localhost:3306/bank";
String user = "root";
String password = "123";
connection = DriverManager.getConnection(url,user,password);
//3.获取预先操作数据库对象,执行第一条SQL语句
String sql = "update carddata set money=? where card_id=?";
//将自动提交事务关闭
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setDouble(1,5000);
preparedStatement.setInt(2,111);
preparedStatement.executeUpdate();
//即使发生异常,也不会导致money丢失
Object o = null;
o.toString();
//执行第二条SQL语句
String sql2 = "update carddata set money=? where card_id=?";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setDouble(1,5000);
preparedStatement.setInt(2,222);
preparedStatement.executeUpdate();
//手动提交事务
connection.commit();
} catch (ClassNotFoundException e) {
//回滚事务
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//6.关闭资源
if (null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != preparedStatement){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
8. 将JDBC进行封装
工具类构造方法建议私有化,因为工具类一般都是静态的,不需要调用构造方法; 在下面的封装代码中,Class.forName();可以写在静态代码块中,当类加载时调用一次就可以了 close关闭时,建议statement,而不是preparedStatement,面向父类编程
- 简单代码实现: ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;
/**
- 操作数据库的基类—静态类
/ public class BaseDao {
//驱动 private static final String driver=”com.mysql.jdbc.Driver”; //数据库链接URL private static final String url=”jdbc:mysql://localhost:3306/employeemanagementsystem?useUnicode=true&characterEncoding=utf8”; //数据库账号 private static final String user=”root”; //数据库密码 private static final String password=”123”; //数据库链接对象 static Connection connection = null; //数据库操作对象 static PreparedStatement preparedStatement=null; //查询返回的结果集 static ResultSet resultSet=null;
/**
- 获取数据库连接
@return 返回数据库链接对象 */ public static Connection getConnection(){
try {
Class.forName(driver);
connection = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return connection; } /**
- 查询操作
- @param sql 执行语句
- @param params 需要的参数
- @return 返回查询的结果集(java.sql.ResultSet)
*/
public static ResultSet executeQuery(String sql,Object[] params) throws Exception{
connection=getConnection();
preparedStatement = connection.prepareStatement(sql);
if(params!=null){
} resultSet = preparedStatement.executeQuery(); return resultSet; } /**for(int i = 0; i < params.length; i++){
preparedStatement.setObject(i+1, params[i]);
}
- 更新操作
- @param sql 操作语句
- @param params 需要的参数
@return 返回受影响行数 */ public static int executeUpdate(String sql,Object[] params) throws Exception{ int updateRows = 0; connection=getConnection(); preparedStatement = connection.prepareStatement(sql); for(int i = 0; i < params.length; i++){
preparedStatement.setObject(i+1, params[i]);
} updateRows = preparedStatement.executeUpdate(); return updateRows; }
/**
释放资源 */ public static void closeResource(){
if(resultSet != null){
try {
resultSet.close();
resultSet = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} if(preparedStatement != null){
try {
preparedStatement.close();
preparedStatement = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} if(connection != null){
try {
connection.close();
connection = null;//GC回收
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } } ```
9. 模糊查询
- 占位符传值里面需要有模糊查询的符号;
String sql = "select * from department where DEPARTMENT_NAME like ?";
preparedStatement = connection.prepareStatement(sql);
//给SQL语句赋值,调用executeQuery方法进行查询
preparedStatement.setString(1,"%事%");
resultSet = preparedStatement.executeQuery();
10. 悲观锁(行级锁)和乐观锁
- 部分主要代码实现:
//关闭自动提交
connection.setAutoCommit(false);
//3.获取预先操作数据库对象,预执行SQL语句
String sql = "select * from department where DEPARTMEN_ID=? for update";
preparedStatement = connection.prepareStatement(sql);
//4.给SQL语句赋值,调用executeQuery方法进行查询
preparedStatement.setString(1,"2");
resultSet = preparedStatement.executeQuery();
connection.commit();
connection.setAutoCommit(false);
//3.获取预先操作数据库对象,预执行SQL语句
String sql = "update department set DEPARTMENT_NAME=? where DEPARTMEN_ID=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"人事部");
preparedStatement.setString(2,"2");
int num = preparedStatement.executeUpdate();
connection.commit();
说明:
在第一部分的代码的commit那里加断点,debug;在运行第二部分的代码;由于第一部分的代码给该行设置了行级锁,导致第二部分的代码无法对该行进行修改操作,当继续debug第一部分剩下的代码时,第二部分的代码也可以继续执行。
11. jdbc使用中出现的一些错误
java.sql.SQLException: Parameter index out of range (1 > number of parameters, which is 0).
错误解决:
SQL语句存在错误:
String sql = “delete from department where departmen_id”;
纠正:departmen_id=?