目录
一、什么是JDBC ?
Java DataBase Connectivity(Java语言连接数据库)
二、JDBC的本质?
JDBC是SUN公司制定的一套接口(interface), java.sql.*; (这个软件包下有很多接口。) 接口都有调用者和实现者: 面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
MS SqlServer数据库也有自己的原理。
....
每一个数据库产品都有自己独特的实现原理。所以在数据库出厂时需要一些特定的接口来实现某些功能
三、JDBC连接数据库的基本步骤
★ ★ ★ ★ ★
1. 注册驱动
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
/*
"驱动在jar包的位置"
Driver.class字节码文件中有注册驱动的方法,所以只需采用类加载的方式加载一次即可。
*/
2. 获取连接
/**
* 2.获取连接
* getConnection("jdbc:哪个品牌数据库://ip地址:端口号/数据库名称","用户名","密码")
* url:统一资源定位符(网络中某个资源的绝对路径)
* url包括哪几个部分:
* 协议:jdbc:mysql://
* IP:127.0.0.1 本机地址
* PORT:端口号 3306
* 资源:test 具体的数据库的实例名
*/
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
3.获取sql语句执行对象
// 3.获取sql执行对象
Statement st = conn.createStatement();
4.执行sql语句
// 4.执行sql语句()
String sql = "insert into dept(deptno,dname,loc) values(51,'RenShiBu','TangShan')";
//以下方法专门执行sql语句的。返回值是 "影响数据库中记录的条数"
int count = st.executeUpdate(sql);
System.out.println(count == 1 ? "增加成功" : "增加失败");
5.查询处理结果集(只有sql语句时select语句时才会执行这一步 )
6.释放资源
// 6.释放资源
//遵循从小到大依次关
finally {
if (conn !=null){
try {
conn.close() ;
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st !=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
使用JDBC连接数据库前的准备工作:
从官网上下载相关jar包,引入项目中【idea为例】:
找到下载好的jar包,增加进去,如果右边出现这个包说明引入成功了。
(一)java.sql.* 下的接口:
* Statement:表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
* ResultSet:表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。
* Connection:与特定数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果。
* DriverManage:驱动管理器
四、使用JDBC连接数据库完成增删改查
(一)对数据库的增加
public class InsertData {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
//3、获取sql语句执行对象
st = conn.createStatement();
//4、创建sql语句并执行
String sql = "insert into t_user(userName,passwd,realName) value('ZS','789','张三')";
int count = st.executeUpdate(sql);
System.out.println(count > 0 ? "增加成功" : "增加失败");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
演示结果:
(二)对数据库的修改,删除和增加一样,只需要修改sql语句 .
//修改sql语句
String sql = "update t_user set userName='LS',passwd='999',realName='李四' where id=11";
演示结果:
//删除sql语句
String sql = "delete from t_user where id=11";
演示结果:
(三)对数据库的查询需要处理查询结果集
public class SelectData {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
//查询结果集
ResultSet rs = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root");
st = conn.createStatement();
String sql = "select * from t_user";
rs = st.executeQuery(sql);
while (rs.next()) {
//这里的 1 2 3 4 代表数据库中的字段
// System.out.print(rs.getString(1) + "\t");
// System.out.print(rs.getString(2) + "\t");
// System.out.print(rs.getString(3) + "\t");
// System.out.print(rs.getString(4) + "\t");
// System.out.println();
//还可以这样学,"" 里面的名字必须和sql 语句里的字段名一样
int id = rs.getInt("id");
String userName = rs.getString("userName");
String passwd = rs.getString("passwd");
String realName = rs.getString("realName");
System.out.println(id + "\t" + userName + "\t" + passwd + "\t" + realName + "\t");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
演示结果:
五、SQL防注入问题
通过 一个实例演示sql 语句注入问题:
例: 使用JDBC来模拟一个用户登录界面,将用户输入的用户名和密码与数据库中的用户名密码进行比对,相等登陆成功,否则,登录失败!!
数据库用户登录表:
代码:
public class JDBCTest4 {
public static void main(String[] args) {
/*
使用jDBC模拟用户登录系统
*/
HashMap<String, String> init = userLoginInfo();
boolean loginSuccess = jugeLoginInfo(init);
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
//连接数据库进行用户信息判断
public static boolean jugeLoginInfo(HashMap<String, String> userLoginInfo) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
boolean loginSuccess = false;
try {
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//3 获取sql语句执行对象
st = conn.createStatement();
// 4 执行sql语句
//将输入的用户名与密码与数据库中进行查询
String sql = "select * from t_user where userName ='" + userLoginInfo.get("userName") + "' and passwd = '" + userLoginInfo.get("passwd") + "' ";
//5 处理查询结果集,结果集只有一条所以不必用循环
rs = st.executeQuery(sql);
if (rs.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6 释放资源
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
if (st != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
return loginSuccess;
}
//接受用户输入信息
public static HashMap<String, String> userLoginInfo() {
HashMap<String, String> userInfo = new HashMap<>();
Scanner in = new Scanner(System.in);
//输入用户和密码并增加到集合中
System.out.println("请输入用户名:");
String userName = in.nextLine();
userInfo.put("userName", userName);
System.out.println("请输入密码:");
String passwd = in.nextLine();
userInfo.put("passwd", passwd);
return userInfo;
}
}
演示:
但是我们看下面这俩种情况,即使用户名和密码不对的情况下,我们还是能够登录成功?这就是sql注入问题。
1、什么是sql注入问题呢?
* 用户输入的关键信息,并且这些关键信息参与了sql语句的编译。
* 导致sql语句编译错误
* 当我们输入jack'# 和 jack'-- 时, "#" 和"-- "后面的sql语句都变成了注释,其实并没有判断passwd
2、怎么解决sql注入问题?
只要用户输入的信息不参与sql语句编译的过程,问题就解决了
即使用户提供的信息中含有sql语句的关键字,但是没有参与编译过程,也没有用了
* 要想输入的信息不参与sql语句编译,使用 java.sql.PrepareStatement代替Statement
* PrepareStatement继承了 java.sql.Statement
* PrepareStatement属于预编译的数据库操作。
解决sql 注入问题:
只需对部分代码进行改动就行。
//连接数据库进行用户信息判断
public static boolean jugeLoginInfo(HashMap<String, String> userLoginInfo) {
Connection conn = null;
ResultSet rs = null;
//代替Statement
PreparedStatement ps = null;
//标记用户输入信息否正确
boolean loginSuccess = false;
try {
//1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
String sql = "select * from t_user where userName =? and passwd = ?";
// 3 预编译数据库操作对象
ps = conn.prepareStatement(sql);
//给?传值,1代表第一个? 2代表第二个?
ps.setString(1, userLoginInfo.get("userName"));
ps.setString(2, userLoginInfo.get("passwd"));
//由于在获取数据库操作对象的时候已经将sql语句进行编译,所以这里不要再执行了
rs = ps.executeQuery();
if (rs.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
return loginSuccess;
}
演示:
这样就防止了用户入侵数据库,但是用户输入参与sql语句编译有时候还是有用的!!
代码中的 ?代表占位符,一个?接收一个值,不能用" "括起来!!!!!
程序执行到第三步的时候,DBMS会预先编译sql语句命令,后面用户传入的数据不影响编译
ps.setString(1, userLoginInfo.get("userName")); :给?传值,1代表第一个?,2代表第二个?,以此类推
六、JDBC中的事务
* JDBC中的事务,是自动提交的,每执行一条DML语句,就会提交 * 这种方式在实际开发中并不好用。因为实际开发中都是多条DML语句配合完成的。
设置手动提交的方法:
* conn.setAutoCommit(false); 关闭自动提交
* conn.commit(); 提交
* conn.rollback(); 回滚
通过一个银行转账的实例演示事务自动提交的局限性:
数据库中的表:
代码:
public class JDBCTest05 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
// 第一步:注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//第二步:获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
// 第三步:获取预编译sql语句执行对象
// 编写sql语句
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
// 传值
ps.setDouble(1, 20000);
ps.setInt(2, 111);
// 执行sql语句
int count = ps.executeUpdate();
//这里会出现空指针异常,下面的代码不会执行,直接执行catch中的代码
String s = null;
s.toString();
ps.setDouble(1, 10000);
ps.setInt(2, 222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
当执行到 String s = null; 时,会报空指针异常程序会直接跳到下面的catch语句,
ps.setDouble(1, 10000);
ps.setInt(2, 222);
count += ps.executeUpdate();这几条语句并没有执行,这个时候就出现了问题,转账操作只执行了一部分。
这个时候就需要设置手动提交,当整个转账操作完成才能执行sql语句
设置手动提交:
public class JDBCTest05 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
// 第一步:注册驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//第二步:获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//在这里设置 手动提交事务
conn.setAutoCommit(false);
// 第三步:获取预编译sql语句执行对象
// 编写sql语句
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
// 传值
ps.setDouble(1, 20000);
ps.setInt(2, 111);
// 执行sql语句
int count = ps.executeUpdate();
//这里会出现空指针异常,下面的代码不会执行,直接执行catch中的代码
String s = null;
s.toString();
ps.setDouble(1, 10000);
ps.setInt(2, 222);
count += ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
// 程序能够走到这里说明程序没有异常,手动提交事务
// 不执行这个语句,sql语句就不执行
conn.commit();
} catch (Exception e) {
if (conn!=null){
// 手动回滚
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
} finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
当设置手动提交,只有当执行 conn.commit(); 这条语句时才会完整的提交sql 语句。
七、JDBCUtils 工具类封装 数据库的连接
由于我们每次进行增删改查都需要注册驱动,连接数据库.....都是重复的操作,那么我们可以将这些重复的操作封装到一个类中。
提前将 注册驱动,连接数据库需要的一些参数封装到 jdbc.resource 文件中。
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
user=root
password=root
封装 JDBCUtils工具类:
/*封装连接数据库的操作*/
public class JDBCUtils {
private static ResourceBundle resourcee = ResourceBundle.getBundle("jdbc");
private static String driver = resourcee.getString("driver");
private static String url = resourcee.getString("url");
private static String user = resourcee.getString("user");
private static String password = resourcee.getString("password");
/*静态代码块:注册驱动只需注册一次*/
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/*连接数据库*/
public static Connection getConnection() {
Connection conn = null;
try {
conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/*释放资源*/
public static void close(Connection conn, ResultSet rs, PreparedStatement ps) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
八、通用的增删改操作
在进行 对数据库 进行 增删改 操作时,其实只是 sql 语句不一样,其他代码都是重复的,所以我们可以将增删改进行封装,只需要传递sql语句和修改的数据即可
/*
* Object...args :可变长参数,用于对 占位符 传值。
* */
public static void update(String sql,Object...args){
PreparedStatement ps = null ;
Connection conn =null ;
try {
//获取连接
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
//为占位符传值
for (int i = 0; i < args.length; i++) {
//注意参数别写错
ps.setObject(i+1,args[i]);
}
//执行 sql 语句
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.close(conn,null,ps);
}
}
}
九、通用的查询操作
处理结果集的时候通常有三种方式:
//第一种方式 String id = rs.getString(1); String userName = rs.getString(2); String passwd = rs.getString(3); String realName = rs.getString(4); System.out.println(id + userName + passwd + realName);
//封装成数组 String id = rs.getString(1); String userName = rs.getString(2); String passwd = rs.getString(3); String realName = rs.getString(4); Object[] data = new Object[]{id,userName,passwd,realName};
//第三种方式:封装成一个对象【推荐这种方式】 int id = rs.getInt(1); String userName = rs.getString(2); String passwd = rs.getString(3); String realName = rs.getString(4); UserBean userBean = new UserBean(id,userName,passwd,realName);
User对象:
public class UserBean { private int id ; private String userName; private String passwd ; private String realName ; public UserBean() { } public UserBean(int id, String userName, String passwd, String realName) { this.id = id; this.userName = userName; this.passwd = passwd; this.realName = realName; } @Override public String toString() { return "pojo.UserBean{" + "id=" + id + ", userName='" + userName + '\'' + ", passwd='" + passwd + '\'' + ", realName='" + realName + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName = realName; } }
针对于一张表的通用查询操作:
public static UserBean query(String sql, Object... args) {
/*获取连接*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
/*占位符传值*/
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//处理查询结果集
//需要直到的:列名 和 列数 和 数据
//metaDate:获取结果集中的元数据
ResultSetMetaData metaData = ps.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
rs = ps.executeQuery();
if (rs.next()) {
UserBean user = new UserBean();
for (int i = 0; i < columnCount; i++) {
//获取数据
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = metaData.getColumnLabel(i + 1);
//通过反射机制向 UserBean 对象传值
Field field = UserBean.class.getDeclaredField(columnName);
//打破封装
field.setAccessible(true);
field.set(user, columnValue);
}
return user;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放资源*/
JDBCUtils.close(conn, rs, ps);
}
return null;
}
}
针对所有的表的通用查询方法,次方法仅返回一条记录:
/*用泛型代替 具体的某个对象
* Class<T>:表示某个对象的类
* */
public static <T> T query(Class<T> tClass,String sql, Object... args) {
/*获取连接*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
/*占位符传值*/
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//处理查询结果集
//需要知道查询的:列名 和 列数
//metaDate:获取元数据
ResultSetMetaData metaData = ps.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
rs = ps.executeQuery();
if (rs.next()) {
//调用这个类的无参构造方法
T t = tClass.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取数据
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = metaData.getColumnLabel(i + 1);
//通过反射机制向 t 对象传值
Field field = tClass.getDeclaredField(columnName);
//打破封装
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放资源*/
JDBCUtils.close(conn, rs, ps);
}
return null;
}
针对所有的表的通用查询方法,次方法仅返回多条记录:
//针对不同的表 返回多条记录
public static <T> List<T> getForList(Class<T> tClass,String sql,Object...args){
/*获取连接*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
/*占位符传值*/
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//处理查询结果集
//需要知道查询的:列名 和 列数
//metaDate:获取元数据
ResultSetMetaData metaData = ps.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
rs = ps.executeQuery();
while (rs.next()) {
//调用这个类的无参构造方法
T t = tClass.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取数据
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = metaData.getColumnLabel(i + 1);
//通过反射机制向 t 对象传值
Field field = tClass.getDeclaredField(columnName);
//打破封装
field.setAccessible(true);
field.set(t, columnValue);
}
// 查到的数据增加到集合中
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放资源*/
JDBCUtils.close(conn, rs, ps);
}
return list;
}
测试:
十、BaseDao 的封装以及实现
BaseDAO:一般是提供从数据库 增加、删除、修改记录、查询所有记录、查询符合某个条件记录、取得某条记录等方法的底层数据操作自定义类,简单来说BaseDao里封装了对数据的增删改查的方法。
BaseDao类的创建:将上面编写好的 增删改查 方法 封装到BaseDao类中。
由于BaseDao不需要实例化,只需要提供一些方法,设置成私有即可。
/*封装了所有表的通用操作【增删改查】,不需要实例化*/
public abstract class BaseDao {
/*增删改
* Object...args :可变长参数,用于对 占位符 传值。
* */
public static void update(String sql, Object... args) {
PreparedStatement ps = null;
Connection conn = null;
try {
//获取连接
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
//为占位符传值
for (int i = 0; i < args.length; i++) {
//注意参数别写错
ps.setObject(i + 1, args[i]);
}
//执行 sql 语句
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.close(conn, null, ps);
}
}
/* 查询:仅能返回一条记录
用泛型代替 具体的某个对象
* Class<T>:表示某个对象的类
* */
public static <T> T query(Class<T> tClass, String sql, Object... args) {
/*获取连接*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
/*占位符传值*/
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//处理查询结果集
//需要知道查询的:列名 和 列数
//metaDate:获取元数据
ResultSetMetaData metaData = ps.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
rs = ps.executeQuery();
if (rs.next()) {
//调用这个类的无参构造方法
T t = tClass.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取数据
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = metaData.getColumnLabel(i + 1);
//通过反射机制向 t 对象传值
Field field = tClass.getDeclaredField(columnName);
//打破封装
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放资源*/
JDBCUtils.close(conn, rs, ps);
}
return null;
}
//针对不同的表 返回多条记录
public static <T> List<T> getForList(Class<T> tClass, String sql, Object...args){
/*获取连接*/
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
/*占位符传值*/
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//处理查询结果集
//需要知道查询的:列名 和 列数
//metaDate:获取元数据
ResultSetMetaData metaData = ps.getMetaData();
//获取查询的列数
int columnCount = metaData.getColumnCount();
rs = ps.executeQuery();
while (rs.next()) {
//调用这个类的无参构造方法
T t = tClass.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取数据
Object columnValue = rs.getObject(i + 1);
//获取列名
String columnName = metaData.getColumnLabel(i + 1);
//通过反射机制向 t 对象传值
Field field = tClass.getDeclaredField(columnName);
//打破封装
field.setAccessible(true);
field.set(t, columnValue);
}
// 查到的数据增加到集合中
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/*释放资源*/
JDBCUtils.close(conn, rs, ps);
}
return list;
}
}
BaseDao 的实现:需要指定的接口和实现类,接口用于对某张表进行的增删改查操作,实现类实现具体的方法。
接口:描述对某张数据库表的操作【增删改查】,一般以:.....Dao 命名。
实现类:需要继承BaseDao,通过BaseDao中的方法,实现接口中的方法,一般以:.....ImplDao 命名。
接口类:
/*
* 此接口仅对于t_user表的增删改查操作
* */
public interface UserDao {
//增加
void insert(UserBean user);
//通过ID删除
void delete(int id);
//修改
void update(UserBean user);
//通过id查询
UserBean findByID(int id);
//查询多条记录
List<UserBean> findAll() ;
}
实现类:
//实现类
public class UserImplDao extends BaseDao implements UserDao{
@Override
public void insert(UserBean user) {
String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
update(sql,user.getUserName(),user.getPasswd(),user.getRealName());
}
@Override
public void delete(int id) {
String sql = "delete from t_user where id=?";
update(sql,id);
}
@Override
public void update(UserBean user) {
String sql = "update t_user set userName=?,passwd=?,realName=? where id=?";
update(sql,user.getUserName(),user.getPasswd(),user.getRealName(),user.getId());
}
@Override
public UserBean findByID(int id) {
String sql = "select * from t_user where id=?";
UserBean user = query(UserBean.class, sql, id);
return user;
}
@Override
public List<UserBean> findAll() {
String sql = "select * from t_user";
List<UserBean> list = getForList(UserBean.class, sql);
return list;
}
}
测试:
public class UserDaoTest {
private UserImplDao userImplDao = new UserImplDao();
@Test
public void testInsert(){
/*这个ID无所谓,前面没有用到*/
UserBean user = new UserBean(5,"zhangsan","111","张三");
userImplDao.insert(user);
}
@Test
public void testUpdate(){
UserBean user = new UserBean(5,"lisi","222","李四");
userImplDao.update(user);
}
@Test
public void testDelete(){
userImplDao.delete(5);
}
@Test
public void testFindById(){
UserBean user = userImplDao.findByID(1);
System.out.println(user);
}
@Test
public void testFindAll(){
List<UserBean> list = userImplDao.findAll();
list.forEach(System.out::println);
}
}
十一、数据库连接池技术
普通连接数据库的弊端?
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要 建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重 新建立一个。
数据库连接池技术的优点
- 1. 资源重用
- 由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
- 2. 更快的系统反应速度
- 数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均 已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销, 从而减少了系统的响应时间
- 3. 新的资源分配手段
- 对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库 连接数的限制,避免某一应用独占所有的数据库资源
- 4. 统一的连接管理,避免数据库连接泄漏
- 在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据 库连接操作中可能出现的资源泄露
JDBC的数据库连接池使用 java.sql.DataSource 来表示,DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接 池。
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因 自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一 点
- BoneCP 是一个开源组织提供的数据库连接池,速度快
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是 速度不确定是否有BoneCP快
使用 C3P0数据库连接池获取连接:
将 jar 包 导入项目中的 lib 目录下
第一种方式:使用C3P0 数据库连接池
/*获取 C3P0 数据库连接池*/
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("root");
//设置连接池初始连接数
cpds.setInitialPoolSize(10);
Connection conn = cpds.getConnection();
System.out.println(conn);
第二种方式:使用 xml 配置文件【推荐使用】
<c3p0-config> <!--连接数据库的四个基本信息--> <named-config name="helloC3P0"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <property name="user">root</property> <property name="password">root</property> <!--当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数--> <property name="acquireIncrement">5</property> <!--初始化的连接数--> <property name="initialPoolSize">10</property> <!--维护的最少连接数--> <property name="minPoolSize">1</property> <!--维护的最多连接数--> <property name="maxPoolSize">5</property> <!-- C3P0 数据库连接池可以维护的 Statement 的个数 --> <property name="maxStatements">20</property> <!-- 每个连接同时可以使用的 Statement 对象的个数 --> <property name="maxStatementsPerConnection">50</property> </named-config> </c3p0-config>
// " " 里面的名字和 配置文件中 <named-config name="helloC3P0"> 一致。 ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3P0"); Connection conn = cpds.getConnection(); System.out.println(conn);
使用C3P0的帮助文档:doc目录下 ----> index.html。帮助文档中有提供连接代码以及配置文件代码。
Dbcp 数据库连接池技术:
将俩个 jar 包导入项目中的 lib 目录下。
第一种方式:
//第一种方式
@Test
public void DbcpTest() throws SQLException {
BasicDataSource source = new BasicDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setUsername("root");
source.setPassword("root");
Connection conn = source.getConnection();
System.out.println(conn);
}
第二种方式:配置文件【推荐使用】
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
//第二种方式:配置文件
@Test
public void DbcpTest02() throws Exception {
//加载流
Properties pr = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
pr.load(is);
DataSource source = BasicDataSourceFactory.createDataSource(pr);
Connection conn = source.getConnection();
System.out.println(conn);
}
增加 jar 包
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
//第一种方式
@Test
public void DruidTest() throws SQLException {
DruidDataSource source = new DruidDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver" );
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setUsername("root");
source.setPassword("root");
DruidPooledConnection conn = source.getConnection();
System.out.println(conn);
}
//第二种方式:配置文件
@Test
public void DruidTest02() throws Exception {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(pros);
Connection conn = source.getConnection();
System.out.println(conn);
}
十二、Apache-DBUtils实现CRUD操作
DBUtils 介绍:
- org.apache.commons.dbutils.QueryRunner 封装了增删改查的方法
- org.apache.commons.dbutils.ResultSetHandler 处理查询结果集
增删改查直接封装到了QueryRunner类 中,使用更方便。
演示数据库增加数据:
//增加
@Test
public void update() throws SQLException {
QueryRunner runner = new QueryRunner();
//获取连接
Connection conn = JDBCUtils.getConnection();
String sql = "insert into t_user(userName,passwd,realName) values(?,?,?)";
int count = runner.update(conn, sql, "wangwu", "2344", "王五");
System.out.println("影响数据库条数:" + count);
}
演示查询数据:
ResultSetHandler 接口中的实现类是为了方便 查询返回的结果。
//查询多条记录
@Test
public void query() throws SQLException {
QueryRunner runner = new QueryRunner();
//获取连接
Connection conn = JDBCUtils.getConnection();
BeanListHandler<UserBean> listHandler = new BeanListHandler<>(UserBean.class);
String sql = "select * from t_user where id>?";
List<UserBean> list = runner.query(conn, sql, listHandler, 2);
list.forEach(System.out::println);
}
当查询特殊值的时候:使用 ScalarHandler 类
/*
*ScalarHandler() {} 查询特殊值的时候使用。
* 查询表中的数据条数
* */
@Test
public void queryTest02() throws SQLException {
QueryRunner runner = new QueryRunner();
//获取连接
Connection conn = JDBCUtils.getConnection();
ScalarHandler<Object> handler = new ScalarHandler<>();
String sql = "select count(*) from t_user";
Object count = runner.query(conn, sql, handler);
System.out.println("数据库中记录总条数:" + count);
}
/*
*ScalarHandler() {} 查询emp表中入职最晚的
* */
@Test
public void queryTest03() throws SQLException {
QueryRunner runner = new QueryRunner();
//获取连接
Connection conn = JDBCUtils.getConnection();
ScalarHandler<Date> handler = new ScalarHandler<>();
String sql = "select max(HIREDATE) from emp";
Date date = runner.query(conn, sql, handler);
System.out.println("入职最晚的:" + date);
}