JDBC:是sun发布的一个java程序和数据库之间通信的规范(接口)
各大数据库厂商去实现JDBC规范(实现类),将这些实现类打成压缩包,就是所谓的jar包
比如:
1.2 创建连接url:防止中文乱码,加上参数useUnicode=true&characterEncoding=utf-8。url的标准格式为:jdbc:mysql://ip:端口号/数据库名称?参数列表
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.添加jar包
// 2.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 3.通过驱动管理器获取连接对象
// 3-1 准备url
String url = "jdbc:mysql://localhost:3306/fruitdb";
// 3-2 准备用户名
String usr = "root";
// 3-3 准备密码
String pwd = "root";
Connection connection = DriverManager.getConnection(url,usr,pwd);
System.out.println("conn = "+connection);
}
}
1.3 添加数据
先建立连接之后,得到connection对象,这个对象相当于建立了一条连接数据库和java之间的马路,通过connection对象得到预处理对象PreparedStatement,这个对象相当于马路上的一辆车,用于运送真实的数据。
在SQL语句中,使用?当做占位符,再通过预处理对象的set方法为占位符填充内容
最后,记得关闭连接,先关闭预处理对象,再关闭连接对象
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.通过驱动管理器获取连接对象
// 2-1 准备url
// url表示和数据库通信的地址,如果需要带参数,则需要使用?进行连接
// 如果使用多个参数,多个参数之间使用&连接
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf-8";
// 2-2 准备用户名
String usr = "root";
// 2-3 准备密码
String pwd = "root";
Connection connection = DriverManager.getConnection(url,usr,pwd);
// 3. 编写SQL语句
// id,fname,price,fcount,remark
String sql = "insert into t_fruit value(0,?,?,?,?)";
// 4.创建预处理对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数
psmt.setString(1,"榴莲");
psmt.setInt(2,15);
psmt.setInt(3,100);
psmt.setString(4,"榴莲是一种神奇的水果");
// 6.执行更新(增删改),返回影响行数
int i = psmt.executeUpdate();
System.out.println(i>0?"添加成功!":"添加失败");
// 7.释放资源
psmt.close();
connection.close();
}
}
1.4 更新和删除数据
和添加数据步骤都一样,最后都是预处理对象调用executeUpdate方法并返回影响行数。因此更新与修改和添加的不同就是SQL语句和预处理设置SQL中的占位符的值而已。
更新:
Fruit fruit = new Fruit(33, "猕猴桃", "猕猴桃啊猕猴桃");
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");
String sql = "update t_fruit set fname=?,remark = ? where fid = ?";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setString(1, fruit.getFname());
psmt.setString(2, fruit.getRemark());
psmt.setInt(3,fruit.getFid());
int i = psmt.executeUpdate();
System.out.println(i>0?"修改成功":"修改失败");
psmt.close();
connection.close();
删除:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");
String sql = "delete from t_fruit where fid = ?";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setInt(1,33);
int i = psmt.executeUpdate();
System.out.println(i>0?"删除成功":"删除失败");
psmt.close();
connection.close();
1.5 增删更新总结
1.5.1 步骤:
- 加载驱动
- 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
- 编写SQL语句,语句中使用占位符
- 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
- 填充预处理数据对象的参数
- 执行更新参数,对于增删查都是执行PreparedStatement对象的executeUpdate方法,返回影响行数
- 关闭资源,关闭PreparedStatement对象和Connection对象
查询与增删改的不同之处在于:
- 预处理对象执行的是executeQuery方法
- 方法返回结果集ResultSet,可以遍历结果接进行数据的操作
例如:查询所有数据
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
// 2.获取连接对象
Connection connection = DriverManager.getConnection(url, "root", "root");
// 3.编写SQL语句
String sql = "select * from t_fruit";
// 4.创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数,这里没有,略
// 6.执行查询,返回结果集
ResultSet res = psmt.executeQuery();
List list = new ArrayList<>();
// 7.处理结果集
// 判断下一行是否有数据,并且指针指到下一行
while(res.next()){
// 表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
int fid = res.getInt(1);
// 也可以通过结果集的列名
String fname = res.getString("fname");
int price = res.getInt(3);
int fcount = res.getInt("fcount");
String remark = res.getString("remark");
Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
list.add(fruit);
}
// 8.释放资源
res.close();
psmt.close();
connection.close();
for (Fruit fruit : list) {
System.out.println(fruit);
}
查询指定的一条记录:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");
// 编写SQL语句
String sql = "select * from t_fruit where fid = ?";
// 创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 填充参数
psmt.setInt(1,2);
// 由于fid是主键,所以可以不使用列表,这里改一下
// 执行查询,返回结果集
ResultSet res = psmt.executeQuery();
// 判断下一行是否有数据,并且指针指到下一行
if(res.next()){
// 表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
int fid = res.getInt(1);
// 也可以通过结果集的列名
String fname = res.getString("fname");
int price = res.getInt(3);
int fcount = res.getInt("fcount");
String remark = res.getString("remark");
Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
System.out.println(fruit);
}
// 释放资源
res.close();
psmt.close();
connection.close();
查询总记录条数:
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");
// 编写SQL语句
String sql = "select count(*) from t_fruit";
// 创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 执行查询,返回结果集
// 这里返回的是一个一行一列的数据
ResultSet res = psmt.executeQuery();
// 判断下一行是否有数据,并且指针指到下一行
if(res.next()){
int count = res.getInt(1);
System.out.println("总记录条数:"+count);
}
// 释放资源
res.close();
psmt.close();
connection.close();
1.7 查询总结
1.7.1 查询与增删改的不同之处在于:
- 预处理对象执行的是executeQuery方法
- 方法返回结果集ResultSet,可以遍历结果接进行数据的操作
- 加载驱动
- 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
- 编写SQL语句,语句中使用占位符
- 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
- 填充预处理数据对象的参数
- 执行更新参数,执行executeQuery方法
- 返回结果集ResultSet
- 关闭资源,关闭ResultSet对象、PreparedStatement对象和Connection对象
如果一次性添加大量数据,若每一条数据都单独执行一次方法,那么效率很低下,所以可以采用批处理的方法来提高处理的效率。
批处理操作的步骤:
- 如果执行批处理任务,需要在添加一个参数: rewriteBatchedStatements=true
- 将数据加入一个批次:psmt.addBatch();
- 执行批处理:int[] batch = psmt.executeBatch();如果继续进行处理,需要进行clear:psmt.clear();
package com.zylai.jdbc01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
public class DemoAddBatch {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.通过驱动管理器获取连接对象
// 批处理操作一
// 如果执行批处理任务,需要添加一个参数: rewriteBatchedStatements=true
String url = "jdbc:mysql://localhost:3306/fruitdb?rewriteBatchedStatements=true&useSSL=false&useUnicode=true&characterEncoding=utf-8";
// 2-2 准备用户名
String usr = "root";
// 2-3 准备密码
String pwd = "root";
Connection connection = DriverManager.getConnection(url,usr,pwd);
// 3. 编写SQL语句
String sql = "insert into t_fruit value(0,?,?,?,?)";
// 4.创建预处理对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数
for (int i = 0; i < 1000; i++) {
psmt.setString(1,"榴莲"+i);
psmt.setInt(2,15);
psmt.setInt(3,100);
psmt.setString(4,"榴莲是一种神奇的水果"+i);
// 批处理操作二:将数据加入一个批次
psmt.addBatch();
// 每一百个处理一次,分批次处理,每次执行完要记得清空一下队列
if(i%100==0){
psmt.executeBatch();
// 记得clear一下
psmt.clearBatch();
}
}
// 批处理操作三
int[] batch = psmt.executeBatch();
System.out.println(Arrays.toString(batch));
// 7.释放资源
psmt.close();
connection.close();
}
}
2 工程
2.1 需求概述和整体框架
以实现一个水果库存系统为例,这个系统需要提供水果的增删改查操作
=================欢迎使用水果库存系统===================== 1.查看水果库存列表 2.添加水果库存信息 3.查看特定水果库存信息 4.水果下架 5.退出 ======================================================
整体框架如下图,在controller里面写调用DAO层的方法,在dao里面写持久层方法,提供接口及其实现类,pojo写实体类,view是客户端视图操作。
2.2 最初版本 2.2.1 pojopojo包下的Fruit类
package com.zylai.fruit01.pojo;
public class Fruit {
private Integer fid;
private String fname;
private Integer price;
private Integer fcount;
private String remark;
public Fruit(){}
public Fruit(Integer fid, String fname, String remark) {
this.fid = fid;
this.fname = fname;
this.remark = remark;
}
public Fruit(Integer fid, String fname, Integer price, Integer fcount, String remark) {
this.fid = fid;
this.fname = fname;
this.price = price;
this.fcount = fcount;
this.remark = remark;
}
@Override
public String toString() {
return fid+"tt"+fname+"tt"+price+"tt"+fcount+"tt"+remark;
}
public Integer getFid() {
return fid;
}
public void setFid(Integer fid) {
this.fid = fid;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public Integer getPrice() {
return price;
}
public void setPrice(Integer price) {
this.price = price;
}
public Integer getFcount() {
return fcount;
}
public void setFcount(Integer fcount) {
this.fcount = fcount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
2.2.2 dao
FruitDAO接口
package com.zylai.fruit01.dao;
import com.zylai.fruit01.pojo.Fruit;
import java.util.List;
public interface FruitDAO {
// 查询库存列表
List getFruitList();
// 根据水果名称查询库存
Fruit getFruitByName(String fname);
// 新增水果库存
boolean addFruit(Fruit fruit);
// 修改库存
boolean updateFruit(Fruit fruit);
// 根据水果名称删除记录
boolean deleteFruitByName(String fname);
}
FruitDAOImpl实现类
注意到这个实现类存在着大量的代码冗余,之后的优化主要对这个类进行优化
package com.zylai.fruit01.dao.impl;
import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.pojo.Fruit;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class FruitDAOImpl implements FruitDAO {
private final String DRIVER = "com.mysql.jdbc.Driver";
private final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
private final String USER = "root";
private final String PWD = "root";
// 查询所有的库存信息
@Override
public List getFruitList() {
List fruitList = new ArrayList<>();
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
connection= DriverManager.getConnection(URL, USER, PWD);
// 3.编写SQL语句
String sql = "select * from t_fruit";
// 4.创建预处理命令对象
psmt = connection.prepareStatement(sql);
// 5.执行查询
rs = psmt.executeQuery();
// 6.解析查询
while(rs.next()){
int fid = rs.getInt(1);
String fname = rs.getString(2);
int price = rs.getInt(3);
int fcount = rs.getInt(4);
String remark = rs.getString(5);
Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
fruitList.add(fruit);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return fruitList;
}
// 通过水果名称查询记录
@Override
public Fruit getFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "select * from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
rs= psmt.executeQuery();
if(rs.next()){
int fid = rs.getInt("fid");
int price = rs.getInt("price");
int fcount = rs.getInt("fcount");
String remark = rs.getString("remark");
return new Fruit(fid,fname,price,fcount,remark);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try{
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
// 添加水果
@Override
public boolean addFruit(Fruit fruit) {
Connection connection = null;
PreparedStatement psmt = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "insert into t_fruit values(0,?,?,?,?)";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fruit.getFname());
psmt.setInt(2,fruit.getPrice());
psmt.setInt(3,fruit.getFcount());
psmt.setString(4,fruit.getRemark());
int i = psmt.executeUpdate();
return i>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}
// 更新水果库存信息
@Override
public boolean updateFruit(Fruit fruit) {
Connection connection = null;
PreparedStatement psmt = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1, fruit.getFname());
psmt.setInt(2,fruit.getPrice());
psmt.setInt(3,fruit.getFcount());
psmt.setString(4,fruit.getRemark());
psmt.setInt(5,fruit.getFid());
int i = psmt.executeUpdate();
return i>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}
// 删除水果
@Override
public boolean deleteFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "delete from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
return psmt.executeUpdate()>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}
}
2.2.3 controller
处理请求层,由于业务比较简单,所以一些业务都在controller里面进行
package com.zylai.fruit01.controller;
import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.dao.impl.FruitDAOImpl;
import com.zylai.fruit01.pojo.Fruit;
import java.util.List;
import java.util.Scanner;
public class Menu {
Scanner input = new Scanner(System.in);
private FruitDAO fruitDAO = new FruitDAOImpl();
public int showMainMenu(){
System.out.println("=================欢迎使用水果库存系统=====================");
System.out.println("1.查看水果库存列表");
System.out.println("2.添加水果库存信息");
System.out.println("3.查看特定水果库存信息");
System.out.println("4.水果下架");
System.out.println("5.退出");
System.out.println("======================================================");
System.out.print("请选择:");
int res = input.nextInt();
while(true){
if(res<1||res>5){
System.out.println("请在1~5当中进行选择!");
System.out.print("请选择:");
res = input.nextInt();
}else{
break;
}
}
return res;
}
// 显示所有水果库存
public void showFruitList(){
List fruitList = fruitDAO.getFruitList();
System.out.println("------------------------------------------------------");
System.out.println("编号tt名称tt单价tt库存tt备注tt");
if(fruitList==null||fruitList.size()<=0){
System.out.println("对不起,库存为空");
}else{
for (Fruit fruit : fruitList) {
System.out.println(fruit);
}
}
System.out.println("------------------------------------------------------");
}
// 添加水果库存信息 -- 业务方法
public void addFruit(){
System.out.print("请输入水果名称:");
String fname = input.next();
// 判断是添加库存还是添加一个新水果
Fruit fruitByName = fruitDAO.getFruitByName(fname);
if(fruitByName==null){//说明库存中没有这个水果,直接添加
System.out.print("请输入水果单价:");
int price = input.nextInt();
System.out.print("请输入水果库存量:");
int fcount = input.nextInt();
System.out.print("请输入水果备注:");
String remark = input.next();
fruitByName = new Fruit(0,fname,price,fcount,remark);
// 调用dao
fruitDAO.addFruit(fruitByName);
}else{
System.out.print("请输入追加的库存量:");
int fcount = input.nextInt();
fruitByName.setFcount(fruitByName.getFcount()+fcount);
fruitDAO.updateFruit(fruitByName);
}
}
// 查看特定水果库存信息
public void showFruitInfo(){
System.out.print("请输入水果名称:");
String fname = input.next();
Fruit fruit = fruitDAO.getFruitByName(fname);
if(fruit == null){
System.out.println("对不起,没有找到指定的水果库存记录");
}else{
System.out.println("------------------------------------------------------");
System.out.println("编号tt名称tt单价tt库存tt备注tt");
System.out.println(fruit);
System.out.println("------------------------------------------------------");
}
}
public void deleteFruit(){
System.out.print("请输入水果名称:");
String fname = input.next();
Fruit fruit = fruitDAO.getFruitByName(fname);
if(fruit==null){
System.out.println("对不起,没有找到要下架的水果信息");
}else{
System.out.print("是否下架?(Y/N)");
String str = input.next();
if("y".equalsIgnoreCase(str)){
boolean b = fruitDAO.deleteFruitByName(fname);
if(b){
System.out.println("下架成功!");
}
}
}
}
// 退出
public boolean exit(){
do{
System.out.print("是否确认退出?(Y/N):");
String res = input.next();
if("y".equalsIgnoreCase(res)){
return true;
}else if("n".equalsIgnoreCase(res)){
return false;
}
}while(true);
}
}
2.2.4 view
package com.zylai.fruit01.view;
import com.zylai.fruit01.controller.Menu;
public class Client {
public static void main(String[] args) {
Menu menu = new Menu();
boolean flag = false;
while(!flag){
// 调用主菜单的方法
int slt = menu.showMainMenu();
switch(slt){
case 1:
menu.showFruitList();
break;
case 2:
menu.addFruit();
break;
case 3:
menu.showFruitInfo();
break;
case 4:
menu.deleteFruit();
break;
case 5:
flag = menu.exit();
break;
}
}
System.out.println("谢谢使用");
}
}
2.2.5 最初版总结
代码的冗余量很大,主要是dao层的实现类,每个方法都要重复建立驱动、获取连接、关闭资源等操作。
2.3 改进:对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取这次的改进就是对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取
如下:将加载驱动获取连接封装在getConnection方法中,将关闭资源封装在close方法中
private Connection getConnection(){
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
private void close(Connection connection,PreparedStatement psmt, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//实际中使用
// 通过水果名称查询记录
@Override
public Fruit getFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
connection = getConnection();
String sql = "select * from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
rs= psmt.executeQuery();
if(rs.next()){
int fid = rs.getInt("fid");
int price = rs.getInt("price");
int fcount = rs.getInt("fcount");
String remark = rs.getString("remark");
return new Fruit(fid,fname,price,fcount,remark);
}
} catch ( SQLException e) {
e.printStackTrace();
}finally {
close(connection,psmt,rs);
}
return null;
}
2.4 改进:设置抽象类,并抽取增删改操作
可以看到下面这个三个操作的步骤完全一样,不一样的地方只是SQL语句和psmt设置的参数
因此可以将这些相同的操作抽取出来
-
addFruit:
1.获取连接
2.编写SQL
3.psmt
4.执行更新
5.关闭资源 -
updateFruit:
1.获取连接
2.编写SQL
3.psmt
4.执行更新
5.关闭资源 -
deleteFruit
1.获取连接
2.编写SQL
3.psmt
4.执行更新
5.关闭连接
此外,这里只是涉及到一个表的操作,当有多个表时,就涉及到多个dao类,而这多个dao类都需要这共同的驱动地址,数据库url,用户名,密码,获取驱动方法,关闭资源的方法,抽取增删改操作方法。
因此,加入一个抽象类baseDAO,里面提供公共的常量和方法,并且把抽取的增删改方法加入
package com.zylai.fruit.dao.base;
import java.sql.*;
public abstract class BaseDAO {
protected final String DRIVER = "com.mysql.jdbc.Driver";
protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
protected final String USER = "root";
protected final String PWD = "root";
protected Connection getConnection(){
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
// 关闭资源
protected void close(Connection connection, PreparedStatement psmt, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 执行更新,返回影响行数
protected int executeUpdate(String sql,Object... params){
Connection connection = null;
PreparedStatement psmt = null;
try {
connection = getConnection();
psmt = connection.prepareStatement(sql);
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
psmt.setObject(i+1,params[i]);
}
}
return psmt.executeUpdate();
} catch ( SQLException e) {
e.printStackTrace();
}finally {
close(connection,psmt,null);
}
return 0;
}
}
然后让daoImpl继承该抽象类
由于没有涉及到查询的改进,这里省去查询的方法。可以看到对于增删改方法,只需要调用父类的更新执行方法,传入sql语句和参数即可完成业务。
package com.zylai.fruit.dao.impl;
import com.zylai.fruit.dao.FruitDAO;
import com.zylai.fruit.dao.base.BaseDAO;
import com.zylai.fruit.pojo.Fruit;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class FruitDAOImpl extends BaseDAO implements FruitDAO {
// 添加水果
@Override
public boolean addFruit(Fruit fruit) {
String sql = "insert into t_fruit values(0,?,?,?,?)";
return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark())>0;
}
// 更新水果库存信息
@Override
public boolean updateFruit(Fruit fruit) {
String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark(),fruit.getFid())>0;
}
// 删除水果
@Override
public boolean deleteFruitByName(String fname) {
String sql = "delete from t_fruit where fname=?";
return super.executeUpdate(sql,fname)>0;
}
}
2.5 改进:抽取查询操作
2.5.1 使用泛型
通过执行查询操作返回查询的结果集,然后通过get方法得到指定列的值赋值给实体类比如Fruit,而对于baseDAO会有多个DAOImpl继承它,因此不能把实体类写死,所以采用泛型。
public abstract class BaseDAO2.5.2 通过反射得到真正的类{} public class FruitDAOImpl extends BaseDAO implements FruitDAO {}
因为泛型只是一个符号,我们不能直接通过泛型创建实例,所以用到反射技术来确定真正的实体类。
首先在类中定义Class 属性
// T的Class对象
private Class entityClass;
在BaseDAO的无参构造器中,确定真正的实体类
public BaseDAO(){
// getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例
// 那么构造方法首先会调用父类(BaseDAO)的无参构造方法
// 因此此处的getClass方法执行,获取的是FruitDAOImpl的Class
// 所以使用getGenericSuperclass
// getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型
Type genericSuperclass = getClass().getGenericSuperclass();
// ParameterizedType:参数化类型
// 获取(注意,泛型可以传递多个如,所以返回值是一个数组)中的T的真是类型
Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
Type actualType = actualTypeArguments[0];
try {
entityClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
2.5.3 查询方法
在处理结果集时我们不知道T到底有几个参数,是什么类型。因此,我们需要获取结果集的元数据,所谓元数据就是描述结果集数据的数据,简单来讲,就是这个结果集有哪些列,什么类型等等。
之后通过Class对象创建一个T的实例进行操作即可
先定义一个工具函数:
// 通过反射,给obj对象的property属性赋propertyValue值
private void setValue(Object obj,String property,Object propertyValue){
Class clazz = obj.getClass();
// 获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性
try {
Field field = clazz.getDeclaredField(property);
if(field!=null){
// 强制访问
field.setAccessible(true);
//为obj对象设置filed属性的值
field.set(obj,propertyValue);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
执行查询的函数:
// 执行查询
protected List executeQuery(String sql,Object... params){
List list = new ArrayList<>();
try {
// 获取连接
connection = getConnection();
// 设置预处理对象
psmt = connection.prepareStatement(sql);
// 设置参数
setParams(psmt,params);
// 执行查询
rs= psmt.executeQuery();
// 处理结果集
// 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
// 通过rs可以获取结果集的元数据
// 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while(rs.next()){
T entity = (T)entityClass.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = metaData.getColumnName(i + 1);
// 获取列对应的值
Object columnValue = rs.getObject(i + 1);
// 这里获取了列名,就需要根据列名把值填充给entity对象
setValue(entity,columnName,columnValue);
}
list.add(entity);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
close(connection,psmt,rs);
}
return list;
}
2.5.4 改进后的整个BaseDAO类为:
package com.zylai.fruit.dao.base; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.sql.*; import java.util.ArrayList; import java.util.List; public abstract class BaseDAO3 数据库连接池 3.1 概述和优势{ protected final String DRIVER = "com.mysql.jdbc.Driver"; protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false"; protected final String USER = "root"; protected final String PWD = "root"; // 老师是吧这几个放到这里的,我觉得可能会出现并发问题,不过目前不考虑这个 protected Connection connection = null; protected PreparedStatement psmt = null; protected ResultSet rs = null; // T的Class对象 private Class entityClass; public BaseDAO(){ // getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例 // 那么构造方法首先会调用父类(BaseDAO)的无参构造方法 // 因此此处的getClass方法执行,获取的是FruitDAOImpl的Class // 所以使用getGenericSuperclass // getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型 Type genericSuperclass = getClass().getGenericSuperclass(); // ParameterizedType:参数化类型 // 获取 (注意,泛型可以传递多个如 ,所以返回值是一个数组)中的T的真是类型 Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); Type actualType = actualTypeArguments[0]; try { entityClass = Class.forName(actualType.getTypeName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } protected Connection getConnection(){ try { // 1.加载驱动 Class.forName(DRIVER); // 2.通过驱动获取连接对象 return DriverManager.getConnection(URL, USER, PWD); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return null; } // 关闭资源 protected void close(Connection connection, PreparedStatement psmt, ResultSet rs){ try { if(rs!=null){ rs.close(); } if(psmt!=null){ psmt.close(); } if(connection!=null){ connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } // 给预处理对象设置参数 private void setParams(PreparedStatement psmt,Object... params) throws SQLException { if(params!=null && params.length>0){ for (int i = 0; i < params.length; i++) { psmt.setObject(i+1,params[i]); } } } // 通过反射,给obj对象的property属性赋propertyValue值 private void setValue(Object obj,String property,Object propertyValue){ Class clazz = obj.getClass(); // 获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性 try { Field field = clazz.getDeclaredField(property); if(field!=null){ // 强制访问 field.setAccessible(true); //为obj对象设置filed属性的值 field.set(obj,propertyValue); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } // 执行查询 protected List executeQuery(String sql,Object... params){ List list = new ArrayList<>(); try { // 获取连接 connection = getConnection(); // 设置预处理对象 psmt = connection.prepareStatement(sql); // 设置参数 setParams(psmt,params); // 执行查询 rs= psmt.executeQuery(); // 处理结果集 // 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数 // 通过rs可以获取结果集的元数据 // 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); while(rs.next()){ T entity = (T)entityClass.newInstance(); for (int i = 0; i < columnCount; i++) { // 获取列名 String columnName = metaData.getColumnName(i + 1); // 获取列对应的值 Object columnValue = rs.getObject(i + 1); // 这里获取了列名,就需要根据列名把值填充给entity对象 setValue(entity,columnName,columnValue); } list.add(entity); } } catch (SQLException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } finally { close(connection,psmt,rs); } return list; } // 通过查询返回一条记录 protected T load(String sql,Object... params){ try { // 获取连接 connection = getConnection(); // 设置预处理对象 psmt = connection.prepareStatement(sql); // 设置参数 setParams(psmt,params); // 执行查询 rs= psmt.executeQuery(); // 处理结果集 // 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数 // 通过rs可以获取结果集的元数据 // 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); if (rs.next()){ T entity = (T)entityClass.newInstance(); for (int i = 0; i < columnCount; i++) { // 获取列名 String columnName = metaData.getColumnName(i + 1); // 获取列对应的值 Object columnValue = rs.getObject(i + 1); // 这里获取了列名,就需要根据列名把值填充给entity对象 setValue(entity,columnName,columnValue); } return entity; } } catch (SQLException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } finally { close(connection,psmt,rs); } return null; } // 执行复杂的查询,返回统计结果,这个方法暂时没有用到 protected Object[] executeComplexQuery(String sql,Object... params){ try { // 获取连接 connection = getConnection(); // 设置预处理对象 psmt = connection.prepareStatement(sql); // 设置参数 setParams(psmt,params); // 执行查询 rs= psmt.executeQuery(); // 处理结果集 // 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数 // 通过rs可以获取结果集的元数据 // 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); if (rs.next()){ Object[] columnValueArr = new Object[columnCount]; for (int i = 0; i < columnCount; i++) { // 获取列对应的值 Object columnValue = rs.getObject(i + 1); columnValueArr[i]=columnValue; } return columnValueArr; } } catch (SQLException e) { e.printStackTrace(); } finally { close(connection,psmt,rs); } return null; } }
之前的的连接都是用的时候才创建连接,当使用完成之后就销毁,这样效率很低,浪费资源。
使用数据库连接池就可以先创建多个连接对象放到连接池中,等到使用的时候直接从连接池中取出来,用完之后归还。
3.1.1 好处:- 响应时间更快
- 连接对象的利用率更高
不需要背,只是参考,记住主要用的几个就可以了
| 配置 | 缺省 | 说明 |
|---|---|---|
| name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
| url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
| username | 连接数据库的用户名 | |
| password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
| driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
| initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
| maxActive | 8 | 最大连接池数量 |
| maxIdle | 8 | 已经不再使用,配置了也没效果 |
| minIdle | 最小连接池数量 | |
| maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
| poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
| maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
| validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
| testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
| testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
| testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
| timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
| numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
| minEvictableIdleTimeMillis | ||
| connectionInitSqls | 物理连接初始化的时候执行的sql | |
| exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
| filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
| proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
将对应的jar引入到项目中之后,创建DruidDataSource对象,进行连接即可。
注意:
- 被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
- 没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
- 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收
package com.zylai.jdbc;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo02Druid {
public static void main(String[] args) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 证明两点:
// 1.被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
// 2.没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
// 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收
for (int i = 0; i < 5; i++) {
Connection conn1 = dataSource.getConnection();
Connection conn2 = dataSource.getConnection();
System.out.println(conn1);
System.out.println(conn2);
if(i%3==0){
conn1.close();
conn2.close();
}
}
}
}
3.2.2 验证连接池的部分参数
这里以最大连接数和最长等待时间为例,当获取完所有的连接之后,再去获取连接超过指定的等待时间会抛出异常
package com.zylai.jdbc;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo03Druid {
public static void main(String[] args) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(2);
dataSource.setMaxActive(5);
dataSource.setMaxWait(5000);
for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();
System.out.println(i+" "+conn1);
}
}
}
3.2.3 读取配置文件信息创建连接池
在实际开发中,比如数据库url,密码用户等信息都是写在配置文件中,不会直接写在代码中进行硬编码
package com.zylai.jdbc;
import com.alibaba.druid.pool.DruidDataSource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class Demo04Druid {
public static void main(String[] args) throws SQLException, IOException {
Properties properties = new Properties();
InputStream is = Demo04Druid.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(is);
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(properties.getProperty("jdbc.driverClassName"));
dataSource.setUrl(properties.getProperty("jdbc.url"));
dataSource.setUsername(properties.getProperty("jdbc.username"));
dataSource.setPassword(properties.getProperty("jdbc.pwd"));
dataSource.setInitialSize(Integer.parseInt(properties.getProperty("druid.initialSize")));
dataSource.setMaxActive(Integer.parseInt(properties.getProperty("druid.maxActive")));
dataSource.setMaxWait(Long.parseLong(properties.getProperty("druid.maxWait")));
for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();
System.out.println(i+" "+conn1);
}
}
}
配置文件jdbc.properties:
jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.pwd=root druid.initialSize=2 druid.maxActive=5 druid.maxWait=50003.2.4 直接通过配置文件建立连接池
通过DruidDataSourceFactory.createDataSource(properties);返回**DataSource接口**,直接通过配置文件建立好了连接池。不过这里对于配置文件的格式有要求。
package com.zylai.jdbc;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class Demo05Druid {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
InputStream is = Demo05Druid.class.getClassLoader().getResourceAsStream("jdbc2.properties");
properties.load(is);
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();
System.out.println(i+" "+conn1);
}
}
}
配置文件的格式应该一致
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8 username=root password=root initialSize=2 maxActive=5 maxWait=5000



