JDBC
一、引言
1.1 如何操作数据
使用客户端工具访问数据库,需要手工建立链接,输入用户名和密码登录,编写SQL语句,点击执行,查看操作结果(结果集或受影响行数)。
1.2 实际开发中,会采用客户端操作数据库吗?
在实际开发过程中,当用户的数据发生改变时,不可能通过客户端操作执行SQL语句,因为操作量过大!无法保证效率和正确性
二、JDBC(Java DataBase Connectivity)
2.1 什么是JDBC?
JDBC(Java DataBase Connectivity) Java连接数据库,可以使用Java语言连接数据库完成CRUD操作
2.2 JDBC核心思想
Java中定义了访问数据库的接口,可以为多种关系型数据库提供统一的访问方式。
由数据库厂商提供驱动实现类(Driver数据库驱动)
2.2.1 MySQL数据库驱动
- mysql-connector-java-5.1.X 适用于5.X版本
- mysql-connector-java-8.0.X 适用于8.X版本
2.2.2 JDBC API
JDBC 是由多个接口和类进行功能实现
类型 | 全限定名 | 简介 |
---|---|---|
class | java.sql.DriverManager | 管理多个数据库驱动类,提供了获取数据库连接的方法 |
interface | java.sql.Connection | 代表一个数据库连接(当Connection不是NULL时,表示已连接一个数据库) |
interface | java.sql.Statement | 发送SQL语句到数据库的工具 |
interface | java.sql.ResultSet | 保存SQL查询语句的结果数据(结果集) |
class | java.sql.SQLException | 处理数据库应用程序时所发生的异常 |
2.3 环境搭建
- 在项目下新建 lib 文件夹,用于存放 jar 文件
- 将MySQL驱动文件mysql-connector-java-5.1.25-bin.jar 复制到项目的lib文件夹中
- 选中lib文件夹 右键选择 add as library,点击OK
三、JDBC开发步骤【重点
】
3.1 注册驱动
使用Class.forName(“com.mysql.jdbc.Driver”); 手动加载字节码文件到JVM中
Class.forName("com.mysql.jdbc.Driver");
3.2 连接数据库
- 通过DriverManager.getConnection(url,user,password);获得数据库连接对象
- URL:jdbc:mysql://localhost:3306/database
- user:root
- password:1234
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf8","root","1234");
3.3 获取发送SQL的对象
通过Connection对象获得Statement对象,用于对数据库进行通用访问的
Statement statement = connection.createStatement();
3.4 执行SQL语句
编写SQL语句,并执行,接收执行后的结果
int result = statement.executeUpdate("update stu set student_name='高强',sex='女' where student_id = 'S1003'");
- 注意:在编写DML语句时,一定要注意字符串参数的符号是单引号 ‘值’
- DML语句:增、删、改时,执行的结果是受影响行数(int类型)。
- DQL语句:查询时,返回的是数据结果集(ResultSet结果集)
3.5 处理结果
接收并处理操作结果
if(result > 0){
System.out.println("执行成功");
}
3.6 释放资源
遵循的是先开后关的原则,释放过程中用到的所有资源对象
statement.close();
connection.close();
3.7 综合案例
综合核心六步,实现增删改
public static void main(String[] args)throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.连接数据库
String url = "jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8";
String user = "root";
String password="1234";
Connection connection=DriverManager.getConnection(url,user,password);
//3.获取发送SQL的对象
Statement statement = connection.createStatement();
//4.编写SQL语句,执行SQL语句(返回受影响行数)
//新增
// String sql = "insert into stu(student_id,student_name,sex,birthday,phone,GradeId) values ('S1005','高强','女','1999-09-09','15612341234',3)";
// String sql = "delete from stu where student_id = 'S1005'";删除
//修改
String sql = "update stu set student_name='高强',sex='女' where student_id = 'S1003'";
int result = statement.executeUpdate(sql);
//5.处理数据
if(result > 0){
System.out.println("执行成功!");
}
//6.释放资源 先开后关
statement.close();
connection.close();
}
四、 ResultSet(结果集)
在执行查询SQL后,存放查询到的结果集数据
4.1 接收结果集
ResultSet rs = statement.executeQuery("SELECT * FROM stu");
4.2 遍历ResultSet中的数据
ResultSet以表(Table)结构进行临时结果的存储,需要通过JDBC API将其中的数据进行依次获取
- 数据行指针:初始位置在第一行数据前,每调用一次boolean next()方法,ResultSet中指针向下移动一行,结果为true,表示当前行有数据
- rs.getXxx(“列名”); 根据列名获得数据
- rs.getXxx(整数下标); 代表根据列的编号顺序获得!从1开始
boolean next() throws SQLException;//判断rs结果集中下一行是否有数据
4.2.1 遍历方法
int getInt(int columnIndex) throws SQLException;//获得当前行的第N列的int值
int getInt(String columnLabel) throws SQLException;//获得当前行columnLabel列的int值
4.3 综合案例
对stu表所有的数据进行遍历
4.3.1 根据列的名称获取
package com.qf.day42.t2.basic;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class TestDql {
public static void main(String[] args) throws Exception{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获得连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8","root","1234");
//3.获取执行SQL的对象
Statement statement = connection.createStatement();
//4.编写SQL语句
String sql = "select student_id,student_name,sex,birthday,phone,GradeId from stu;";
ResultSet resultSet = statement.executeQuery(sql);
//5.处理结果 (结果集!)
while(resultSet.next()){//判断结果集中是否有下一行!
//根据列名获取当前行每一列的数据
String student_id = resultSet.getString("student_id");
String student_name = resultSet.getString("student_name");
String sex = resultSet.getString("sex");
String birthday = resultSet.getString("birthday");
String phone = resultSet.getString("phone");
int gradeId = resultSet.getInt("gradeId");
System.out.println(student_id+"\t"+student_name+"\t"+sex+"\t"+birthday+"\t"+phone+"\t"+gradeId);
}
//6.释放资源
resultSet.close();
statement.close();
connection.close();
}
}
4.3.2 根据列的下标获取
//5.处理结果 (结果集!)
while(resultSet.next()){//判断结果集中是否有下一行!
//根据列的编号获取当前行每一列的数据
String student_id = resultSet.getString(1);
String student_name = resultSet.getString(2);
String sex = resultSet.getString(3);
String birthday = resultSet.getString(4);
String phone= resultSet.getString(5);
int gradeId = resultSet.getInt(6);
System.out.println(student_id+"\t"+student_name+"\t"+sex+"\t"+birthday+"\t"+phone+"\t"+gradeId);
}
五、常见错误
- java.lang.ClassNotFoundException 找不到类(类名书写错误、没有导入jar包)
- com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException 与SQL语句相关的错误(表名列名书写错误、约束错误、插入的值是String类型,但是没有加单引号)建议:在客户端工具中测试sql语句后,再粘贴到代码中来
- com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry ‘S1003’ for key ‘PRIMARY’ 原因:主键值已存在!更改要插入的主键值
- com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:Unknown column ‘password’ in
- 可能输入的值的类型不对,确定插入元素时,对应的值的类型是否争取
六、综合案例【登录】
6.1 创建表
- 创建一张用户表 User
- id 主键、自动增长
- username 字符串类型 非空
- password 字符串类型 非空
- phone 字符串类型
- 插入2条测试语句
6.2 实现登录
- 通过控制台,用户输入用户名和密码
- 用户输入的用户名和密码作为参数,编写查询SQL语句。
- 如果查询到用户,则用户存在,提示登录成功,反之,提示失败!
七、SQL注入问题
7.1 什么是SQL注入
当用户输入的数据中有SQL关键字或语法时,并且参与了SQL语句的编译,导致SQL语句编译后条件结果为true,一直得到正确的结果。称为SQL注入
7.2如何避免SQL注入
由于编写的SQL语句,是在用户输入数据后,整合后再编译成SQL语句。所以为了避免SQL注入的问题,使SQL语句在用户输入数据前,SQL语句已经完成编译,成为了完整的SQL语句,再进行填充数据
八、 PreparedStatement【重点
】
PreparedStatement接口继承了Statement接口。执行SQL语句的方法没有区别!
8.1 PreparedStatement的应用
作用:1.预编译SQL语句,效率高!
2.安全,避免SQL注入
3.可以动态的填充数据,执行多个同构的SQL语句
8.1.1 参数标记
//1.预编译SQL语句
PreparedStatement pstmt = connection.prepareStatement(sql);
8.1.2 动态参数绑定
pstmt.setXxx(下标,值); 参数下标是从1开始,为指定占位符下标绑定值
//2.为占位符下标赋值
pstmt.setString(1,username);
pstmt.setString(2,password);
九、综合练习
9.1创建数据库、表
数据库 Account
- 创建一张表 t_ccount。有以下列
- cardId:字符串,主键
- password:字符串,非空
- username:字符串,非空
- balance:小数,非空
- phone:字符串,非空
9.2 创建项目通过JDBC实现功能
创建AccountSystem类,完成下列功能
- 开户:控制台输入所有的账户信息,使用PreparedStatement添加至t_account表
- 存款:控制台输入卡号、密码、存储金额进行修改
- 取款:输入卡号、密码、取款金额
- 转账:输入卡号、密码、对方卡号、转账金额进行修改
- 修改密码:控制台输入卡号、密码,再输入新密码进行修改
- 注销:控制台输入卡号、密码,删除对应的账户信息
十、封装工具类
10.1 重用性方案
- 封装了获取连接、释放资源两个方法
- 提供public static Connection getConnection()方法
- 提供public static void closeAll(Connection conn,Statement statement,ResultSet resultSet)
10.1.1 工具实现
package com.qf.day43.t1;
import sun.plugin2.gluegen.runtime.CPU;
import java.sql.*;
/**
* 数据库工具类
* 1、提供连接 -->Connection
* 2、提供统一资源关闭
* 可重用性方案
*/
public class DBUtils {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//硬编码
//获得连接
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=UTF8", "root", "1234");
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
//释放资源
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
10.2 跨平台方案
定义 private static final Properties properties = new Properties();//配置文件集合
定义static{
//首次使用工具类,触发类加载
InputStream is = DBUtils.class.getResourceAsStream(“路径”);//复用本类自带流,读取配置文件
properties.load(is);//将is流中的配置文件信息,加载到集合中
Class.forName(properties.getProperty(“driver”));
}
在getConnection方法中。应用properties.getProperty(“url”);
10.2.1 实现
在src目录下新建db.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/companydb?useUnicode=true&characterEncoding=utf8
username=root
password=123456
DBUtils代码实现
package com.qf.day43.t2;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* 数据库工具类
* 1、获取连接 connection
* 2、释放资源
* 可跨平台方案
*/
public class DBUtils {
private static final Properties properties = new Properties();
static {
try {
//适用类自身自带的流 路径
InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
properties.load(is);//通过流,将配置信息的内容分割成键值对
Class.forName(properties.getProperty("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(properties.getProperty("url"),properties.getProperty("username"),properties.getProperty("password"));
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//释放资源
public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
十一、ORM
ORM(Object Relational Mapping)
从数据库查询到的结果集(ResultSet)在进行遍历时,逐行遍历,取出的都是零散的数据。在实际应用开发中,我们需要将零散的数据进行封装整理
11.1 ORM 实体类(entity):零散数据的载体
- 一行数据中,多个零散的数据进行整理
- 通过entity的规则对表中的数据进行对象的封装
- 表名=类名;列名=属性名;提供各个属性的get、set方法
- 提供无参构造方法、(视情况添加有参构造)
11.1.1 ORM应用
package com.qf.day43.t2;
public class User {
private int id;
private String username;
private String password;
private String sex;
private String email;
private String address;
public User(){}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
", address='" + address + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public User(int id, String username, String password, String sex, String email, String address) {
this.id = id;
this.username = username;
this.password = password;
this.sex = sex;
this.email = email;
this.address = address;
}
}
package com.qf.day43.t2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class OrmSelect {
public static void main(String[] args) {
Connection connection = DBUtils.getConnection();
String sql = "select id,username,PASSWORD,sex,email,address from `user`;";
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
preparedStatement = connection.prepareStatement(sql);
System.out.println(preparedStatement);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {//拿到每一行数据、
//拿到每一列的数据
User user = new User();
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String sex = resultSet.getString("sex");
String email = resultSet.getString("email");
String address = resultSet.getString("address");
//将一行中零散的数据,封装在一个User对象里。
user.setId(id);
user.setUsername(username);
user.setPassword(password);
user.setSex(sex);
user.setEmail(email);
user.setAddress(address);
System.out.println(user);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, resultSet);
}
}
}
十二、DAO(Data Access Object)
数据访问对象
- 将所有对同一张表的操作都封装在一个XXXDaoImpl对象中。
- 根据增删改查的不同功能,实现具体的方法(insert,update,delete,select,selectAll)
- 经验:对于任何一张表中的数据进行操作时,无非就是增、删、改、查。应将对于一张表的所有操作统一封装在一个数据访问对象中。重用
**
* 对数据库中User表的一系列操作。
* 只做对数据库访问的操作!\
* 复用! 对同一张表的操作 实现复用
*/
public class UserDaoImpl {
private Connection connection = null;
private PreparedStatement preparedStatement = null;
private ResultSet resultSet = null;
//增
public int insert(User user) {
try {
connection = DBUtils.getConnection();
String sql = "insert into user(id,username,password,sex,email,address) values(?,?,?,?,?,?);";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, user.getId());
preparedStatement.setString(2, user.getUsername());
preparedStatement.setString(3, user.getPassword());
preparedStatement.setString(4, user.getSex());
preparedStatement.setString(5, user.getEmail());
preparedStatement.setString(6, user.getAddress());
int i = preparedStatement.executeUpdate();
return i;//将操作结果返回给调用者!
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, null);
}
return 0;
}
//删
public int delete(int id) {
connection = DBUtils.getConnection();
String sql = "delete from user where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, resultSet);
}
return 0;
}
//改
public int update(User user) {
connection = DBUtils.getConnection();
String sql = "update user set username=?,password=?,sex=?,email =?,address=? where id = ?";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, user.getUsername());
preparedStatement.setString(2, user.getPassword());
preparedStatement.setString(3, user.getSex());
preparedStatement.setString(4, user.getEmail());
preparedStatement.setString(5, user.getAddress());
preparedStatement.setInt(6, user.getId());
return preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, resultSet);
}
return 0;
}
//查单个
public User select(int id) {
connection = DBUtils.getConnection();
String sql = "select id,username,password,sex,email,address from user where id = ?;";
try {
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, id);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id1 = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String sex = resultSet.getString("sex");
String email = resultSet.getString("email");
String address = resultSet.getString("address");
User user = new User(id1, username, password, sex, email, address);
return user;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, resultSet);
}
return null;
}
//查多个
public List<User> selectAll() {
connection = DBUtils.getConnection();
String sql = "select id,username,password,sex,email,address from user";
List<User> userList = new ArrayList<>();
try {
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String sex = resultSet.getString("sex");
String email = resultSet.getString("email");
String address = resultSet.getString("address");
User user = new User(id, username, password, sex, email, address);
userList.add(user);//每封装完一个对象,添加到集合当中
}
return userList;
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(connection, preparedStatement, resultSet);
}
return null;
}
}
十三、日期类型
- java.util.Date
- Java语言常规应用层面的日期类型。可以通过字符串创建对应的时间对象
- 无法直接通过JDBC插入数据库
- java.sql.Date
- 不可以通过字符串创建对应的时间对象。只能通过毫秒值创建对象(1970年1月1日至今的毫秒值)
- 可以直接通过JDBC插入数据库
13.1 日期格式化工具
SimpleDateFormat 日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");//按照指定格式转换成util.Date类型
java.util.Date date = sdf.parse("2000-01-01");
13.2 日期工具类 DateUtil
package com.qf.day43.t4;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* 日期转换
* 字符串转UtilDate
* 字符串转SqlDate
* utilDate转成Sqldate
*/
public class DateUtils {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
//字符串转Util
public static java.util.Date strToUtilDate(String str) {
try {
return simpleDateFormat.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
//字符串转sql
// public static java.sql.Date strToSqlDate(String str){
// SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
// try {
// java.util.Date date = simpleDateFormat.parse(str);
// return new java.sql.Date(date.getTime());
// } catch (ParseException e) {
// e.printStackTrace();
// }
// return null;
// }
//util转sql
public static java.sql.Date utilToSql(java.util.Date date){
return new java.sql.Date(date.getTime());
}
}
13.2.1 测试
package com.qf.day43.t4;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TestDatetimes {
public static void main(String[] args) throws ParseException {
//1.java.util.Date 当前系统时间
// System.out.println(new java.util.Date());
//
// //自定义一个时间
// String str = "1999-09-09";
// //日期转换 字符串转为 java.util.Date
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// //将日期字符串转换成 util.Date类型
// java.util.Date utilDate = sdf.parse(str);
// System.out.println(utilDate);
//
// //sql.Date 需要毫秒值,来构建一个日期
// java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());
// System.out.println(sqlDate);
//
//
// java.util.Date date = DateUtils.strToUtilDate("2002-3-18");
// System.out.println(date);
//
// java.sql.Date date2 = DateUtils.utilToSql(date);
// System.out.println(date2);
System.out.println(new java.util.Date());
System.out.println(new java.sql.Date(new java.util.Date().getTime()));
}
}