软件设计之JDBC(3)
此篇应在MySQL之后进行学习:
路线图推荐:
【Java学习路线-极速版】【Java架构师技术图谱】
尚硅谷2024最新JDBC教程 | jdbc基础到高级一套通关!
资料可以去尚硅谷官网免费领取
学习内容:
- JDBC优化及工具类的封装
- ThreadLocal概述
- DAO概念及搭建
- 事务的概述
1、JDBC优化及工具类的封装
JDBC过程中,部分代码存在冗余问题:创建连接池、获取连接、连接的回收
因此将创建连接池、获取连接、连接的回收这三步封装为一个类,提供静态代码块初始连接池对象,提供获取连接、回收连接的静态方法。
package com.atguigu.senior.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.atguigu.advanced.pool.DruidTest;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/*
* 1 维护一个连接池对象
* 2 对外提供在连接池中获取连接的方法
* 3 对外提供回收连接的方法
* 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法
* */
public class JDBCUtil {
//创建连接池引用
private static DataSource dataSource;
//在项目启动时,即创建连接池对象,赋值给dataSource
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//对外提供在连接池中获取连接的方法
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//对外提供回收连接的方法
public static void release(Connection connection){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
2、ThreadLocal概述
针对同一用户线程多次操作获取多个连接,造成连接资源的浪费,利用ThreadLocal进行优化
/*
* 1 维护一个连接池对象,同时维护了一个线程绑定变量的ThreadLocal对象
* 2 对外提供在ThreadLocal中获取连接的方法
* 3 对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除
* 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法
* */
public class JDBCUtilV2 {
//创建连接池引用
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//在项目启动时,即创建连接池对象,赋值给dataSource
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//对外提供在连接池中获取连接的方法
public static Connection getConnection(){
try {
//在ThreadLocal中获取Connection
Connection connection = threadLocal.get();
//ThreadLocal里没有Connection,也就是第一次获取
if (connection == null) {
//在连接池中获取一个连接,存储在ThreadLocal
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//对外提供回收连接的方法
public static void release(){
try {
Connection connection = threadLocal.get();
if (connection!=null){
//从threadlocal中移除当前已经存储的Connection对象
threadLocal.remove();
//将Connection对象归还给连接池
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
3、DAO概念及搭建
DAO:
Data Access Object 数据访问对象
一张表对应一个实体类,一张表的操作对应一个DAO对象
在Java操作数据库时,将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层
DAO层只关注对数据库的操作,供业务层的Service调用
接口设计
/*
* EmployeeDao这个类对应的是t_emp这张表的增删改查操作
* */
public interface EmployeeDao {
/*
* 数据库对应的查询所有操作
* @return 表中所有的数据
* */
List<Employee> selectAll();
/*
* 数据库对应的根据empId查询单个员工数据操作
* @param empId 主键列
* @return 一个员工对象(一行数据)
* */
Employee selectByEmpId(Integer empId);
/*
* 数据库对应的新增一条员工数据
* @param employee ORM思想中的一个员工对象
* @return 受影响行数
* */
int insert(Employee employee);
/*
* 数据库对应的修改一条员工数据
* @param employee ORM思想中的一个员工对象
* @return 受影响行数
* */
int update(Employee employee);
/*
* 数据库对应的根据empId删除一条员工数据
* @param empId 主键列
* @return 受影响行数
* */
int delete(Integer empId);
}
BaseDAO
BaseDAO方法搭建
/*
* 将共性的数据库操作代码封装在BaseDAO里
* */
public class BaseDAO {
/*
* 通过的增删改的方法
* @parma sql调用者要执行的SQL语句
* @parma SQL语句中的占位符要赋值的参数
* @return 受影响行数
* */
public int executeUpdate(String sql,Object... params) throws Exception{
//通过JDBCUtilV2获取数据库连接
Connection connection = JDBCUtilV2.getConnection();
//预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//为占位符赋值,执行SQL,接收返回结果
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
//占位符是从1开始,数组是从0开始
preparedStatement.setObject(i+1,params[i]);
}
}
int row = preparedStatement.executeUpdate();
//释放资源
preparedStatement.close();
JDBCUtilV2.release();
//返回结果
return row;
}
/*
* 通用的查询:多行多列、单行多列、单行单列
* 多行多列 List<Employee>
* 单行多列 Employee
* 单行单列 封装的是一个结果 Double、Integer...
* 封装过程:
* 返回的类型:泛型:类型不确定,调用者知道,调用时将此次查询结果类型告知BaseDAO
* 返回的结果:通用 List 可以存储多个结果,也可以存储一个结果 get(0)
* 结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象 Class
* */
public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params)throws Exception{
//通过JDBCUtilV2获取数据库连接
Connection connection = JDBCUtilV2.getConnection();
//预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//为占位符赋值,执行SQL,接收返回结果
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
//占位符是从1开始,数组是从0开始
preparedStatement.setObject(i+1,params[i]);
}
}
//执行SQL,并接收返回的结果集
ResultSet resultSet = preparedStatement.executeQuery();
//获取结果集中的元数据对象
//其中包含了列的数量、每个列的名称
ResultSetMetaData metaData = resultSet.getMetaData();
int columCount = metaData.getColumnCount();
List<T> list = new ArrayList<>();
//处理结果
while (resultSet.next()){
// T t = clazz.newInstance();已弃用
//getDeclaredConstructor()方法会根据他的参数对该类的构造函数进行搜索并返回对应的构造函数,没有参数就返回该类的无参构造函数,然后再通过newInstance进行实例化。
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <=columCount; i++) {
//通过下标获取列的值
Object value = resultSet.getObject(i);
//获取到的列的value值,这个值就是t这个对象中的某一个属性
//获取当前拿到的列的名字 = 对象的属性名
String fieldName = metaData.getColumnLabel(i);
//通过类对象和fieldName获取要封装的对象的属性
Field field = clazz.getDeclaredField(fieldName);
//突破封装的private
field.setAccessible(true);
field.set(t,value);
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
JDBCUtilV2.release();
return list;
}
/*
* 通用查询:在上面查询的集合结果中获取第一个结果*/
public <T> T executeQueryBean(Class<T> clazz,String sql,Object... params)throws Exception{
List<T> list = this.executeQuery(clazz, sql, params);
if (list==null || list.size()==0){
return null;
}
return list.get(0);
}
}
实现接口
public class EmployeeDaoImpl extends BaseDAO implements EmployeeDao {
@Override
public List<Employee> selectAll() {
try {
String sql = "SELECT emp_id empId ,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp";
return executeQuery(Employee.class,sql,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Employee selectByEmpId(Integer empId) {
try {
String sql = "SELECT emp_id empId ,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp WHERE emp_id = ?";
return executeQueryBean(Employee.class,sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int insert(Employee employee) {
try {
String sql = "INSERT INTO t_emp(emp_name,emp_salary,emp_age) VALUES(?,?,?)";
return executeUpdate(sql,employee.getEmpName(),employee.getEmpSalary(),employee.getEmpAge());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int update(Employee employee) {
//类似insert
}
@Override
public int delete(Integer empId) {
//类似insert
}
}
4、事务的概述
事务的特性
JDBC中事务实现
优化1:
在release函数中,需要添加一条代码connection.setAutoCommit(true);
原因1:
如果开启了事务的手动提交,操作完毕后,归还给连接池之前,要将事务的自动提交改为true
优化2:
在BaseDAO中的增删改查代码里需要在释放连接时进行判断
,如果是自动提交事务。则关闭连接,否则不执行操作
原因2:
关闭连接就代表操作已经完成了,但是手动提交事务,还会存在回滚操作,得给回滚操作预留执行代码
优化3:设计BankDao接口,继承BaseDAO和实现BankDao接口设计BankDaoImpl,主要存放加钱、减钱函数操作
public void testTransaction(){
//接口引用指向实现类对象
BankDao bankDao = new BankDaoImpl();
Connection connection = null;
try {
//获取连接
connection = JDBCUtilV2.getConnection();
connection.setAutoCommit(false);
//操作减钱
bankDao.subMoney(1,100);
int i = 10/0;
//操作加钱
bankDao.addMoney(2,100);
//前置的多次dao操作,没有异常,提交事务
connection.commit();
} catch (Exception e) {
try {
connection.rollback();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
throw new RuntimeException(e);
}finally {
//关闭连接
JDBCUtilV2.release();
}
}