jdbc学习笔记以及DAO层设计

1 简介和基础使用 1.1 简介
JDBC:是sun发布的一个java程序和数据库之间通信的规范(接口)
各大数据库厂商去实现JDBC规范(实现类),将这些实现类打成压缩包 , 就是所谓的jar包
比如:
1.2 创建连接
url:防止中文乱码 , 加上参数=true&=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 准备urlString 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 添加数据
先建立连接之后 , 得到对象 , 这个对象相当于建立了一条连接数据库和java之间的马路,通过对象得到预处理对象 , 这个对象相当于马路上的一辆车,用于运送真实的数据 。
在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,remarkString 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 更新和删除数据
和添加数据步骤都一样,最后都是预处理对象调用方法并返回影响行数 。因此更新与修改和添加的不同就是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 步骤: 加载驱动通过驱动获取连接对象,连接对象相当于连接java与数据库之间的一条马路编写SQL语句 , 语句中使用占位符创建数据预处理对象,数据预处理对象相当于马路上运送SQL语句的小车填充预处理数据对象的参数执行更新参数,对于增删查都是执行对象的方法 , 返回影响行数关闭资源,关闭对象和对象 1.6 查询
查询与增删改的不同之处在于:
例如:查询所有数据
//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类型的数据,所以使用getIntint 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类型的数据,所以使用getIntint 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 查询与增删改的不同之处在于:1.7.2 步骤: 加载驱动通过驱动获取连接对象 , 连接对象相当于连接java与数据库之间的一条马路编写SQL语句 , 语句中使用占位符创建数据预处理对象,数据预处理对象相当于马路上运送SQL语句的小车填充预处理数据对象的参数执行更新参数,执行方法返回结果集关闭资源,关闭对象、对象和对象 1.8 批处理
如果一次性添加大量数据,若每一条数据都单独执行一次方法,那么效率很低下,所以可以采用批处理的方法来提高处理的效率 。
批处理操作的步骤:
如果执行批处理任务,需要在添加一个参数: ents=true将数据加入一个批次:psmt.();执行批处理:int[] batch = psmt.();如果继续进行处理 , 需要进行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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/12:54* @Description: 添加*/public class DemoAddBatch {public static void main(String[] args) throws ClassNotFoundException, SQLException {//1.加载驱动Class.forName("com.mysql.jdbc.Driver");//2.通过驱动管理器获取连接对象//批处理操作一//如果执行批处理任务,需要添加一个参数: rewriteBatchedStatements=trueString 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.退出======================================================
整体框架如下图 , 在里面写调用DAO层的方法 , 在dao里面写持久层方法 , 提供接口及其实现类,pojo写实体类 , view是客户端视图操作 。
2.2 最初版本 2.2.1 pojo
pojo包下的Fruit类
package com.zylai.fruit01.pojo;/*** @Author: Zhao YunLai* @Date: 2022/06/21/17:33* @Description:*/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;}@Overridepublic String toString() {return fid+"\t\t"+fname+"\t\t"+price+"\t\t"+fcount+"\t\t"+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
接口
package com.zylai.fruit01.dao;import com.zylai.fruit01.pojo.Fruit;import java.util.List;/*** @Author: Zhao YunLai* @Date: 2022/06/21/18:02* @Description:*/public interface FruitDAO {//查询库存列表List getFruitList();//根据水果名称查询库存Fruit getFruitByName(String fname);//新增水果库存boolean addFruit(Fruit fruit);//修改库存boolean updateFruit(Fruit fruit);//根据水果名称删除记录boolean deleteFruitByName(String fname);}
实现类
注意到这个实现类存在着大量的代码冗余,之后的优化主要对这个类进行优化
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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/18:05* @Description:*/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";//查询所有的库存信息@Overridepublic 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;}//通过水果名称查询记录@Overridepublic 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;}//添加水果@Overridepublic 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;}//更新水果库存信息@Overridepublic 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;}//删除水果@Overridepublic 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
处理请求层,由于业务比较简单,所以一些业务都在里面进行
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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/17:35* @Description:*/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("编号\t\t名称\t\t单价\t\t库存\t\t备注\t\t");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);//调用daofruitDAO.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("编号\t\t名称\t\t单价\t\t库存\t\t备注\t\t");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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/17:36* @Description:*/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层的实现类 , 每个方法都要重复建立驱动、获取连接、关闭资源等操作 。

jdbc学习笔记以及DAO层设计

文章插图
2.3 改进:对于中获取连接操作以及释放资源操作做了提取
这次的改进就是对于中获取连接操作以及释放资源操作做了提取
如下:将加载驱动获取连接封装在方法中,将关闭资源封装在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();}}//实际中使用//通过水果名称查询记录@Overridepublic 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设置的参数
因此可以将这些相同的操作抽取出来
此外,这里只是涉及到一个表的操作,当有多个表时 , 就涉及到多个dao类,而这多个dao类都需要这共同的驱动地址,数据库url , 用户名,密码 , 获取驱动方法,关闭资源的方法 , 抽取增删改操作方法 。
因此,加入一个抽象类 , 里面提供公共的常量和方法,并且把抽取的增删改方法加入
package com.zylai.fruit.dao.base;import java.sql.*;/*** @Author: Zhao YunLai* @Date: 2022/06/21/21:45* @Description:*/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;}//关闭资源protectedvoid 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;}}
然后让继承该抽象类
由于没有涉及到查询的改进 , 这里省去查询的方法 。可以看到对于增删改方法,只需要调用父类的更新执行方法 , 传入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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/18:05* @Description:*/public class FruitDAOImpl extends BaseDAO implements FruitDAO {//添加水果@Overridepublic 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;}//更新水果库存信息@Overridepublic 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;}//删除水果@Overridepublic 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,而对于会有多个继承它,因此不能把实体类写死,所以采用泛型 。
public abstract class BaseDAO {}public class FruitDAOImpl extends BaseDAO implements FruitDAO {}
2.5.2 通过反射得到真正的类
因为泛型只是一个符号,我们不能直接通过泛型创建实例 , 所以用到反射技术来确定真正的实体类 。
首先在类中定义Class 属性
//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();}}
2.5.3 查询方法
在处理结果集时我们不知道T到底有几个参数,是什么类型 。因此,我们需要获取结果集的元数据,所谓元数据就是描述结果集数据的数据,简单来讲,就是这个结果集有哪些列,什么类型等等 。
之后通过Class对象创建一个T的实例进行操作即可
【jdbc学习笔记以及DAO层设计】先定义一个工具函数:
//通过反射,给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 = http://www.kingceram.com/post/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 改进后的整个类为:
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;/*** @Author: Zhao YunLai* @Date: 2022/06/21/21:45* @Description:*/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 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;}//关闭资源protectedvoid 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 = http://www.kingceram.com/post/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 数据库连接池 3.1 概述和优势
之前的的连接都是用的时候才创建连接,当使用完成之后就销毁,这样效率很低,浪费资源 。
使用数据库连接池就可以先创建多个连接对象放到连接池中,等到使用的时候直接从连接池中取出来,用完之后归还 。
3.1.1 好处: 响应时间更快连接对象的利用率更高 3.1.2详细配置参数:
不需要背,只是参考,记住主要用的几个就可以了
配置缺省说明
name
配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来 。如果没有配置,将会生成一个名字,格式是:”-” + .(this)
url
连接数据库的url,不同数据库不一样 。例如:mysql : jdbc:mysql://10.20.153.104:3306/: jdbc::thin:@10.20.149.85:1521:
连接数据库的用户名
连接数据库的密码 。如果你不希望密码直接写在配置文件中 , 可以使用 。详细看这里:使用
根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别 , 然后选择相应的(建议配置下)
初始化时建立物理连接的个数 。初始化发生在显示调用init方法 , 或者第一次时
最大连接池数量
已经不再使用 , 配置了也没效果
jdbc学习笔记以及DAO层设计

文章插图
最小连接池数量
获取连接时最大等待时间,单位毫秒 。配置了之后 , 缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置属性为true使用非公平锁 。
ts
false
是否缓存,也就是 。对支持游标的数据库性能提升巨大,比如说 。在mysql下建议关闭 。
ments
-1
要启用,必须配置大于0,当大于0时 , ts自动触发修改为true 。在Druid中,不会存在下占用内存过多的问题,可以把这个数值配置大一些,比如说100
用来检测连接是否有效的sql,要求是一个查询语句 。如果为null , 、、都不会其作用 。
true
申请连接时执行检测连接是否有效,做了这个配置会降低性能 。
false
归还连接时执行检测连接是否有效,做了这个配置会降低性能
false
建议配置为true , 不影响性能,并且保证安全性 。申请连接的时候检测,如果空闲时间大于,执行检测连接是否有效 。
有两个含义: 1)线程会检测连接的间隔时间2)的判断依据 , 详细看属性的说明
un
不再使用,一个只支持一个
物理连接初始化的时候执行的sql
根据自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的:stat日志用的:log4j防御sql注入的:wall
类型是List , 如果同时配置了和,是组合关系,并非替换关系
3.2 使用 3.2.1 基础使用
将对应的jar引入到项目中之后,创建对象,进行连接即可 。
注意:
被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;/*** @Author: Zhao YunLai* @Date: 2022/06/22/14:13* @Description:*/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;/*** @Author: Zhao YunLai* @Date: 2022/06/22/14:13* @Description: 验证连接池的部分参数*/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;/*** @Author: Zhao YunLai* @Date: 2022/06/22/14:13* @Description: 从配置文件中读取信息*/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.:
jdbc.driverClassName=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8jdbc.username=rootjdbc.pwd=rootdruid.initialSize=2druid.maxActive=5druid.maxWait=5000
3.2.4 直接通过配置文件建立连接池
通过ry.();返回**接口**,直接通过配置文件建立好了连接池 。不过这里对于配置文件的格式有要求 。
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;/*** @Author: Zhao YunLai* @Date: 2022/06/22/14:13* @Description: 直接通过配置文件创建连接池*/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.Driverurl=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8username=rootpassword=rootinitialSize=2maxActive=5maxWait=5000