1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Statement对象
4.遍历查询结果
5.关闭连接,释放资源
https://www.mysql.com/products/connector/
String dbDriver = "com.mysql.cj.jdbc.Driver"; //JDBC驱动类
String dbURL = "jdbc:mysql://localhost:3306/imooc" ;//连接字符串
String dbUsername = "root";//数据库用户名
String dbPassword = "123456";//数据库密码
//1.加载并初始化JDBC驱动
Class.forName(dbDriver);
//2.创建数据库连接
Connection connection = DriverManager.getConnection(dbURL,
dbUsername, dbPassword);
Class.forName用于加载指定的JDBC驱动类
Class.forName本质是通知JDBC注册这个驱动类
驱动由数据库厂商自行开发,连接字符串也不同
DriverManager类用于注册/管理JDBC驱动程序
DriverManager.getConnection(连接字符串,用户名,密码)方法的作用建立数据库的连接
返回值Connection对象,对应数据库的物理网络连接
Connection对象: Connection用于JDBC与数据库的网络通信对象。 java.sql.Connection是一个接口,具体的内容由驱动厂商实现, 所有数据库的操作都建立在Connection对象的基础上
格式: jdbc:mysql://[主机ip][:端口]/数据库名?参数列表
主机ip与端口是可选设置,默认值为127.0.0.1与3306
参数列表采用url编码,格式:参数1=值1&参数2=值2&…
将SQL语句写入输入中从而导致的泄露数据
SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为
SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理
解决方法: 放弃Statement改用PreparedStatement处理SQL
PreparedStatement: 预编译Statement是Statement的子接口
PreparedStatement对SQL进行参数化,预防SQL注入攻击
PreparedStatementkStatement执行效率更高
/*为预防SQL注入攻击, 将Statement改为PreparedStatement, 将输入值参数化
stmt = conn.createStatement();
// 结果集JDBC接口,用于保存数据库返回结果
rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");*/
String sql = "select * from employee where dname=? and eno>?";
pstmt = conn.prepareStatement(sql);
// 把输入的字符串参数化,第一个参数是位置索引,从1开始,第二个参数时传入参数值。
pstmt.setString(1, pdname);
pstmt.setInt(2, 3500);
rs = pstmt.executeQuery();
节省每次创建数据库连接和关闭数据库连接的代码量
import java.sql.*;
public class DbUtils {
/**
* 创建新的数据库连接
* @param user 数据库用户名
* @param password 数据库密码
* @return 新的Connection对象
* @throws SQLException
* @throws ClassNotFoundException
*/
public static Connection getConnection(String user, String password) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
/**
* 关闭连接,释放资源
* @param rs 结果及对象
* @param stmt Statement对象
* @param conn Connection对象
*/
public static void closeConnection(ResultSet rs, Statement stmt, Connection conn){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
try {
if(conn != null && !conn.isClosed()){
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
import java.sql.*;
import java.util.Scanner;
public class QueryCommand implements Command{
public void execute(){
System.out.println("请输入部门名称: ");
Scanner in = new Scanner(System.in);
String pdname = in.next();
// Connection、 Statement、 ResultSet都内置了Close方法,放到try/catch块外边扩大生命周期。
Connection conn = null;
PreparedStatement pstmt = null;
// Statement stmt = null;
ResultSet rs = null;
/*1.加载并注册JDBC驱动
2.创建数据库连接
3.创建Statement对象
4.遍历查询结果
5.关闭连接,释放资源*/
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true";
conn = DriverManager.getConnection(url,"root","123456");
/*为预防SQL注入攻击, 将Statement改为PreparedStatement, 将输入值参数化
stmt = conn.createStatement();
// 结果集JDBC接口,用于保存数据库返回结果
rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");*/
String sql = "select * from employee where dname=? and eno>?";
pstmt = conn.prepareStatement(sql);
// 把输入的字符串参数化,第一个参数是位置索引,从1开始,第二个参数时传入参数值。
pstmt.setString(1, pdname);
pstmt.setInt(2, 3500);
rs = pstmt.executeQuery();
// rs.next( )返回布尔值,代表是否存在下一条记录
// 如果有,返回true ,同时结果集提取下一条记录
// 如果没有,返回false,循坏就会停止
while(rs.next()){
//可以写两种参数来得到返回值,columnLabel为字段名,columnIndex为字段索引,从1开始。
Integer eno = rs.getInt(1);
String ename = rs.getString("ename");
Float salary = rs.getFloat("salary");
String dname = rs.getString("dname");
System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 当Connection断开时Statement和ResultSet自动断开,所以没有必要手动关闭
try {
if(conn != null && !conn.isClosed()){
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class InsertCommand implements Command {
@Override
public void execute() {
Scanner in = new Scanner(System.in);
System.out.println("请输入员工编号:");
int eno = in.nextInt();
System.out.println("请输入员工姓名:");
String ename = in.next();
System.out.println("请输入员工薪资:");
Float salary = in.nextFloat();
System.out.println("请输入隶属部门:");
String dname = in.next();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection("root", "123456");
String sql = "insert into employee (eno,ename,salary,dname) value (?,?,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, eno);
pstmt.setString(2, ename);
pstmt.setFloat(3, salary);
pstmt.setString(4, dname);
// 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
Integer cnt = pstmt.executeUpdate();
System.out.println("cnt:" + cnt);
System.out.println(ename + "员工入职手续已办理");
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class UpdateCommand implements Command {
@Override
public void execute() {
Scanner in = new Scanner(System.in);
System.out.println("请输入员工编号:");
int eno = in.nextInt();
System.out.println("请输入员工新的薪资:");
Float salary = in.nextFloat();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection("root", "123456");
String sql = "update employee set salary=? where eno=?";
pstmt = conn.prepareStatement(sql);
pstmt.setFloat(1, salary);
pstmt.setInt(2, eno);
// 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
Integer cnt = pstmt.executeUpdate();
System.out.println("cnt:" + cnt);
if(cnt == 1) {
System.out.println(eno + "员工薪资已调整为:" + salary);
} else {
System.out.println("未找到" + eno + "编号员工数据");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class DeleteCommand implements Command {
@Override
public void execute() {
Scanner in = new Scanner(System.in);
System.out.println("请输入员工编号:");
int eno = in.nextInt();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection("root", "123456");
String sql = "delete from employee where eno=?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, eno);
// 所有增、删、改操作都用executeUpdate更新数据库的表,返回影响数据的行数
Integer cnt = pstmt.executeUpdate();
System.out.println("cnt:" + cnt);
if(cnt == 1) {
System.out.println("员工数据已删除");
} else {
System.out.println("未找到" + eno + "编号员工数据");
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
JDBC允许两种事务模式
自动提交事务: 自动提交模式是指每执行一次写操作SQL,自动提交事务
自动提交开启方法: conn.setAutoCommit(true)
自动事务是JDBC默认行为,此模式无法保证多数据一致性
手动提交事务: 手动提交模式是指显式调用commit()与rollback()方法管理事务
手动提交开启方法: conn.setAutoCommit(false)
手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionSample {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection("root", "123456");
// 关闭自动事务提交
conn.setAutoCommit(false);
String sql = "insert into employee (eno, ename, salary, dname) value (?, ?, ?, ?)";
for (int i = 1000; i < 2000; i++) {
if(i == 1005){
// 当抛出错误时,由于是手动提交事务,还未执行事务的提交,所以可以进行回滚,不会出现只插入了一部分数据的情况
throw new RuntimeException("插入错误");
}
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, i);
pstmt.setString(2, "员工" + i);
pstmt.setFloat(3, 4000f);
pstmt.setString(4, "市场部");
pstmt.executeUpdate();
}
// 手动提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
if (conn != null && !conn.isClosed()){
conn.rollback();
}
} catch (SQLException ex) {
e.printStackTrace();
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
}
实体类 Java bean 的格式要求:
- 具备默认构造函数
- 属性私有
- 存在getter与setter
注:实体类的名字应尽量与表名相同,属性名应与数据的字段名相同
public class Employee {
public Employee(){
}
private Integer eno;
private String ename;
private Float salary;
private String dname;
public Integer getEno() {
return eno;
}
public void setEno(Integer eno) {
this.eno = eno;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
}
import com.imooc.jdbc.common.DbUtils;
import com.imooc.jdbc.hrapp.entity.Employee;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class PaginationCommand implements Command {
@Override
public void execute() {
Scanner in = new Scanner(System.in);
System.out.println("请输入页号:");
int page = in.nextInt();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<Employee> list = new ArrayList<>();
try {
conn = DbUtils.getConnection("root", "123456");
String sql = "select * from employee limit ?, 10";
pstmt = conn.prepareStatement(sql);
// 分页数据,MySql的limit从0开始,第二个参数为偏移量,每次提取10个数据
pstmt.setInt(1,(page-1)*10);
rs = pstmt.executeQuery();
while (rs.next()){
Integer eno = rs.getInt("eno");
String ename = rs.getString("ename");
Float salary = rs.getFloat("salary");
String dname = rs.getString("dname");
// 将数据的值传入到实体类中并用add添加到列表中
Employee emp = new Employee();
emp.setEno(eno);
emp.setEname(ename);
emp.setSalary(salary);
emp.setDname(dname);
list.add(emp);
System.out.println(list);
}
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
DbUtils.closeConnection(rs, pstmt, conn);
}
}
}
date类型只精确到天,
datetime类型精确到秒,但二者的处理方式在JDBC中相同。
java.util.Date 为java.sql.Date的父类,当查询时,可直接用JDBC的 java.util.Date接收数据库中的java.sql.Date日期类型数据。修改数据时,要先将输入的字符串转为java.sql.Date类型的数据再传入数据库中,也可以先将数据转为java.util.Date类型再转为java.sql.Date类型。
// 实体类中的代码
// 此处创建为 java.util.Date 类型
private Date hiredate;
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
// 查询数据日期类的代码
Date hiredate = rs.getDate("hiredate");
emp.setHiredate(hiredate);
list.add(emp);
// 新增数据日期类型的代码
java.sql.Date hiredate = null;
while (true){
try {
System.out.println("请输入入职日期:");
String strHiredate = in.next();
hiredate = java.sql.Date.valueOf(strHiredate);
break;
} catch (Exception e) {
System.out.println("日期格式有误。");
continue;
}
}
// String 转为java.util.Date再转为java.sql.Date类型的代码
System.out.println("请输入入职日期:");
String strHiredate = in.next();
java.util.Date udHiredate = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
udHiredate = sdf.parse(strHiredate);
} catch (ParseException e) {
throw new RuntimeException(e);
}
long time = udHiredate.getTime();//获取自1970年的到设置日期为止的毫秒数
java.sql.Date sdHiredate = null;
sdHiredate = new java.sql.Date(time);
// 3行把代码改为批处理
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BatchSample {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection("root", "123456");
conn.setAutoCommit(false);
String sql = "insert into employee (eno, ename, salary, dname) value (?, ?, ?, ?)";
// 1.将 PreparedStatement 解析 sql 语句放到循环外执行
pstmt = conn.prepareStatement(sql);
for (int i = 1000; i < 2000; i++) {
pstmt.setInt(1, i);
pstmt.setString(2, "员工" + i);
pstmt.setFloat(3, 4000f);
pstmt.setString(4, "市场部");
// 2.把pstmt添加到批处理的任务中
pstmt.addBatch();
}
// 3.执行批处理任务
pstmt.executeBatch();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
if (conn != null && !conn.isClosed()){
conn.rollback();
}
} catch (SQLException ex) {
e.printStackTrace();
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
}
Druid是阿里巴巴开源连接池组件,是最好的连接池之一
Druid对数据库连接进行有效管理与重用,最大化程序执行效率
连接池负责创建管理连接,程序只负责取用与归还
在应用程序启动的时候连接池的连接被初始化并创建
https://github.com/alibaba/druid
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username=root
password=123456
initialSize=10
maxActive=20
- Druid连接池创建代码
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.common.DbUtils;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/**
* Druid连接池配置与使用
*/
public class DruidSample {
public static void main(String[] args) {
// 1. 加载属性文件
Properties properties = new Properties();
// 获取文件路径
System.out.println(DruidSample.class.getResource("/druid-config.properties").getPath());
String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
try {
// getPath()方法会对url进行编码,需要用decode解码为原来的路径字符串
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
// 加载配置文件
properties.load(new FileInputStream(propertyFile));
} catch (Exception e) {
e.printStackTrace();
}
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 2. 获取DataSource数据源 / 数据库对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 3. 创建数据库连接
conn = dataSource.getConnection();
pstmt = conn.prepareStatement("select * from employee limit 0, 10");
rs = pstmt.executeQuery();
while(rs.next()){
//可以写两种参数来得到返回值,columnLabel为字段名,columnIndex为字段索引,从1开始。
Integer eno = rs.getInt(1);
String ename = rs.getString("ename");
Float salary = rs.getFloat("salary");
String dname = rs.getString("dname");
System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
/** 连接关闭的区别
* 不使用连接池: conn.close() 关闭连接
* 使用连接池: conn.close() 将连接回收至连接池
*/
DbUtils.closeConnection(rs, pstmt, conn);
}
}
}
commons-dbutils是Apache提供的开源JDBC工具类库
它是对JDBC的简单封装,学习成本极低
使用commons-dbutils可以极大简化JDBC编码工作量
https://commons.apache.org/proper/commons-dbutils/download_dbutils.cgi
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.hrapp.entity.Employee;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
/**
* Apache DbUtils + Druid联合使用例子
*/
public class DbUtilsSample {
public static void query(){
Properties properties = new Properties();
String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
try {
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
properties.load(new FileInputStream(propertyFile));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 使用DbUtils进行查询操作
// 如果添加dataSource, qr.query方法可以自己创建连接和关闭连接
QueryRunner qr = new QueryRunner(dataSource);
//Object[]{10}用来按顺序存放sql语句中的参数
List<Employee> list = qr.query("select * from employee limit ? , 10", new BeanListHandler<>(Employee.class), new Object[]{10});
for(Employee emp: list){
System.out.println(emp.getEname());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void update(){
Properties properties = new Properties();
String propertyFile = DbUtilsSample.class.getResource("/druid-config.properties").getPath();
Connection conn = null;
try {
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
properties.load(new FileInputStream(propertyFile));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
conn = dataSource.getConnection();
// 使用DbUtils进行查询操作
// 如果自己创建连接则不需要传入dataSource参数
QueryRunner qr = new QueryRunner();
String sql1 = "update employee set salary=salary+1000 where eno=?";
String sql2 = "update employee set salary=salary-500 where eno=?";
qr.update(conn,sql1, new Object[]{1000});
qr.update(conn,sql2, new Object[]{1001});
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch(SQLException e){
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
query();
update();
}
}
注: 可用 show full processlist 命令查看当前MySQL数据库的连接情况。