文章目录
1. 数据库事务
数据一旦提交,就不可回滚。
- 哪些操作会导致数据的自动提交?
DDL操作一旦执行都会自动提交(表的创建删除和修改)
set autocommit=false的方式对DDL操作失效
DML操作一旦执行都会自动提交(数据的增删改)
>我们可以通过set autocommit=false的方式取消DML操作的自动提交
默认在关闭连接会进行自动提交
通用的增删改查都需要做哪些更改?
- 将连接作为参数传入方法
- 删除原方法内的创建连接的代码
- 原代码中关闭连接的那个改为null
在哪创建连接在哪关,考虑上事务时,需要在具体的处理代码中
- 创建连接
- 调用通用增删改查方法
- 再关闭连接
jdbcstu.transaction\TransactionTest.java
package jdbcstu1.transaction;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import org.junit.Test;
import jdbcstu1.util.JDBCUtils;
public class TransactionTest {
/*
* 针对于数据表user_table来说,AA用户给BB用户转账100
* update user_table set balance=balance-100 where user='AA'
* update user_table set balance=balance+100 where user='BB'
* 这两句话要么都执行,要么都不执行(事务);出现异常需要回滚
* 数据一旦提交,就不可回滚。
* 哪些操作会导致数据的自动提交?
* >DDL操作一旦执行都会自动提交(表的创建删除和修改)
* >set autocommit=false的方式对DDL操作失效
* >DML操作一旦执行都会自动提交(数据的增删改)
* >我们可以通过set autocommit=false的方式取消DML操作的自动提交
* >默认在关闭连接会进行自动提交
*/
/*******************未考虑数据库事务的一个转账操作************************/
@Test
public void testUpdate() {
String sql1="update user_table set balance=balance-100 where user=?";
update(sql1,"AA");
//模拟网络异常
System.out.println(10/0);
String sql2="update user_table set balance=balance+100 where user=?";
update(sql2,"BB");
System.out.println("转账成功");
}
//通用的增删改操作(同库不同表)---version 1.0
public int update(String sql,Object...args) {
PreparedStatement ps = null;
Connection conn = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);//小心参数声明错误
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.资源的关闭
JDBCUtils.closeResource(conn, ps);
}
return 0;
}
/*****************考虑数据库事务后的转账操作
* @throws Exception *********************/
//通用的增删改操作(同库不同表)---version 2.0
/*
* 从外面传入连接,并在外面关闭连接;而不是在里面造里面关
*/
@Test
public void testUpdateWithTX() {//事务的缩写
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
//1.取消数据的自动提交功能
conn.setAutoCommit(false);
String sql1="update user_table set balance=balance-100 where user=?";
update(conn,sql1,"AA");
//模拟网络异常
System.out.println(10/0);
String sql2="update user_table set balance=balance+100 where user=?";
update(conn,sql2,"BB");
System.out.println("转账成功");
//2.提交数据
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//3.当出现异常进入这里,在这里回滚数据
try {
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally {
JDBCUtils.closeResource(conn, null);
}
}
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//2.预编译sql语句返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);//小心参数声明错误
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.资源的关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
//******************************************************
@Test
public void testTransactionSelect() throws Exception {
Connection conn = JDBCUtils.getConnection();
//获取当前连接的隔离级别
System.out.println(conn.getTransactionIsolation());
//设置数据库的隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//取消自动提交数据
conn.setAutoCommit(false);
String sql="select user,password,balance from user_table where user=? ";
User user = getInstance(conn,User.class,sql,"CC");
System.out.println(user);
}
@Test
public void testTransactionUpdate() throws Exception {
Connection conn = JDBCUtils.getConnection();
//取消自动提交数据
conn.setAutoCommit(false);
String sql="update user_table set balance=? where user=?";
update(conn,sql,5000,"CC");
Thread.sleep(15000);
System.out.println("修改结束");
}
//返回一个对象的查询,将连接作为参数的查询版本
//通用的查询操作用于返回数据表中的一条记录(version 2.0 考虑上事务)
public <T>T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {// 获取一个对象实例,泛型方法T是对应的运行时类。返回值类型是T,T是一个参数
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]);
}
rs = ps.executeQuery();
// 获取结果集的元数据(修饰现有数据的数据)
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
// 如下述所言这里造对象
// Customer cust = new Customer();对于通用查询来说应该利用反射来得到应该造哪个类的对象
T t=clazz.newInstance();
/**
* 现在是查一条数据,若是查多条可以写成while(数据库表中的行) 获取结果集列数,根据结果集列数来判断需要读取多少个字段值
* 列数封装在rs的元数据中,在if之前使用getMetaData()方法获取结果集的元数据 通过ResultSetMetaData获取结果集的列数
*/
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {// 根据列数(数据库表中的列),循环读取属性字段值
// 获取列值
Object columValue = rs.getObject(i + 1);
/**
* 将数据封装到一个对象当中,对象需要给属性们附上所查到的值;构造对象(两种方式,这里推荐方式二)
* 方式一:在构造器中将属性直接填进去(不推荐,因为对应同参数个数的构造器不一定有)
* 方式二:先用空参构造器造一个对象,根据查询属性字段通过Set方法将值设置进去(推荐)
* 对象造一次故不能写到for里,最好写到if里,for外;不写到if里因为可能存在没有结果集却造了个对象的情况
*/
/**
* 现在需要给cust这个对象指定的某一个属性名赋值为value 需要获取结果集当中的列名,将这个列名对应的同名的属性赋值为相应的value
*/
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i+1);// 获取i+1这列
// 将cust这个对象叫columnName这个名的属性赋值为columValue这个值,通过反射
/**
* Customer类中找叫columnName的属性,把那个属性对应cust对象的值赋值为columValue 即调用运行时类的指定属性用户值的操作
*/
//Field field = Customer.class.getDeclaredField(columnName);// 先将叫这个名的属性拿到
Field field = clazz.getDeclaredField(columnLabel);// 先将叫这个名的属性拿到
field.setAccessible(true);// 拿到的这个属性可能是私有的,将它变得能访问
field.set(t, columValue);// 将这个属性名的值设置给当前的cust,赋值为columValue
}
return t;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
// 如果没附上值,则返回null
}
return null;
// 再去掉throws用try catch以及用finally,赋值为null
}
}
jdbcstu.transaction\ConnectionTest.java
package jdbcstu1.transaction;
import java.sql.Connection;
import org.junit.Test;
import jdbcstu1.util.JDBCUtils;
public class ConnectionTest {
@Test
public void testGetConnection() throws Exception {
Connection conn = JDBCUtils.getConnection();
System.out.println(conn);
}
}
jdbc.transaction\User.java
package jdbcstu1.transaction;
public class User {
private String user;
private String password;
private int balance;
public User() {
super();
}
public User(String user, String password, int balance) {
super();
this.user = user;
this.password = password;
this.balance = balance;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "User [user=" + user + ", password=" + password + ", balance=" + balance + "]";
}
}
jdbcstu.util\JDBCUtils
package jdbcstu1.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**alt+shift+j 创建文档注释
* @Description 因为每次获取连接和关闭连接都是必须要做的,所以我们可以将这两部分代码封装起来
* 这个是自己创建的工具类的包。
* @author Field
* @version
* @date 2021年3月29日 下午8:52:27
*/
public class JDBCUtils {
/**
* 打/**+Enter
* @Description 获取数据库连接
* @author Dell
* @version
* @date 2021年3月29日 下午9:10:00
* @return
* @throws Exception
*/
//静态方法,返回的是一个连接,方法名为getConnection
public static Connection getConnection() throws Exception {
//1.读取配置文件中四个基本信息;类.class.方法即类的加载器中系统加载器.方法(文件名)
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//ctrl+1生成流
Properties pros = new Properties();
pros.load(is);//通过pros加载文件
String user=pros.getProperty("user");//通过这个来读取
String password=pros.getProperty("password");
String url=pros.getProperty("url");
String driverClass=pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void closeResource(Connection conn,Statement ps) {//PreparedStatement,这里写成Statement大一点,PreparedStatement也能放
/**
* 关闭连接和statement操作
*/
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
}
/**
*
* @Description关闭资源操作(连接、Statement、结果集)
* @version
* @date 2021年3月31日 下午8:49:18
* @param conn
* @param ps
* @param rs
*/
public static void closeResource(Connection conn,Statement ps,ResultSet rs) {//多一个参数构成方法的重载,需要导入sql下的包(面向接口编程不出现其他第三方API)
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
try {
if(rs!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
配置文件jdbc.properties
user=root
password=admin
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
driverClass=com.mysql.jdbc.Driver
2. BaseDAO(封装数据表操作)
- 将对数据库表的通用操作方法封装到BaseDAO抽象类中,用抽象类的方式表示不能对其实例化
- 将这个类作为基础父类,针对于具体的表会造它具体的DAO然后去继承于这个BaseDAO,然后针对于具体的表可重写方法。
- 但一般为了代码规范首先会造具体表的接口,将要实现的方法写在里面,这样更加清楚。
- 比如下BaseDAO(基础父类:通用方法操作)、CustomerDAO接口、CustomerDAO接口的实现类
如下为BaseDAO
package jdbcstu2.dao;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import jdbcstu1.util.JDBCUtils;
/*
* DAO:data(base) access object
* 封装了针对于数据表的通用的操作
*/
/*
* 仅提供通用方法,不会去造它的对象
* 故abstract修饰此类,表示仅是抽象类,不能对它实例化
* 这是基础父类,针对于具体的表会造它具体的DAO然后去继承于这个BaseDAO
* 但为了代码规范,一般先造一个接口
*/
public abstract class BaseDAO {
//考虑了事务的增删改
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//2.预编译sql语句返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);//小心参数声明错误
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.资源的关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
//通用的查询操作用于返回数据表中的一条记录(version 2.0 考虑上事务)
public <T>T getInstance(Connection conn,Class<T> clazz,String sql, Object... args) {// 获取一个对象实例,泛型方法T是对应的运行时类。返回值类型是T,T是一个参数
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]);
}
rs = ps.executeQuery();
// 获取结果集的元数据(修饰现有数据的数据)
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
// 如下述所言这里造对象
// Customer cust = new Customer();对于通用查询来说应该利用反射来得到应该造哪个类的对象
T t=clazz.newInstance();
/**
* 现在是查一条数据,若是查多条可以写成while(数据库表中的行) 获取结果集列数,根据结果集列数来判断需要读取多少个字段值
* 列数封装在rs的元数据中,在if之前使用getMetaData()方法获取结果集的元数据 通过ResultSetMetaData获取结果集的列数
*/
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {// 根据列数(数据库表中的列),循环读取属性字段值
// 获取列值
Object columValue = rs.getObject(i + 1);
/**
* 将数据封装到一个对象当中,对象需要给属性们附上所查到的值;构造对象
* 先用空参构造器造一个对象,根据查询属性字段通过Set方法将值设置进去(推荐)
* 对象造一次故不能写到for里,最好写到if里,for外;不写到if里因为可能存在没有结果集却造了个对象的情况
*/
/**
* 现在需要给cust这个对象指定的某一个属性名赋值为value 需要获取结果集当中的列名,将这个列名对应的同名的属性赋值为相应的value
*/
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i+1);// 获取i+1这列
// 将cust这个对象叫columnName这个名的属性赋值为columValue这个值,通过反射
/**
* Customer类中找叫columnName的属性,把那个属性对应cust对象的值赋值为columValue 即调用运行时类的指定属性用户值的操作
*/
//Field field = Customer.class.getDeclaredField(columnName);// 先将叫这个名的属性拿到
Field field = clazz.getDeclaredField(columnLabel);// 先将叫这个名的属性拿到
field.setAccessible(true);// 拿到的这个属性可能是私有的,将它变得能访问
field.set(t, columValue);// 将这个属性名的值设置给当前的cust,赋值为columValue
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
// 如果没附上值,则返回null
}
return null;
// 再去掉throws用try catch以及用finally,赋值为null
}
//通用的查询操作,用于返回表中多条记录构成的集合 version2(考虑上事务的)
public <T> List<T> getForList(Connection conn,Class<T> clazz,String sql, Object... args){//ctrl+shift+O选择util下的list
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据(修饰现有数据的数据)
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
/**
* 造一个集合将对象放到集合当中,然后返回集合
*/
//创建集合对象
ArrayList<T> list = new ArrayList<T>();//Ctrl+shift+O引入包
while(rs.next()) {//查询多条记录,这里将if换成while
// 如下述所言这里造对象
T t=clazz.newInstance();
/**
* 现在是查一条数据,若是查多条可以写成while(数据库表中的行) 获取结果集列数,根据结果集列数来判断需要读取多少个字段值
* 列数封装在rs的元数据中,在if之前使用getMetaData()方法获取结果集的元数据 通过ResultSetMetaData获取结果集的列数
*/
// 处理结果集一行数据中的每一个列:给t对象指定属性赋值
for (int i = 0; i < columnCount; i++) {// 根据列数(数据库表中的列),循环读取属性字段值
// 获取列值
Object columValue = rs.getObject(i + 1);
/**
* 将数据封装到一个对象当中,对象需要给属性们附上所查到的值;构造对象
* 先用空参构造器造一个对象,根据查询属性字段通过Set方法将值设置进去(推荐)
* 对象造一次故不能写到for里,最好写到if里,for外;不写到if里因为可能存在没有结果集却造了个对象的情况
*/
/**
* 现在需要给cust这个对象指定的某一个属性名赋值为value 需要获取结果集当中的列名,将这个列名对应的同名的属性赋值为相应的value
*/
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i+1);// 获取i+1这列
// 将cust这个对象叫columnName这个名的属性赋值为columValue这个值,通过反射
/**
* Customer类中找叫columnName的属性,把那个属性对应cust对象的值赋值为columValue 即调用运行时类的指定属性用户值的操作
*/
//Field field = Customer.class.getDeclaredField(columnName);// 先将叫这个名的属性拿到
Field field = clazz.getDeclaredField(columnLabel);// 先将叫这个名的属性拿到
field.setAccessible(true);// 拿到的这个属性可能是私有的,将它变得能访问
field.set(t, columValue);// 将这个属性名的值设置给当前的cust,赋值为columValue
}
list.add(t);//将对象加入集合
}
return list;//整个while循环结束后,在这里返回集合
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//针对于组函数的通用查询特殊值的方法(查个数等)
public <E>E getValue(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
if(rs.next()) {
rs.getObject(1);
//返回值为泛型
return (E) rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
以下代码是CustomerDAO的接口
package jdbcstu2.dao;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import jdbcstu2.bean.Customer;//无参构造器,带参构造器,get、set方法,toString方法
/*
* 此接口用于规范针对于customers表的常用操作
*/
public interface CustomerDAO {
/* 定义抽象方法,针对于Customer表可以做些什么呢?
* 1.insert的一条数据
* 2.等等
* 具体看你有什么诉求就提供什么功能
*/
/**
*
* @Description 将cust对象添加到数据库中
* @author Dell
* @version
* @date 2021年4月6日 下午7:32:10
* @param conn
* @param cust
*/
public abstract void insert(Connection conn,Customer cust);
/**
*
* @Description 针对指定的ID删除表中的一条记录
* @author Dell
* @version
* @date 2021年4月6日 下午7:34:07
* @param conn
* @param cust
*/
public abstract void deleteById(Connection conn,int id);
/**
*
* @Description 针对于内存中的cust对象去修改数据表中的记录
* Java层面所new的Customer对象的ID是多少就将表中对应ID的属性改成java对象的属性(改其对应ID的记录)
* 把指定ID的这一条记录改成新的对象
* @author Dell
* @version
* @date 2021年4月6日 下午7:36:06
* @param conn
* @param id
* @param cust
*/
public abstract void update(Connection conn,Customer cust);
/**
*
* @Description 针对指定的ID查询得到对应的Customer对象
* @author Dell
* @version
* @date 2021年4月6日 下午7:42:42
* @param conn
* @param id
*/
public abstract Customer getCustomerById(Connection conn,int id);
/**
*
* @Description 查询表中的所有记录构成的集合
* @author Dell
* @version
* @date 2021年4月6日 下午7:45:30
* @param conn
* @return
*/
public abstract List<Customer> getAll(Connection conn);
/**
*
* @Description 返回数据表中的数据条目数
* @author Dell
* @version
* @date 2021年4月6日 下午7:48:33
* @param conn
* @return
*/
public abstract Long getCount(Connection conn);
/**
*
* @Description 返回数据表中最大的生日(数值最大)
* @author Dell
* @version
* @date 2021年4月6日 下午7:51:18
* @param conn
* @return
*/
public abstract Date getMaxBirth(Connection conn);
}
如下为CustomerDAO接口的实现类
package jdbcstu2.dao;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import jdbcstu2.bean.Customer;
//重写实现这些方法
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
@Override
public void insert(Connection conn, Customer cust) {
String sql="insert into customers(name,email,birth)values(?,?,?)";
update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql="delete from customers where id=?";
update(conn, sql, id);
}
@Override
public void update(Connection conn, Customer cust) {
String sql="update customers set name=?,email=?,birth=? where id=?";
update(conn, sql, cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql="select id,name,email,birth from customers where id=?";
Customer customer = getInstance(conn, Customer.class, sql, id);
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql="select id,name,email,birth from customers";
List<Customer> list = getForList(conn, Customer.class, sql);
return list;
}
@Override
public Long getCount(Connection conn) {
String sql="select count(*) from customers";
return getValue(conn, sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql="select max(birth) from customers";
return getValue(conn, sql);
}
}
测试实现类中的方法,防止某个select写错没有发现之类的
- 新建原包名.junit(针对于该包的测试)
- 包中选择新建JUnit Test Case
- name处写上将要测试的类名Test
- Class under test处用来指明到底要测谁(Browers后写上要测的类名)
- 选择next,勾选要测试的方法(如该类下的所有方法);然后Finish
- 此时已经自动把方法名写上再改写即可
以下为测试类
package jdbcstu2.dao.junit;
import static org.junit.Assert.*;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import org.junit.Test;
import jdbcstu1.util.JDBCUtils;
import jdbcstu2.bean.Customer;
import jdbcstu2.dao.CustomerDAOImpl;
/**
*
* @Description 测试CustomerDAOImpl.java中所有方法
* @author Dell
* @version
* @date 2021年4月6日 下午9:02:38
*/
public class CustomerDAOImplTest {
//先创造一个CustomerDAOImpl类的对象
private CustomerDAOImpl dao = new CustomerDAOImpl();//就不对外暴露了
@Test
public void testInsert() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust = new Customer(1,"林子风", "zifeng@126.com", new Date(4555614135668L));
dao.insert(conn,cust);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testDeleteById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
dao.deleteById(conn, 13);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testUpdateConnectionCustomer() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust= new Customer(18,"贝多芬","beiduofen@126.com",new Date(4512653235L));
dao.update(conn, cust);
System.out.println("修改成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetCustomerById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer cust = dao.getCustomerById(conn, 20);
System.out.println(cust);
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetAll() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
List<Customer> list = dao.getAll(conn);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetCount() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Long count = dao.getCount(conn);
System.out.println("表中的记录数为:"+count);
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testGetMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Date maxBirth = dao.getMaxBirth(conn);
System.out.println("最大的生日为:"+maxBirth);
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
}
2.1 BaseDAO优化(也可以不用优化)
CustomerDAOImpl继承BaseDAO的后面指明要操作的是哪个表对应过来的类,在这里即
在CustomerDAO接口实现类中的方法实参中删除Customer.class参数,牵扯到获取父类泛型问题
实现类如下所示
package jdbcstu3.dao;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
import jdbcstu.bean.Customer;
//重写实现这些方法
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {
//优化后:CustomerDAOImpl继承BaseDAO的后面指明要操作的是哪个表对应过来的类,在这里即<Customer>
@Override
public void insert(Connection conn, Customer cust) {
String sql="insert into customers(name,email,birth)values(?,?,?)";
update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql="delete from customers where id=?";
update(conn, sql, id);
}
@Override
public void update(Connection conn, Customer cust) {
String sql="update customers set name=?,email=?,birth=? where id=?";
update(conn, sql, cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql="select id,name,email,birth from customers where id=?";
Customer customer = getInstance(conn,sql, id);//优化后:删除Customer.class参数
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql="select id,name,email,birth from customers";
List<Customer> list = getForList(conn,sql);//优化后:删除Customer.class参数
return list;
}
@Override
public Long getCount(Connection conn) {
String sql="select count(*) from customers";
return getValue(conn, sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql="select max(birth) from customers";
return getValue(conn, sql);
}
}
- 增加代码块获取子类(当前)对象的父类的泛型
- 这个代码块一定是要在创建对象之前执行的
- 这个代码块可以写到Customer接口类中,也可以写到BaseDAO中。但写到BaseDAO中最好-
- 同时并更改方法名中前的泛型,以及方法中形参Class clazz;类名中新增泛型参数;成员新增声明clazz;删除方法名中的。
- 当前谁的对象调用的这个或者说当前new的是谁的对象,this就是指谁的对象
如下所示的新增代码块后的BaseDAO
package jdbcstu3.dao;
//优化dao;反射之获取父类泛型
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import jdbcstu1.util.JDBCUtils;
/*
* DAO:data(base) access object
* 封装了针对于数据表的通用的操作
*/
/*
* 仅提供通用方法,不会去造它的对象
* 故abstract修饰此类,表示仅是抽象类,不能对它实例化
* 这是基础父类,针对于具体的表会造它具体的DAO然后去继承于这个BaseDAO
* 但为了代码规范,一般先造一个接口
*/
public abstract class BaseDAO<T> {//优化后新增了泛型参数<T>
private Class<T> clazz=null;//优化后新增,但是null肯定不靠谱,需要对clazz做实例化。
/* 实例化为谁取决于子类实现BaseDAO时父类的泛型是谁。
* 如果在每一个子类在实例化前都写一个获取父类的泛型,有些麻烦。
* 故:将获取父类泛型的这段代码写到父类中通过继承子类就能获得了
* 写到父类中对这个属性进行赋值,保证在调低下的方法之前clazz要有值
* 调方法是通过对象来调的,即在获取对象前clazz属性有值就行
* 出现对象之前可以给属性赋值的位置有:1.显示赋值(麻烦,需要写多行代码,要重做个方法) 2.代码块中赋值 3.构造器中实例化
* 我们在这里选择代码块的方式,注意:一定是非静态的;因为属性的声明是非静态的,这里如果写成静态的将不能调用(静态结构中不能调用非静态的)
* 在测试中在new子类对象时,子类对象构造器中有super空就会加载父类结构,加载父类结构时就先后把构造器和代码块就加载了,在执行时就要获取当前对象父类的泛型
*/
//构造器中对clazz实例化(需要在造对象之前进行实例化)
// public BaseDAO() {
//
// }
//写一个代码块,获取当前对象的父类的泛型
//获取当前BaseDAO的子类继承父类中的泛型
{
Type genericSuperclass = this.getClass().getGenericSuperclass();//这个this仍然是子类的对象。得到这个类的带泛型的父类(这是Type类型的)
//这个this虽然在父类方法中,但这个this仍然是子类的对象,因为是子类对象去调用new的是子类的对象,只new了子类的对象也没有new父类的对象。因此这个this仍然是子类的对象。(造的是子类对象,这个this还是子类对象)
//当前调用的new的是谁的对象,this就是指谁的对象;这里只new了子类的对象也没有new父类的对象,因此这个this仍然是子类的对象。(本例中这个父类是抽象类也不能new对象)
//当前谁的对象调用的这个或者说当前new的是谁的对象,this就是指谁的对象
//this.getClass先获取当前这个类(当前对象);
//this.getClass().getGenericSuperclass();获取当前这个类带泛型的父类
ParameterizedType paramType=(ParameterizedType)genericSuperclass;//做强转,带参数的Type
Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数(有多个,返回的是数组)
clazz=(Class<T>) typeArguments[0];//泛型的第一个参数即Customer,再将它赋值给clazz即可。同时赋值前再做一个强转。
}
//考虑了事务的增删改
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//2.预编译sql语句返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);//小心参数声明错误
}
//4.执行
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
//5.资源的关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
//通用的查询操作用于返回数据表中的一条记录(version 2.0 考虑上事务)
public T getInstance(Connection conn,String sql, Object... args) {// 获取一个对象实例,泛型方法T是对应的运行时类。返回值类型是T,T是一个参数
//优化后:删除形参中Class<T> clazz; 并删除方法名T前的<T>,因为声明了父类的泛型这里不再是泛型方法。
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]);
}
rs = ps.executeQuery();
// 获取结果集的元数据(修饰现有数据的数据)
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t=clazz.newInstance();
//优化后:在删除了删除形参中Class<T> clazz后,这里会报错,因为这里要用clazz却没有了,故要获取这个clazz到底是谁?如何获取?----这个clazz在哪出现呢?------在实现类名处声明时的父类的泛型中出现。即获取当前这个类的父类的泛型。让它对父类中的clazz做实例化。
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {// 根据列数(数据库表中的列),循环读取属性字段值
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i+1);// 获取i+1这列
Field field = clazz.getDeclaredField(columnLabel);// 先将叫这个名的属性拿到
field.setAccessible(true);// 拿到的这个属性可能是私有的,将它变得能访问
field.set(t, columValue);// 将这个属性名的值设置给当前的cust,赋值为columValue
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
// 如果没附上值,则返回null
}
return null;
// 再去掉throws用try catch以及用finally,赋值为null
}
//通用的查询操作,用于返回表中多条记录构成的集合 version2(考虑上事务的)
public List<T> getForList(Connection conn,String sql, Object... args){//ctrl+shift+O选择util下的list
//优化后:删除形参中Class<T> clazz,并删除方法名T前的<T>,因为声明了父类的泛型这里不再是泛型方法。
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据(修饰现有数据的数据)
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
/**
* 造一个集合将对象放到集合当中,然后返回集合
*/
//创建集合对象
ArrayList<T> list = new ArrayList<T>();//Ctrl+shift+O引入包
while(rs.next()) {//查询多条记录,这里将if换成while
// 如下述所言这里造对象
T t=clazz.newInstance();
/**
* 现在是查一条数据,若是查多条可以写成while(数据库表中的行) 获取结果集列数,根据结果集列数来判断需要读取多少个字段值
* 列数封装在rs的元数据中,在if之前使用getMetaData()方法获取结果集的元数据 通过ResultSetMetaData获取结果集的列数
*/
// 处理结果集一行数据中的每一个列:给t对象指定属性赋值
for (int i = 0; i < columnCount; i++) {// 根据列数(数据库表中的列),循环读取属性字段值
// 获取列值
Object columValue = rs.getObject(i + 1);
/**
* 将数据封装到一个对象当中,对象需要给属性们附上所查到的值;构造对象
* 先用空参构造器造一个对象,根据查询属性字段通过Set方法将值设置进去(推荐)
* 对象造一次故不能写到for里,最好写到if里,for外;不写到if里因为可能存在没有结果集却造了个对象的情况
*/
/**
* 现在需要给cust这个对象指定的某一个属性名赋值为value 需要获取结果集当中的列名,将这个列名对应的同名的属性赋值为相应的value
*/
// 获取每个列的列名
String columnLabel = rsmd.getColumnLabel(i+1);// 获取i+1这列
// 将cust这个对象叫columnName这个名的属性赋值为columValue这个值,通过反射
/**
* Customer类中找叫columnName的属性,把那个属性对应cust对象的值赋值为columValue 即调用运行时类的指定属性用户值的操作
*/
//Field field = Customer.class.getDeclaredField(columnName);// 先将叫这个名的属性拿到
Field field = clazz.getDeclaredField(columnLabel);// 先将叫这个名的属性拿到
field.setAccessible(true);// 拿到的这个属性可能是私有的,将它变得能访问
field.set(t, columValue);// 将这个属性名的值设置给当前的cust,赋值为columValue
}
list.add(t);//将对象加入集合
}
return list;//整个while循环结束后,在这里返回集合
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//针对于组函数的通用查询特殊值的方法(查个数等)
public <E>E getValue(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
if(rs.next()) {
rs.getObject(1);
//返回值为泛型
return (E) rs.getObject(1);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
3. 数据库连接池
3.1 C3P0
需要导入第三方jar包(先放到lib文件下,再选中右键添加build path)
c3p0test.java
package jdbcstu4.connection;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
public class C3P0test {
//方式一:这是将配置信息写到代码中的硬编码方式(不提倡)
@Test
public void testGetConnection() throws Exception{
//获取c3p0数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource();//DataSource接口的具体实现类
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //填写jdbc driver的路径
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("admin");
//通过设置相关的参数,对数据库连接池进行管理
cpds.setInitialPoolSize(10);//这里设置的初始时数据库连接池中的连接数
Connection conn = cpds.getConnection();
System.out.println(conn);
//DataSources.destroy( cpds );//销毁c3p0数据库连接池(一般情况下也不会关闭数据库连接池的)
}
//方式二:使用配置文件
@Test
public void testGetConnection1() throws SQLException {
ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致
Connection conn = cpds.getConnection();
System.out.println(conn);
}
}
使用.xml文件的方式写数据库配置数据。xml一般用来给前端传送数据或者写配置
src下右键new一个xml文件。文件名为c3p0-config.xml
创建好.xml文件是表格样式的可以在底下点source
可从官方文档那里粘过来.xml写法例子。删掉默认配置部分、用户重写部分。
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="hellc3p0"><!-- 文件名 -->
<!--提供获取连接的4个基本信息-->
<property name="driverClass">com.mysql.jdbc.Driver</property><!-- 这里的名字一定要一致 -->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property><!-- 若连本地且默认端口为3306,可写为jdbc:mysql:///test -->
<property name="user">root</property>
<property name="password">admin</property>
<!--进行数据库连接池管理的基本信息-->
<!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 -->
<property name="acquireIncrement">5</property>
<!-- c3p0数据库连接池中初始化时的连接数 -->
<property name="initialPoolSize">10</property>
<!-- c3p0数据库连接池中维护的最少连接数 -->
<property name="minPoolSize">10</property>
<!-- c3p0数据库连接池中维护的最多的连接数 -->
<property name="maxPoolSize">100</property>
<!-- c3p0数据库连接池中最多维护的Statement的个数 -->
<property name="maxStatements">50</property>
<!-- 每个连接中可以最多使用的Statement的个数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
JDBCUtils.java文件如下
package jdbcstu4.util;
import java.sql.Connection;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
/**
*
* @Description 使用c3p0的数据库连接技术
* @author Dell
* @version
* @date 2021年4月7日 下午8:37:39
* @return
* @throws SQLException
*/
//这个放到这里较好,并且前面加上private static
//因为如果放到里面,每次调用getConnection时就会new一个。但它new一个就行(数据库连接池仅需提供一个即可)
private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致;
public static Connection getConnection1() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
}
3.2 DBCP
- DBCP需要导入两个jar包:commons-dbcp-1.4.jar和commons-pool-1.5.5.jar
- XXXXFactory是造XXXX类的对象的
DBCPTest.java文件如下
package jdbcstu4.connection;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;
public class DBCPTest {
/**
*
* @Description 测试DBCP的数据库连接池技术
* @author Dell
* @version
* @throws SQLException
* @date 2021年4月7日 下午9:11:20
*/
//方式一:不推荐
@Test
public void testGetConnection() throws SQLException {
//创建了DBCP的数据库连接池
BasicDataSource source=new BasicDataSource();//在这一行ctrl+T可以看到它具体的实现类(可以看到有BasicDataSource,则可以new一个(其要有空参构造器,有的))
//设置基本信息
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql:///test");
source.setUsername("root");
source.setPassword("admin");
//还可以设置其他涉及数据库连接池管理的相关属性
source.setInitialSize(10);
source.setMaxActive(10);
//等等
Connection conn = source.getConnection();
System.out.println(conn);
}
//方式二:推荐:使用配置文件
@Test
public void testGetConnection1() throws Exception {
Properties pros = new Properties();
//加载配置文件的方式1:类的加载器
//InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
//方式2:
FileInputStream is=new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载一个流
DataSource source = BasicDataSourceFactory.createDataSource(pros);
//出现arg0是因为没有导入源码(按下ctrl键不松手,再将鼠标移到该函数位置选择Open Declaration(或直接进入)
//进入后选择Attach Source,选择External location选择commons-dbcp-1.4-src.zip)
Connection conn = source.getConnection();
System.out.println(conn);
}
}
dbcp.properties文件,(一定要是位于src下)
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///test
username=root
password=admin
initialSize=10
JDBCUtils.java文件如下
package com.atguigu4.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
/**
*
* @Description 使用c3p0的数据库连接技术
* @author Dell
* @version
* @date 2021年4月7日 下午8:37:39
* @return
* @throws SQLException
*/
//这个放到这里较好,并且前面加上private static
//因为如果放到里面,每次调用getConnection时就会new一个。但它new一个就行(数据库连接池仅需提供一个即可)
private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致;
public static Connection getConnection1() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
/**
*
* @Description 使用DBCP数据库连接池技术获取数据库连接
* @author Dell
* @version
* @date 2021年4月7日 下午10:10:27
* @return
* @throws Exception
*/
//同样的(数据库连接池仅需提供一个即可)因此考虑使用静态代码块
private static DataSource source;
static {
try {
//创建一个DBCP数据库连接池
Properties pros = new Properties();
//方式2:
FileInputStream is=new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载一个流
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection2 () throws Exception {
Connection conn = source.getConnection();
return conn;
}
}
3.3 Druid(德鲁伊)数据库连接池
首先加载驱动
DruidTest.java文件如下
package jdbcstu4.connection;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class DruidTest {
@Test
public void getConnection() 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);
}
}
Druid.properties文件如下
url=jdbc:mysql:///test
username=root
password=admin
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=10
JDBCUtils.java文件如下
package com.atguigu4.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
/**
*
* @Description 使用c3p0的数据库连接技术
* @author Dell
* @version
* @date 2021年4月7日 下午8:37:39
* @return
* @throws SQLException
*/
//这个放到这里较好,并且前面加上private static
//因为如果放到里面,每次调用getConnection时就会new一个。但它new一个就行(数据库连接池仅需提供一个即可)
private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致;
public static Connection getConnection1() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
/**
*
* @Description 使用DBCP数据库连接池技术获取数据库连接
* @author Dell
* @version
* @date 2021年4月7日 下午10:10:27
* @return
* @throws Exception
*/
//同样的(数据库连接池仅需提供一个即可)因此考虑使用静态代码块
private static DataSource source;
static {
try {
//创建一个DBCP数据库连接池
Properties pros = new Properties();
//方式2:
FileInputStream is=new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载一个流
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection2 () throws Exception {
Connection conn = source.getConnection();
return conn;
}
/**
* 使用Druid数据库连接池技术
*/
private static DataSource source1;
static {
try {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
source1 = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws SQLException {
Connection conn = source1.getConnection();
return conn;
}
}
4. Apache-DBUtils实现CRUD操作(增删改查)
首先,导入jar包(commons-dbutils-1.3);Apache提供封装了jdbc增删改查的工具类库
QueryRunnerTest.java文件
package jdbcstu5.dbutils;
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
import jdbcstu2.bean.Customer;
import jdbcstu4.util.JDBCUtils;
public class QueryRunnerTest {
//测试插入
@Test
public void testInsert(){
//调用下面方法时出现args0了,需要关联源码(commons-dbutils-1.3-src.zip)
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="insert into customers(name,email,birth)values(?,?,?)";
int insertCount = runner.update(conn, sql, "张果果","guoguo@126.com","1998-05-12");//牵扯到事务类就选择传入连接的
System.out.println("添加了"+insertCount+"条记录");
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
//测试查询
/*
* BeanHandler:是ResultSetHandler接口的实现类,用于封装表中的一条记录。(返回一个对象)
*/
@Test
public void testQuery1() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
//本例中想要封装的是Customer类,它没有空参构造器需要传一下,参数要写Customer.class
BeanHandler<Customer> handler=new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn, sql, handler, 25);//第三个参数是结果集的处理器(这是一个接口所以只能传它的具体实现类);
//如需要返回一个对象,则根据官方文档可看,可以使用BeanHandler<T>实现类(带泛型,看你具体想封装的是哪一个类了)
System.out.println(customer);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
*BeanListHandler是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。
*/
@Test
public void testQuery2() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id<?";
BeanListHandler<Customer> handler=new BeanListHandler<>(Customer.class);
List<Customer> list = runner.query(conn, sql, handler, 25);//第三个参数是结果集的处理器(这是一个接口所以只能传它的具体实现类);
list.forEach(System.out::println);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
*MapHandler是ResultSetHandler接口的实现类,对应表中的一条记录
*将字段及相应字段的值作为map中的key和value。(返回结果是键值对map的形式,而不是对象)
*/
@Test
public void testQuery3() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
//看文档中没有泛型且有空参构造器
MapHandler handler=new MapHandler();
Map<String, Object> map = runner.query(conn, sql, handler, 25);
System.out.println(map);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
*MapListHandler是ResultSetHandler接口的实现类,对应表中的多条记录
*将字段及相应字段的值作为map中的key和value。将这些map添加到list中(返回结果是键值对map的形式,而不是对象)
*/
@Test
public void testQuery4() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id<?";
//看文档中没有泛型且有空参构造器
MapListHandler handler=new MapListHandler();
List<Map<String,Object>> list = runner.query(conn, sql, handler, 25);
list.forEach(System.out::println);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
* 查看表中记录个数(特殊的查询)
*/
@Test
public void testQuery5() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select count(*) from customers";
//看文档中没有泛型且有空参构造器
ScalarHandler handler = new ScalarHandler();
Long count=(Long) runner.query(conn, sql, handler);
System.out.println(count);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
* ScalarHandler:用于查询特殊值
* 查看最大生日(特殊的查询)
*/
@Test
public void testQuery6() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select max(birth) from customers";
//看文档中没有泛型且有空参构造器
ScalarHandler handler = new ScalarHandler();
Date maxBirth=(Date) runner.query(conn, sql, handler);
System.out.println(maxBirth);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
/*
* 当没有能满足的实现类时,可以自己写一个实现类
* 自定义ResultSetHandler的实现类
*/
@Test
public void testQuery7() {
Connection conn = null;
try {
QueryRunner runner=new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
//自己写一个handler(匿名实现类)
//假设写一个单条语句查询
ResultSetHandler<Customer> handler=new ResultSetHandler<Customer>(){
@Override
public Customer handle(ResultSet rs) throws SQLException {
if(rs.next()) {
int id=rs.getInt("id");
String name=rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer customer = new Customer(id, name, email, birth);
return customer;
}
return null;
}
};
Customer customer = runner.query(conn, sql, handler,25);
System.out.println(customer);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtils.closeResource(conn, null);
}
}
}
jdbcstu4.util.JDBCUtils文件
package jdbcstu4.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
/**
*
* @Description 使用c3p0的数据库连接技术
* @author Dell
* @version
* @date 2021年4月7日 下午8:37:39
* @return
* @throws SQLException
*/
//这个放到这里较好,并且前面加上private static
//因为如果放到里面,每次调用getConnection时就会new一个。但它new一个就行(数据库连接池仅需提供一个即可)
private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致;
public static Connection getConnection1() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
/**
*
* @Description 使用DBCP数据库连接池技术获取数据库连接
* @author Dell
* @version
* @date 2021年4月7日 下午10:10:27
* @return
* @throws Exception
*/
//同样的(数据库连接池仅需提供一个即可)因此考虑使用静态代码块
private static DataSource source;
static {
try {
//创建一个DBCP数据库连接池
Properties pros = new Properties();
//方式2:
FileInputStream is=new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载一个流
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection2 () throws Exception {
Connection conn = source.getConnection();
return conn;
}
/**
* 使用Druid数据库连接池技术
*/
private static DataSource source1;
static {
try {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
source1 = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws SQLException {
Connection conn = source1.getConnection();
return conn;
}
/**
*
* 以下是原来的自己写的方法
*/
//静态方法,返回的是一个连接,方法名为getConnection
public static Connection getConnection() throws Exception {
//1.读取配置文件中四个基本信息;类.class.方法即类的加载器中系统加载器.方法(文件名)
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//ctrl+1生成流
Properties pros = new Properties();
pros.load(is);//通过pros加载文件
String user=pros.getProperty("user");//通过这个来读取
String password=pros.getProperty("password");
String url=pros.getProperty("url");
String driverClass=pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void closeResource(Connection conn,Statement ps) {//PreparedStatement,这里写成Statement大一点,PreparedStatement也能放
/**
* 关闭连接和statement操作
*/
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
}
/**
*
* @Description关闭资源操作(连接、Statement、结果集)
* @version
* @date 2021年3月31日 下午8:49:18
* @param conn
* @param ps
* @param rs
*/
public static void closeResource(Connection conn,Statement ps,ResultSet rs) {//多一个参数构成方法的重载,需要导入sql下的包(面向接口编程不出现其他第三方API)
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
try {
if(rs!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.DbUtils类关闭资源的方式
JDBCUtils.java文件
package jdbcstu4.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.dbutils.DbUtils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
public class JDBCUtils {
/**
*
* @Description 使用c3p0的数据库连接技术
* @author Dell
* @version
* @date 2021年4月7日 下午8:37:39
* @return
* @throws SQLException
*/
//这个放到这里较好,并且前面加上private static
//因为如果放到里面,每次调用getConnection时就会new一个。但它new一个就行(数据库连接池仅需提供一个即可)
private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0");//与配置文件中自定义写的名字一致;
public static Connection getConnection1() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
/**
*
* @Description 使用DBCP数据库连接池技术获取数据库连接
* @author Dell
* @version
* @date 2021年4月7日 下午10:10:27
* @return
* @throws Exception
*/
//同样的(数据库连接池仅需提供一个即可)因此考虑使用静态代码块
private static DataSource source;
static {
try {
//创建一个DBCP数据库连接池
Properties pros = new Properties();
//方式2:
FileInputStream is=new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);//加载一个流
source = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection2 () throws Exception {
Connection conn = source.getConnection();
return conn;
}
/**
* 使用Druid数据库连接池技术
*/
private static DataSource source1;
static {
try {
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
source1 = DruidDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws SQLException {
Connection conn = source1.getConnection();
return conn;
}
//静态方法,返回的是一个连接,方法名为getConnection
public static Connection getConnection() throws Exception {
//1.读取配置文件中四个基本信息;类.class.方法即类的加载器中系统加载器.方法(文件名)
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//ctrl+1生成流
Properties pros = new Properties();
pros.load(is);//通过pros加载文件
String user=pros.getProperty("user");//通过这个来读取
String password=pros.getProperty("password");
String url=pros.getProperty("url");
String driverClass=pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
public static void closeResource(Connection conn,Statement ps) {//PreparedStatement,这里写成Statement大一点,PreparedStatement也能放
/**
* 关闭连接和statement操作
*/
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
}
/**
*
* @Description关闭资源操作(连接、Statement、结果集)
* @version
* @date 2021年3月31日 下午8:49:18
* @param conn
* @param ps
* @param rs
*/
public static void closeResource(Connection conn,Statement ps,ResultSet rs) {//多一个参数构成方法的重载,需要导入sql下的包(面向接口编程不出现其他第三方API)
try {
if(ps!=null)//避免空指针问题,因为对象没有创建报异常,finally这里进行关闭操作,从而出现空指针的调用,因此加以判断,避免空指针问题
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭执行实例
try {
if(conn!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//关闭连接,(关闭了资源则可以不用再抛出异常,对上面的大块进行统一try catch,再还要对两个关闭进行try catch)
try {
if(rs!=null)//避免空指针问题,因为获取连接的时候报异常,对象没拿到却仍然在finally这里进行关闭操作,因此加以判断,避免空指针问题
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
* @Description 使用dbutils.jar中提供的DbUtils工具类,实现资源的关闭
* @author Dell
* @version
* @date 2021年4月8日 下午9:35:18
* @param conn
* @param ps
* @param rs
*/
//方法一
public static void closeResource1(Connection conn,Statement ps,ResultSet rs) {//多一个参数构成方法的重载,需要导入sql下的包(面向接口编程不出现其他第三方API)
// try {
// DbUtils.close(conn);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// DbUtils.close(ps);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// DbUtils.close(rs);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
//
//方法二
DbUtils.closeQuietly(conn);
DbUtils.closeQuietly(ps);
DbUtils.closeQuietly(rs);
}
}