整体介绍
◆JDBC快速入门 ◆精讲JDBC开发细节 ◆连接池与DBC进阶使用
主要知识点
◆JDBC使用步骤 ◆数据库查询方法 ◆数据库写入方法 ◆SQL注入攻击的应对 ◆连接池的使用 Apache Commons DBUtils
JDBC快速入门
什么是JDBC
◆Java数据库连接-Java DataBase Connectivity ◆JDBC可让Java通过程序操作关系型数据库 ◆JDBC基于驱动程序实现与数据库的连接与操作
什么是驱动程序
驱动是每个厂商自己提供的,主要主板内安装了相应驱动,才能识别usb口插入的是什么设备
为啥我们插上设备就能用,没有安装驱动,那是windows内置了很多常见设备的驱动,不用我们自己安装,当使用高级设备如具备宏命令的鼠标时,就需要在相应的官网安装下载相应驱动了
JDBC驱动程序
JAVA无法为每种数据库提供对应的接口,只能提供统一接口JDBC,让每种数据库使用对应的厂商驱动即jar包进行对接访问,JDBC就能通过这个驱动识别数据库并对其访问操作了平台提供标准,厂商提供实现
JDBC的优点
◆统一的API,提供一致的开发过程 ◆易于学习,容易上手,代码结构稳定 ◆功能强大,执行效率高,可处理海量数据
快速上手JDBC
JDBC开发流程
1.加载并注册JDBC驱动 2.创建数据库连接 3.创建Statement对象 4.遍历查询结果 5.关闭连接,释放资源
准备:在mysql的HL连接下新建imooc数据库,或者导入imooc.sql文件
新建java类项目文件
浏览器搜索”所需驱动名“+ driver ,在各个厂商官网即可找到驱动 mysql jar
创建lib文件夹,将mysql的驱动jar包复制粘贴进去 在工程结构中,选择模块→依赖→’+‘号→JAR
1.加载并注册JDBC驱动
//1. 加载并注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
- 创建数据库连接
//2. 创建数据库连接
Connection conn = null;
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
- 创建Statement对象
//3. 创建Statement对象
stmt = conn.createStatement();
//结果集
System.out.println("select * from employee where dname='" + pdname + "'");
rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
- 遍历查询结果
//4. 遍历查询结果
//rs.next()返回布尔值,代表是否存在下一条记录
//如果有,返回true,同时结果集提取下一条记录
//如果没有,返回false,循环就会停止
while (rs.next()) {
Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
String ename = rs.getString("ename");
Float salary = rs.getFloat("salary");
String dname = rs.getString("dname");
System.out.println(dname + "-" + eno + "-" + ename + "-" + salary);
}
- 关闭连接,释放资源
try{
前面四步
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//5. 关闭连接,释放资源
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(stmt != null){
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null && !conn.isClosed() ) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
注意:此处我的数据库的密码为空,所以设置如下:
//2. 创建数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "");
创建数据库连接代码
Class.forName的作用
◆Class.forName用于加载指定的JDBC驱动类 ◆Class.forName本质是通知JDBC注册这个驱动类 ◆驱动由数据库厂商自行开发,连接字符串也不同 数据库存储非常重要,一般需要采购服务器进行存储,所以有对应ip和端口号,所以通过连接字符串进行表达
数据库与连接字符串
数据库连接配置
DriverManager
◆DriverManager用于注册/管理JDBC驱动程序 ◆DriverManager.getConnection(连接字符串,用户名,密码) ◆返回值Connection对象,对应数据库的物理网络连接
Connection对象
◆Connection对象用于DBC与数据库的网络通信对象 ◆java.sql.Connection是一个接口,具体由驱动厂商实现 ◆所有数据库的操作都建立在Connection基础上
可以不只建立一个connection对象连接数据库服务器
形成连接池,当JDBC连接服务器如何知道ip和端号以及哪个数据库?
这时需要连接字符串
MySQL连接字符串
◆格式:jdbc:mysql:/[主机ip][:端口]/数据库名?参数列表 ◆主机ip与端口是可选设置,默认值为127.0.0.1与3306 ◆参数列表采用u编码,格式:参数1=值1&参数2=值2&…
常用参数
局域网开发可以将useSSL设置为false,部署上线时最好设置为true
若工作时发现字符串没有包含其中某些字段,可能说明服务器端已经默认设置好了
创建connection和异常处理
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionSample {
public static void main(String[] args) {
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";
Connection conn = DriverManager.getConnection(url,"user","password");
System.out.println(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
allowPublicKeyRetrieval=true
表示允许从客户端获取公钥加密传输,在生产开发环境中可以设置user为root,但是生产部署中不要对外开放root,因为权限太大,一旦密码泄露,后果不堪设想
记得选中JDBC五步骤,点击code→surround with→try/catch可以自动捕获异常
实现按部门查询员工功能
使用接口进行封装,新建Command.java接口
package com.imooc.jdbc.hrapp.command;
public interface Command {
public void execute();
}
新建HumanResourceApplication.java文件,是app功能
package com.imooc.jdbc.hrapp;
import com.imooc.jdbc.hrapp.command.*;
import java.util.Scanner;
public class HumanResourceApplication {
public static void main(String[] args) {
System.out.println("1-查询部门员工");
Scanner in = new Scanner(System.in);
Integer cmd = in.nextInt();
Command command = null;
switch (cmd){
case 1://查询部门员工
command = new PstmtQueryCommand();
command.execute();
break;
//后续增加增删查改功能
}
}
}
新建QueryCommand.java文件,实现查询功能,实现上面的接口
package com.imooc.jdbc.hrapp.command;
import java.sql.*;
import java.util.Scanner;
/**
* 数据查询方法
*/
public class QueryCommand implements Command {
@Override
public void execute() {
System.out.print("请输入部门名称:");
Scanner in = new Scanner(System.in);
String pdname = in.nextLine();
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1. 加载并注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 创建数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
//3. 创建Statement对象
stmt = conn.createStatement();
//结果集
System.out.println("select * from employee where dname='" + pdname + "'");
rs = stmt.executeQuery("select * from employee where dname='" + pdname + "'");
//4. 遍历查询结果
//rs.next()返回布尔值,代表是否存在下一条记录
//如果有,返回true,同时结果集提取下一条记录
//如果没有,返回false,循环就会停止
while (rs.next()) {
Integer eno = rs.getInt(1);//JDBC中字段索引从1开始,而非0
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) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//5. 关闭连接,释放资源
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(stmt != null){
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null && !conn.isClosed() ) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
rs里面有一系列针对字段的方法,根据字段的数据类型进行选择 JDBC中字段索引从1开始,而非0 代码有非常严重的内存泄露风险,导致可以进行sql注入攻击
SQL注入攻击
输入特殊格式字符从而嵌入到原有sql中破环原有sql筛选条件导致数据破坏泄露的攻击方式称之为sql注入攻击
根本原因是我们没有考虑处理单引号这样的特殊字符
PreparedStatement预编译SQL
用以解决sql注入攻击问题
SQL注入
◆SQL注入攻击是指利用SQL漏洞越权获取数据的黑客行为 ◆SQL注入攻击根源是未对原始SQL中的敏感字符做特殊处理 解决方法:放弃Statement改用PreparedStatement处理SQL
PreparedStatement
◆PreparedStatement预编译Statement是Statement的子接口 ◆PreparedStatement对SQL进行参数化,预防SQL注入攻击 ◆PreparedStatement比Statement执行效率更高
使用PreparedStatement
符号会进行转义处理
//3. 创建PreparedStatement对象
String sql = "select * from employee where dname=? and eno > ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1,pdname); //注意:参数索引从1
pstmt.setInt(2,3500);
使用?占位,setString、setInt传入输入信息
JDBC实现写数据
新增、修改、删除数据
封装DbUtils工具类
将JDBC的重复五个步骤封装 实现打开和关闭两个接口 在文件下新建common文件夹,新增DbUtils。java类 实行封装,抛出异常:
package com.imooc.jdbc.common;
import java.sql.*;
public class DbUtils {
/**
* 创建新的数据库连接
* @return 新的Connection对象
* @throws SQLException
* @throws ClassNotFoundException
*/
public static Connection getConnection() throws SQLException, ClassNotFoundException {
//1. 加载并注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 创建数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true", "root", "root");
return conn;
}
/**
* 关闭连接,释放资源
* @param rs 结果集对象
* @param stmt Statement对象
* @param conn Connection对象
*/
public static void closeConnection(ResultSet rs , Statement stmt , Connection conn){
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(stmt != null){
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn != null && !conn.isClosed() ) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
JDBC实现新增数据
JDBC执行INSERT语句
所有写操作都要使用execute.Update方法,而且不返回结果集,而是整数,表示写操作所影响行数
使用DbUtils封装工具类来实现新增数据操作
package com.imooc.jdbc.hrapp.command;
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Scanner;
/**
* 新增员工数据
*/
public class InsertCommand implements Command{
@Override
public void execute() {
Scanner in = new Scanner(System.in);
System.out.print("请输入员工编号:");
int eno = in.nextInt();
System.out.print("请输入员工姓名:");
String ename = in.next();
System.out.print("请输入员工薪资:");
float salary = in.nextFloat();
System.out.print("请输入隶属部门:");
String dname = in.next();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection();
String sql = "insert into employee(eno,ename,salary,dname,hiredate ) values(?,?,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, eno);
pstmt.setString(2, ename);
pstmt.setFloat(3, salary);
pstmt.setString(4,dname);
int cnt = pstmt.executeUpdate();//所有写操作都使用executeUpdate
System.out.println("cnt:" + cnt);
System.out.println(ename + "员工入职手续已办理");
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
DbUtils.closeConnection(null,pstmt,conn);
}
}
}
调用封装的接口conn = DbUtils.getConnection();
,表示打开JDBC连接mysql数据库,使用四个?
进行占位,然后后面使用一系列set方法传入值,最后记得调用关闭接口进行资源释放DbUtils.closeConnection(null,pstmt,conn);
,没有生成结果集,所以使用null
实现了第二个APP的功能:
package com.imooc.jdbc.hrapp;
import com.imooc.jdbc.hrapp.command.*;
import java.util.Scanner;
public class HumanResourceApplication {
public static void main(String[] args) {
System.out.println("1-查询部门员工");
System.out.println("2-办理员工入职");
System.out.println("请选择功能:");
Scanner in = new Scanner(System.in);
Integer cmd = in.nextInt();
Command command = null;
switch (cmd){
case 1://查询部门员工
command = new PstmtQueryCommand();
command.execute();
break;
case 2:
command = new InsertCommand();
command.execute();
break;
}
}
}
JDBC实现更新与删除数据
JDBC执行UPDATE语句
JDBC执行DELETE语句
此处cnt的值为0或1,0为不存在,1为存在删除
更新操作:
package com.imooc.jdbc.hrapp.command;
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.print("请输入员工编号:");
int eno = in.nextInt();
System.out.print("请输入员工新的薪资:");
float salary = in.nextFloat();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection();
String sql = "update employee set salary=? where eno=?";
pstmt = conn.prepareStatement(sql);
pstmt.setFloat(1, salary);
pstmt.setInt(2, eno);
int cnt = pstmt.executeUpdate();
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);
}
}
}
删除操作:
package com.imooc.jdbc.hrapp.command;
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.print("请输入员工编号:");
int eno = in.nextInt();
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection();
String sql = "delete from employee where eno = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setFloat(1, eno);
int cnt = pstmt.executeUpdate();
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);
}
}
}
APP的增删查改功能
package com.imooc.jdbc.hrapp;
import com.imooc.jdbc.hrapp.command.*;
import java.util.Scanner;
public class HumanResourceApplication {
public static void main(String[] args) {
System.out.println("1-查询部门员工");
System.out.println("2-办理员工入职");
System.out.println("3-调整薪资");
System.out.println("4-员工离职");
System.out.println("请选择功能:");
Scanner in = new Scanner(System.in);
Integer cmd = in.nextInt();
Command command = null;
switch (cmd){
case 1://查询部门员工
command = new PstmtQueryCommand();
command.execute();
break;
case 2:
command = new InsertCommand();
command.execute();
break;
case 3:
command = new UpdateCommand();
command.execute();
break;
case 4:
command = new DeleteCommand();
command.execute();
break;
}
}
}
JDBC中的事务操作
什么是事务
◆事务是以一种可靠的、一致的方式,访问和操作数据库的程序单元 ◆说人话:要么把事情做完,要么什么都不做,不要做一半 ◆事务依赖于数据库实现,MySQL通过事务区作为数据缓冲地带
事务的提交操作
案例:韩梅梅向李雷借了100元,韩梅梅余额加100,李雷余额减100,这是一个完整事务,我们不需要两次写入到数据表,我们可以不先更新到数据表,而更新到事务区,等到韩梅梅与李雷的数据全部修改后,再一次性写入到数据表,然后事务区清空,等待下一次事务
事物的回滚操作
回滚后不会对数据表产生任何影响
JDBC两种事务模式
◆JDBC允许两种事务模式 ◆自动提交事务模式 ◆手动提交事务模式
自动提交事务模式
◆自动提交模式是指每执行一次写操作SQL,自动提交事务 ◆自动提交开启方法:conn.setAutoCommit(true) ◆自动事务是DBC默认行为,此模式无法保证多数据一致性
手动提交事务模式
◆手动提交模式是指显式调用commit0与rollback()方法管理事务 ◆手动提交开启方法:conn.setAutoCommit(false) ◆手动提交事务可保证多数据一致性,但必须手动调用提交/回滚方法
package com.imooc.jdbc.sample;
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* JDBC中的事务控制
*/
public class TransactionSample {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection();
//JDBC默认使用自动提交模式
String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
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();
}
我们想要插入1000条员工数据,当执行到1005时会产生异常,这时我们想要撤回已经写入的5条数据并修改后重新再次提交到数据表,他是无法完成的,导致数据不完整,不因为是默认自动提交,所以我们需要使用事务控制
这时我们需要关闭自动提交,执行executeUpdate时,就会提交到事务区 ,需要自己手动提交,出现异常,自动回滚
package com.imooc.jdbc.sample;
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* JDBC中的事务控制
*/
public class TransactionSample {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = DbUtils.getConnection();
//JDBC默认使用自动提交模式
conn.setAutoCommit(false);//关闭自动提交
String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
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){
ex.printStackTrace();
}
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
}
如果插入失败后,一条数据也不会写入到数据表中,必须全部成功才会写入数据表中
基于实体类实现分页数据封装
数据的处理不单是打印到屏幕这们简单,在日常开发中,通常将数据转化成一个个实体封装在集合中,即使结果集关闭,也不会影响到我们封装的数据,而且是通过面向对象的方法进行存储,后期更容易被我们的JAVA进行处理
员工实体类构造方法:
package com.imooc.jdbc.hrapp.entity;
import java.util.Date;
/**
* 员工实体类
*/
public class Employee {
/**
* 1. 具备默认构造函数
* 2. 属性私有
* 3. 存在getter与setter
*/
public Employee(){
}
private Integer eno;
private String ename;
private Float salary;
private String dname;
private Date hiredate;
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;
}
public Date getHiredate() {
return hiredate;
}
public void setHiredate(Date hiredate) {
this.hiredate = hiredate;
}
}
通常属性与mysql字段一致 进行分页查询操作: 新建PaginationCommond.java文件,类似上面的增删查改方式,实现Commond的接口
package com.imooc.jdbc.hrapp.command;
import com.imooc.jdbc.common.DbUtils;
import com.imooc.jdbc.hrapp.entity.Employee;
import java.sql.*;
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();
String sql = "select * from employee limit ?,10";
pstmt = conn.prepareStatement(sql);
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");
Employee emp = new Employee();
emp.setEno(eno);
emp.setEname(ename);
emp.setSalary(salary);
emp.setDname(dname);
emp.setHiredate(hiredate);
list.add(emp);
}
System.out.println(list.size());
} catch (Exception e){
e.printStackTrace();
} finally {
DbUtils.closeConnection(rs,pstmt,conn);
}
}
}
每页10条数据,依托mysql独有方言特殊关键字limit,?表示从第几行开始,后面数字表示截取后面的几行记录 注意mysql页数索引从0开始
JDBC中Date日期对象处理
新增字段hiredate字段,默认date类型,保持到日期的天数 注意选择为java.util下的包
//JDBC获取日期使用java.sql.Date,其继承自java.util.Date
//所以两者互相兼容
Date hiredate = rs.getDate("hiredate");
System.out.print("请输入入职日期:");
String strHiredate = in.next();
//String到java.sql.Date分为两步
//1.String转为java.util.Date
java.util.Date udHiredate = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
udHiredate = sdf.parse(strHiredate);
} catch (ParseException e) {
e.printStackTrace();
}
//2.java.util.Date转为java.sql.Date
long time = udHiredate.getTime();//获取自1970年到现在的毫秒数
java.sql.Date sdHiredate = new java.sql.Date(time);
JDBC批处理
package com.imooc.jdbc.sample;
import com.imooc.jdbc.common.DbUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
/**
* JDBC批处理
*/
public class BatchSample {
//标准方式插入若干数据
private static void tc1(){
Connection conn = null;
PreparedStatement pstmt = null;
try {
long startTime = new Date().getTime();
conn = DbUtils.getConnection();
//JDBC默认使用自动提交模式
conn.setAutoCommit(false);//关闭自动提交
String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
for (int i=100000;i<200000;i++){
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, i);
pstmt.setString(2, "员工" + i);
pstmt.setFloat(3, 4000f);
pstmt.setString(4, "市场部");
pstmt.executeUpdate();
}
conn.commit();//提交数据
long endTime = new Date().getTime();
System.out.println("tc1()执行时长:" + (endTime-startTime));
} catch (Exception e) {
e.printStackTrace();
try {
if(conn != null && !conn.isClosed()) {
conn.rollback();//回滚数据
}
}catch (SQLException ex){
ex.printStackTrace();
}
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
//使用批处理插入若干数据
private static void tc2(){
Connection conn = null;
PreparedStatement pstmt = null;
try {
long startTime = new Date().getTime();
conn = DbUtils.getConnection();
//JDBC默认使用自动提交模式
conn.setAutoCommit(false);//关闭自动提交
String sql = "insert into employee(eno,ename,salary,dname) values(?,?,?,?)";
pstmt = conn.prepareStatement(sql);
for (int i=200000;i<300000;i++){
pstmt.setInt(1, i);
pstmt.setString(2, "员工" + i);
pstmt.setFloat(3, 4000f);
pstmt.setString(4, "市场部");
pstmt.addBatch();//将参数加入批处理任务
// pstmt.executeUpdate();
}
pstmt.executeBatch();//执行批处理任务
conn.commit();//提交数据
long endTime = new Date().getTime();
System.out.println("tc2()执行时长:" + (endTime-startTime));
} catch (Exception e) {
e.printStackTrace();
try {
if(conn != null && !conn.isClosed()) {
conn.rollback();//回滚数据
}
}catch (SQLException ex){
ex.printStackTrace();
}
} finally {
DbUtils.closeConnection(null, pstmt, conn);
}
}
public static void main(String[] args) {
tc1();
tc2();
}
}
tc1()是新增100000个对象,每个对象新增1个数据 tc2()是创建一个对象,新增100000条数据
tc1()创建10万个prepareStatement(sql)
,而tc2()只需创建一个,发送一条sql语句,覆盖10万个参数,再由mysql一次性进行批处理,相反tc1()需要发送10万条sql语句,每一条都相同。浪费了时间
连接池和JDBC的进阶使用
连接池类似生活中的物资仓库,每使用一次mysql就要创建一个JDBC的连接,耗费时间,将连接预算放入连接池,直接在连接池提取使用即可,用完可以还到连接池,继续分配他人使用
阿里巴巴Druid连接池
◆Duid是阿里巴巴开源连接池组件是最好的连接池之一 ◆Duid对数据库连接进行有效管理与重用,最大化程序执行效率 ◆连接池负责创建管理连接,程序只负责取用与归还
JDBC程序一假设为负责数据的新增,JDBC程序二假设负责数据的查找,都需要与数据库建立连接,耗费时间,我们需要建立一个连接池,让JDBC与连接池通信即可,连接池可以在程序启动时创建,也可以在第一个JDBC与连接池通信时创建,其它JDBC分配即可
Druid连接池的配置与使用
alibaba/druid: 阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品,为监控而生的数据库连接池 (github.com) 类似mysql的jar包导入方法,将druid的jar包导入lib目录 src下创建druid-config.properties进行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=********
initialSize=10
maxActive=20
initialSize=10
表示连接池创建时默认创建10个连接,maxActive=20
表示如果实际过程不够用,连接池会额外创建10个连接,即最多不大于20个,如果运行过程中达到上限,就会处于等待状态造成阻塞,等待其他使用的程序调用close()方法,将连接池回收
一般将这两个数值设为一致数值,一开始就分配好所有资源,提升性能和管理
执行完成后会全部消失,回收到连接池
新建DruidSample.java文件
package com.imooc.jdbc.sample;
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();
String propertyFile = DruidSample.class.getResource("/druid-config.properties").getPath();
//空格->%20 | c:\java code\druid-config.properties
//c:\java%20code\druid-config.properties
try {
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()) {
Integer empId = rs.getInt(1);
String ename = rs.getString("ename");
String dname = rs.getString("dname");
Float salary = rs.getFloat("salary");
System.out.println(dname + "-" + empId + "-" + ename + "-" + salary);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
/**
* 不使用连接池:conn.close()关闭连接
* 使用连接池:conn.close()将连接回收至连接池
*/
//之前封装的Dbutils工具
DbUtils.closeConnection(rs,pstmt,conn);
}
}
配置文件会随着发布到out目录下,所以使用DruidSample.class.getResource("/druid-config.properties").getPath();
获取类路径文件下的文件路径
propertyFile = new URLDecoder().decode(propertyFile, "UTF-8");
是为了容错考虑,防止文件路径中有中文或者空格,使用getpath方法会对路径进行编码,如空格变为%20,decode就是将%20还原成空格
properties.load(new FileInputStream(propertyFile));
需要传入一个文件输入流,所以文件使用new FileInputStream()
进行包裹
Apache Commons DBUtils
◆commons-dbutils是Apache提供的开源JDBC工具类库 ◆它是对DBC的简单封装学习成本极低 ◆使用commons-dbutils可以极大简化JDBC编码工作量 DbUtils – Download Apache Commons DbUtils 继续类似上面方法将jar包添加到lib文件夹
package com.imooc.jdbc.sample;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.imooc.jdbc.common.DbUtils;
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.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
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 {
private 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);
//利用Apache DbUtils大幅简化了数据的提取过程
QueryRunner qr = new QueryRunner(dataSource);
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) {
e.printStackTrace();
}
}
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();
conn.setAutoCommit(false);
String sql1 = "update employee set salary=salary+1000 where eno=?";
String sql2 = "update employee set salary=salary-500 where eno=?";
QueryRunner qr = new QueryRunner();
qr.update(conn,sql1,new Object[]{1000});
qr.update(conn,sql2,new Object[]{1001});
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
if(conn != null && !conn.isClosed()) {
conn.rollback();
}
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
if(conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// query();
update();
}
}
Apache Commons DBUtils没有结果集用来存储数据,所以使用new BeanListHandler<>(Employee.class)
转换为实体类进行存储,要求必须是属性和字段一一对应的上,‘?’的参数的传递使用new Object[]{10}
表示从第十行开始截取,{}中的数值对应’?‘里要传入的数值,使用集合进行接收,使用 for (Employee emp : list)
进行遍历后可以打印或者后续处理
注意,在执行query方法的时候,Apache Commons DBUtils会自动连接连接池,执行完后会自动关闭,不需要我们手动打开和关闭 所以利用Apache DbUtils大幅简化了数据的提取过程
然而在执行非query查询方法的时候,如update更新方法,就需要自己创建与数据库的连接,update不止用于更新操作,加入删除等写操作都须使用update方法