目录
execute(String sql):常用于执行DDL语句(CREATE,DROP,ALTER)
executeUpdate(String sql):常用于执行DML语句(增删改)
executeQuery(String sql):常用于执行DQL语句
什么是JDBC?
JDBC代表JAVA DATA bASE Connectivity(Java数据库连接),它是一个标准的 Java API,用于 Java 编程语言和大量数据库之间的独立于数据库的连接。
JDBC库包括用于下面提到的每个任务的API,这些API通常与数据库使用相关联。
-
与数据库建立连接。
-
创建SQL或MySQL语句。
-
在数据库中执行SQL或MySQL查询。
-
查看和修改结果记录。
JDBC架构
JDBC API支持用于数据库访问的两层和三层处理模型,但是通常,JDBC体系结构由两层组成-
-
JDBC API: 这提供了应用程序到JDBC管理器的连接。
-
JDBC Driver API: 这支持JDBC管理器到驱动程序的连接。
通用JDBC组件
JDBC API提供以下接口和类:
-
DriverManager:此类管理数据库驱动程序列表。使用通信子协议将来自Java应用程序的连接请求与适当的数据库驱动程序进行匹配。第一个在JDBC下识别特定子协议的驱动程序将用于建立数据库连接。
-
Driver:该接口处理与数据库服务器的通信。您很少会直接与Driver对象进行交互。而是使用DriverManager对象,该对象管理此类型的对象。它还抽象了与使用Driver对象相关的详细信息。
-
Connection:此接口包含用于联系数据库的所有方法。连接对象表示通信上下文,即,与数据库的所有通信仅通过连接对象进行。
-
Statement:您可以使用从此接口创建的对象将SQL语句提交到数据库。除执行存储过程外,某些派生接口还接受参数。
-
ResultSet:使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它充当迭代器,使您可以遍历其数据。
-
SQLException:此类处理数据库应用程序中发生的所有错误。
使用JDBC连接数据库的步骤:
1:加载驱动 Class.forName()
2:建立连接 DirverManager建立连接
3:获取执行对象
execute(String sql):常用于执行DDL语句(CREATE,DROP,ALTER)
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* JDBC java database connectivity java数据库连接
* JDBC是有SUM提供的一套API,是使用java连接数据的一套标准API接口,各个数据库提供上都实现了这一条接口提供了
* 连接其提供的数据库产品的实现类(以jar包形式,又称为连接该数据库的驱动包)。
*/
public class JDBCDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
/*
使用JDBC连接数据库的步骤
1:加载驱动 Class.forName()
2:建立连接 DirverManager建立连接
3:获取执行对象
*/
//1
Class.forName("com.mysql.cj.jdbc.Driver");//不同数据库的包名.类名不一样,但是对于同一个数据库无论那个版本写法都是一致的。
//2
/*
DriverManager的getConnection用来连接数据库,该方法需要传入三个String类型的参数
参数1:数据库的URL地址(每种数据库有各自的固定格式)
参数2:用户名
参数3:密码
该方法返回一个java.sql.Connection的实例(Connection是一个接口,是JDBC核心接口之一,表示与
数据库的一个连接,不同的数据库驱动包中都提供了对应的实现类)
*/
// jdbc:不同数据库的URL格式... /数据库名?参数
try (
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/empdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
) {
/*
通过连接对象Connection获取执行SQL的执行对象Statement
Statement对象用于向数据库执行SQL语句
*/
Statement statement = conn.createStatement();
/*
新建一张表:userinfo
表字段:id,username,password,age,salary
*/
String sql = "CREATE TABLE userinfo(" +
" id INT primary key auto_increment," +
" username VARCHAR(30) NOT NULL ," +
" password VARCHAR(30)," +
" age INT(3)," +
" salary DOUBLE(7,2)" +
" )";
/*
execute(String sql)
该方法可以用来执行任意类型的SQL语句,但是由于DML,DQL有专门的SQL
因此该方法常用于执行DDL语句(CREATE,DROP,ALTER)
*/
statement.execute(sql);
System.out.println("执行完毕!");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
executeUpdate(String sql):常用于执行DML语句(增删改)
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* 执行DML语句
*/
public class JDBCDemo2 {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
try (
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/empdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
) {
Statement statement = conn.createStatement();
/*
向class表中插入数据:1年级1班
*/
String sql = "INSERT INTO class (name) VAlUES ('1年级1班')";
/*
int executeUpdate(String sql)
通常用来专门执行DML(INSERT,UPDATE,DELETE)语句,返回的int值表示影响了表中多少条记录
*/
int num = statement.executeUpdate(sql);
if(num>0){//说明至少影响了表中1条记录
System.out.println("插入成功");
}else{
System.out.println("插入失败");
}
}catch (Exception e){
}
}
}
修改:将“1年级1班”改为学前班
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* 将1年级1班,改为"学前班"
*/
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
try (
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/empdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
) {
Statement statement = conn.createStatement();
String sql = "UPDATE class SET name='学前班' WHERE name='1年级1班'";
int num = statement.executeUpdate(sql);
if(num>0){//说明至少影响了表中1条记录
System.out.println("修改成功");
}else{
System.out.println("修改失败");
}
}catch (Exception e){
}
}
}
executeQuery(String sql):常用于执行DQL语句
重用代码:建立连接部分重用
DBUtil类:管理数据库连接
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 数据库工具类
*/
public class DBUtil {
static {
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取一个数据库连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/empdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true", "root", "root");
}
}
指定DQL语句:
package jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 指定DQL语句
*/
public class JDBCDemo3 {
public static void main(String[] args) {
try(
Connection connection = DBUtil.getConnection();
){
Statement statement = connection.createStatement();
//查看6岁的学生都有谁?
String sql = "SELECT id,name,age,class_id FROM student WHERE age=6";
/*
Statement中有专门用来执行查询语句DQL的方法:ResultSet executeQuery(String sql)
该方法会返回一个ResultSet对象,这个对象封装了查询出来的结果集。
*/
ResultSet rs = statement.executeQuery(sql);
/*
结果集的遍历类似集合的迭代器
boolean next()
该方法是结果集核心方法之一,用于让结果集游标向下一条记录,返回值表示是否有下一条。
注:游标默认是在结果集第一条记录之上。
*/
while(rs.next()) {
//根据字段位置获取
// int id = rs.getInt(1);//获取该条记录第一个字段的值(因为该字段是int型,所以用getInt())
// String name = rs.getString(2);
// int age = rs.getInt(3);
// int classId = rs.getInt(4);
//根据字段名
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
int classId = rs.getInt("class_id");
System.out.println(id + "," + name + "," + age + "," + classId);
}
//如果结果集数据需要保存,应当在当前java程序中用集合等形式保存结果集使用。
rs.close();//当结果集遍历完毕后将其关闭
}catch(Exception e){
}
}
}
预编译SQL语句
Statement每次执行SQL语句时,都会将该SQL语句发送给数据库,而数据库接收到SQL语句后会解析SQL语句并生成。执行计划(该操作是一个耗时的操作),然后在执行该计划。当SQL语义相同,但是数据不同时,如果我们执行这些SQL,那么每次数据库接收SQL都要生成执行计划。
SQL注入攻击:拼接SQL注入攻击内容后,语义发生了改变,因此数据库接收到该SQL是就错误的执行了内容,AND和OR产生顺序错误。
SELECT * FROM userinfo WHERE username='xxx' AND password='1' OR '1'='1'
Statement执行SQL语句时,数据是需要拼接SQL来完成,这存在SQL注入攻击。但是PreparedStatement会先将预编译SQL发送给数据库生成执行计划,那么所有数据都会被当作参数。因此就算传入的是注入攻击的内容,它也仅会当这部分内容为参数值,语义已经不会发生改变了(因为执行计划已经生成)。
PreparedStatement会在创建时,先将预编译SQL语句发送给数据库来生成执行计划(仅1次),并且"?"内容会在生成的执行计划中当作"参数"。在多次执行时,每次仅需要将"?"对应的数据发送给数据库,来重用预编译SQL对应的执行计划,这样效率会高很多。
预编译SQL先行发送给数据,生成执行计划后,数据库就理解了操作,并等待发送过来用户名和密码的值了。
SELECT * FROM userinfo WHERE username=? AND password=?
当我们发送SQL注入攻击内容时:
参数1(第一个?的内容):xxx,参数2(第二个?的内容):1' OR '1'='1
此时数据库会理解为你要查询的人的密码是"1' OR '1'='1",并不会将其当作SQL语句的一部分了。
预编译SQL语句:是将在SQL中会变化的值(原来拼接SQL语句的部分)先以"?"进行占位
package jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
/**
* 预编译SQL语句
*/
public class JDBCDemo4 {
public static void main(String[] args) {
try(
Connection connection = DBUtil.getConnection();
){
/*
预编译SQL语句是将在SQL中会变化的值(原来拼接SQL语句的部分)先以"?"进行占位
查看孙悟空的信息
SELECT id,name,salary,dept_id
FROM emp
WHERE name='孙悟空'
*/
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要查询的员工名称?");
String name = scanner.nextLine();
/*
拼接SQL语句会存在两个明显问题:
1:代码复杂度高,容易出现错误,且可读性差。
2:存在着SQL注入攻击
*/
/*String sql = "SELECT id,name,salary,dept_id " +
"FROM emp " +
"WHERE name='"+name+"'";*/
String sql = "SELECT id,name,sal,dept_id " +
"FROM emp " +
"WHERE name=?";
PreparedStatement ps = connection.prepareStatement(sql);
//name字段是VARCHAR,因此这里设置该?对应的值应当选取字符串类型
ps.setString(1,"孙悟空");//第1个"?"要设置为字符串的值'孙悟空'
ResultSet rs = ps.executeQuery();
if(rs.next()){
int id = rs.getInt(1);
String n = rs.getString(2);
int salary = rs.getInt(3);
int deptId = rs.getInt(4);
System.out.println(id+","+n+","+salary+","+deptId);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
预编译SQL:在DML语句中使用
package jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 在DML语句中使用预编译SQL
*/
public class JDBCDemo5 {
public static void main(String[] args) {
try(
Connection connection = DBUtil.getConnection();
){
String sql = "INSERT INTO student1(name,age,class_id) VALUES(?,?,?)";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,"张三");
ps.setInt(2,5);
ps.setInt(3,0);
int num = ps.executeUpdate();
if(num>0){
System.out.println("插入成功");
}else{
System.out.println("插入失败");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
UserController类:用户注册的信息插入到数据库的userinfo表中;在登录中,查看登录信息和数据库里的信息是否匹配
package com.webserver.controller;
import com.webserver.annotations.Controller;
import com.webserver.annotations.RequestMapping;
import com.webserver.http.HttpServletRequest;
import com.webserver.http.HttpServletResponse;
import com.webserver.util.DBUtil;
import java.io.*;
import java.sql.*;
@Controller
public class UserController {
private static File userDir;//用来表示存放所有用户信息的目录
static {
userDir = new File("./users");
if(!userDir.exists()){
userDir.mkdirs();
}
}
@RequestMapping("/regUser")
public void reg(HttpServletRequest request, HttpServletResponse response){
System.out.println("开始处理注册!!!!");
//获取表单信息
String username = request.getParameter("username");
String password = request.getParameter("password");
String nickname = request.getParameter("nickname");
String ageStr = request.getParameter("age");
//必要验证
if(username==null||username.isEmpty()||
password==null||password.isEmpty()||
nickname==null||nickname.isEmpty()||
ageStr==null||ageStr.isEmpty()||!ageStr.matches("[0-9]+")
){
//信息输入有误提示页面
response.sendRedirect("/reg_info_error.html");
return;
}
System.out.println(username+","+password+","+nickname+","+ageStr);
int age = Integer.parseInt(ageStr);
//2 将用户信息插入到数据库的userinfo表中
try (
Connection connection = DBUtil.getConnection();
){
Statement statement = connection.createStatement();
//验证该用户是否存在,若存在则直接响应have_user.html,否则才执行插入操作
String sql = "SELECT 1 FROM userinfo WHERE username='"+username+"'";
ResultSet rs = statement.executeQuery(sql);
if(rs.next()){//判断结果集是否有一条记录
response.sendRedirect("/have_user.html");
return;
}
/*
INSERT INTO userinfo (username,password,nickname,age)
VALUES('xx','xx','xx',2)
*/
sql = "INSERT INTO userinfo (username,password,nickname,age) " +
"VALUES('"+username+"','"+password+"','"+nickname+"',"+age+")";
System.out.println(sql);
int num = statement.executeUpdate(sql);
if(num>0) {
//利用响应对象要求浏览器访问注册成功页面
response.sendRedirect("/reg_success.html");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
@RequestMapping("/loginUser")
public void login(HttpServletRequest request, HttpServletResponse response){
System.out.println("开始处理登录!!!");
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username+","+password);
//必要的验证工作
if(username==null||username.trim().isEmpty()||
password==null||password.trim().isEmpty()){
response.sendRedirect("login_info_error.html");
return;
}
try(
Connection connection = DBUtil.getConnection();
){
// 1' OR '1'='1 OR和AND执行顺序不一样
String sql = "SELECT id,username,password,nickname,age " +
"FROM userinfo " +
"WHERE username=? " +
"AND password=?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
ResultSet rs = ps.executeQuery();
if(rs.next()){
response.sendRedirect("/login_success.html");
}else{
//登录失败
response.sendRedirect("/login_fail.html");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
DBUtil类:使用数据库连接池来连接数据库
1.在pom中导入驱动
<!--连接池驱动--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency>
DBUtil数据库工具类好处:控制连接数量,重用连接
package com.webserver.util;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* 数据库工具类
*/
public class DBUtil {
//Druid是阿里提供的数据库连接池。连接池的主要作用:1,控制连接数量 2,重用连接
private static DruidDataSource dds;
static {
init();
}
private static void init(){
dds = new DruidDataSource();//实例化连接池对象
//设置连接数据库的基本信息:URL,username,password,初始连接数,最大连接数等
dds.setUsername("root");//数据库用户名
dds.setPassword("root");//数据库密码
dds.setUrl("jdbc:mysql://localhost:3306/bbsdb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true");
dds.setInitialSize(5);//设置初始线程数
dds.setMaxActive(10);//设置最大连接数
}
/**
* 获取一个数据库连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
/*
连接池返回的连接是在实际数据库驱动提供的连接对象上包装了一层连接池自己提供的连接对象,这个
连接对象的close方法并不是真的断开连接,而是将当前连接归还到连接池中。
*/
return dds.getConnection();
}
}
例子:使用DBUtil工具类,实现用户的注册登录
准备工作:创建数据库和表,有username,password,nickname三个字段
1:创建静态注册登录页面,部分代码
<h1>注册页面</h1> <form action="/reg"> <input type="text" name="username" placeholder="用户名"> <input type="text" name="password" placeholder="密码"> <input type="text" name="nickname" placeholder="昵称"> <input type="submit" value="注册"> </form><h1>登录页面</h1> <form action="/login"> <input type="text" name="username" placeholder="请输入用户名"> <input type="text" name="password" placeholder="请输入密码"> <input type="submit" value="登录"> </form>
2:创建controller.UserController类,实现注册登录业务(使用DBUtil工具类,连接数据库)
package cn.tedu.boot011.controller;
import cn.tedu.boot011.entity.User;
import cn.tedu.boot011.util.DBUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@Controller
public class UserController {
@RequestMapping("/reg")
@ResponseBody
public String reg(User user){
System.out.println("user = " + user);
try (Connection conn = DBUtil.getConnection()){
String sql = "insert into user values(null,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,user.getUsername());
ps.setString(2,user.getPassword());
ps.setString(3,user.getNickname());
ps.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return "注册成功!";
}
@RequestMapping("/login")
@ResponseBody
public String login(User user){
System.out.println("user = " + user);
try (Connection conn = DBUtil.getConnection()){
String sql = "select password from user where username=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1,user.getUsername());
ResultSet rs = ps.executeQuery();
if (rs.next()){
//得到查询到的密码
String pw = rs.getString(1);
if (pw.equals(user.getPassword())){
return "登录成功!";
}
return "密码错误!";
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return "用户名不存在!";
}
}
3:创建实体类entity.User,属性有username,password,nickname,添加get,set,toString方法
缺点:重复连接数据库,后用Mybatis框架进行改善。