1 简介和基础使用
1.1 简介
JDBC:是sun发布的一个java程序和数据库之间通信的规范(接口)
各大数据库厂商去实现JDBC规范(实现类),将这些实现类打成压缩包,就是所谓的jar包
比如:
1.2 创建连接
url:防止中文乱码,加上参数useUnicode=true&characterEncoding=utf-8
。url的标准格式为:jdbc:mysql://ip:端口号/数据库名称?参数列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb";
String usr = "root";
String pwd = "root"; Connection connection = DriverManager.getConnection(url,usr,pwd); System.out.println("conn = "+connection); } }
|
1.3 添加数据
先建立连接之后,得到connection对象,这个对象相当于建立了一条连接数据库和java之间的马路,通过connection对象得到预处理对象PreparedStatement
,这个对象相当于马路上的一辆车,用于运送真实的数据。
在SQL语句中,使用?
当做占位符,再通过预处理对象的set方法为占位符填充内容
最后,记得关闭连接,先关闭预处理对象,再关闭连接对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class Demo02 { public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf-8";
String usr = "root";
String pwd = "root"; Connection connection = DriverManager.getConnection(url,usr,pwd);
String sql = "insert into t_fruit value(0,?,?,?,?)";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setString(1,"榴莲"); psmt.setInt(2,15); psmt.setInt(3,100); psmt.setString(4,"榴莲是一种神奇的水果");
int i = psmt.executeUpdate(); System.out.println(i>0?"添加成功!":"添加失败");
psmt.close(); connection.close(); } }
|
1.4 更新和删除数据
和添加数据步骤都一样,最后都是预处理对象调用executeUpdate
方法并返回影响行数。因此更新与修改和添加的不同就是SQL语句和预处理设置SQL中的占位符的值而已。
更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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();
|
删除:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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
对象
1.6 查询
查询与增删改的不同之处在于:
- 预处理对象执行的是
executeQuery
方法
- 方法返回结果集
ResultSet
,可以遍历结果接进行数据的操作
例如:查询所有数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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 = "select * from t_fruit";
PreparedStatement psmt = connection.prepareStatement(sql);
ResultSet res = psmt.executeQuery(); List<Fruit> list = new ArrayList<>();
while(res.next()){
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); }
res.close(); psmt.close(); connection.close(); for (Fruit fruit : list) { System.out.println(fruit); }
|
查询指定的一条记录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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 = "select * from t_fruit where fid = ?";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setInt(1,2);
ResultSet res = psmt.executeQuery();
if(res.next()){
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();
|
查询总记录条数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 = "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
,可以遍历结果接进行数据的操作
1.7.2 步骤:
- 加载驱动
- 通过驱动获取连接对象
Connection
,连接对象相当于连接java与数据库之间的一条马路
- 编写SQL语句,语句中使用占位符
- 创建数据预处理对象
PreparedStatement
,数据预处理对象相当于马路上运送SQL语句的小车
- 填充预处理数据对象的参数
- 执行更新参数,执行
executeQuery
方法
- 返回结果集
ResultSet
- 关闭资源,关闭
ResultSet
对象、PreparedStatement
对象和Connection
对象
1.8 批处理
如果一次性添加大量数据,若每一条数据都单独执行一次方法,那么效率很低下,所以可以采用批处理的方法来提高处理的效率。
批处理操作的步骤:
- 如果执行批处理任务,需要在添加一个参数: rewriteBatchedStatements=true
- 将数据加入一个批次:psmt.addBatch();
- 执行批处理:int[] batch = psmt.executeBatch();如果继续进行处理,需要进行clear:psmt.clear();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| 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 {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?rewriteBatchedStatements=true&useSSL=false&useUnicode=true&characterEncoding=utf-8";
String usr = "root";
String pwd = "root"; Connection connection = DriverManager.getConnection(url,usr,pwd);
String sql = "insert into t_fruit value(0,?,?,?,?)";
PreparedStatement psmt = connection.prepareStatement(sql);
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();
psmt.clearBatch(); } }
int[] batch = psmt.executeBatch(); System.out.println(Arrays.toString(batch));
psmt.close(); connection.close(); } }
|
2 工程
2.1 需求概述和整体框架
以实现一个水果库存系统为例,这个系统需要提供水果的增删改查操作
1 2 3 4 5 6 7 8
| =================欢迎使用水果库存系统===================== 1.查看水果库存列表 2.添加水果库存信息 3.查看特定水果库存信息 4.水果下架 5.退出 ======================================================
|
整体框架如下图,在controller里面写调用DAO层的方法,在dao里面写持久层方法,提供接口及其实现类,pojo写实体类,view是客户端视图操作。
2.2 最初版本
2.2.1 pojo
pojo包下的Fruit类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| 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+"\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
FruitDAO接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package com.zylai.fruit01.dao;
import com.zylai.fruit01.pojo.Fruit;
import java.util.List;
public interface FruitDAO {
List<Fruit> getFruitList();
Fruit getFruitByName(String fname);
boolean addFruit(Fruit fruit);
boolean updateFruit(Fruit fruit);
boolean deleteFruitByName(String fname);
}
|
FruitDAOImpl实现类
注意到这个实现类存在着大量的代码冗余,之后的优化主要对这个类进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
| 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<Fruit> getFruitList() { List<Fruit> fruitList = new ArrayList<>(); 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";
psmt = connection.prepareStatement(sql);
rs = psmt.executeQuery();
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里面进行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| 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<Fruit> 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);
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("编号\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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| private Connection getConnection(){ try { Class.forName(DRIVER); 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,里面提供公共的常量和方法,并且把抽取的增删改方法加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| 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 { Class.forName(DRIVER); 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语句和参数即可完成业务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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继承它,因此不能把实体类写死,所以采用泛型。
1 2 3
| public abstract class BaseDAO<T> {}
public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {}
|
2.5.2 通过反射得到真正的类
因为泛型只是一个符号,我们不能直接通过泛型创建实例,所以用到反射技术来确定真正的实体类。
首先在类中定义Class 属性
1 2
| private Class entityClass;
|
在BaseDAO的无参构造器中,确定真正的实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public BaseDAO(){
Type genericSuperclass = getClass().getGenericSuperclass();
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的实例进行操作即可
先定义一个工具函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private void setValue(Object obj,String property,Object propertyValue){ Class clazz = obj.getClass();
try { Field field = clazz.getDeclaredField(property); if(field!=null){ field.setAccessible(true); field.set(obj,propertyValue); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } }
|
执行查询的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| protected List<T> executeQuery(String sql,Object... params){ List<T> list = new ArrayList<>(); try {
connection = getConnection();
psmt = connection.prepareStatement(sql);
setParams(psmt,params);
rs= psmt.executeQuery();
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);
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类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
| 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 BaseDAO<T> { 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;
private Class entityClass;
public BaseDAO(){
Type genericSuperclass = getClass().getGenericSuperclass();
Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments(); Type actualType = actualTypeArguments[0];
try { entityClass = Class.forName(actualType.getTypeName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
protected Connection getConnection(){ try { Class.forName(DRIVER); 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]); } } }
private void setValue(Object obj,String property,Object propertyValue){ Class clazz = obj.getClass();
try { Field field = clazz.getDeclaredField(property); if(field!=null){ field.setAccessible(true); field.set(obj,propertyValue); } } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } }
protected List<T> executeQuery(String sql,Object... params){ List<T> list = new ArrayList<>(); try {
connection = getConnection();
psmt = connection.prepareStatement(sql);
setParams(psmt,params);
rs= psmt.executeQuery();
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);
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();
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);
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();
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 |
|
配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”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/%E4%BD%BF%E7%94%A8ConfigFilter |
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,是组合关系,并非替换关系 |
3.2 使用
3.2.1 基础使用
将对应的jar引入到项目中之后,创建DruidDataSource
对象,进行连接即可。
注意:
- 被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
- 没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
- 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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");
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 验证连接池的部分参数
这里以最大连接数和最长等待时间为例,当获取完所有的连接之后,再去获取连接超过指定的等待时间会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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,密码用户等信息都是写在配置文件中,不会直接写在代码中进行硬编码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| 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:
1 2 3 4 5 6 7 8
| 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=5000
|
3.2.4 直接通过配置文件建立连接池
通过DruidDataSourceFactory.createDataSource(properties);
返回DataSource
接口,直接通过配置文件建立好了连接池。不过这里对于配置文件的格式有要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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); } } }
|
配置文件的格式应该一致
1 2 3 4 5 6 7 8
| 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
|