jdbc工具类封装v1.0
我们封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法!
外部配置文件
位置: src/mian/resources/druid.properties
# key = value => java Properties读取(key | value)
# druid配置的key固定命名
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:13306/huan
工具类代码
package com.jdbc.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author : Aurora
* @version : 1.0
* <p>
* 建议:工具类中的方法,推荐写成静态,外部调用会更加方便!
* 实现:属性 连接池对象[实例化一次]
* 单例模式
* static{
* 全局调用一次
* }
* 方法:对外提供连接的方法。回收外部传入连接方法
* @Date : 2023/1/5
* @Describe : 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法!
*/
public class JdbcUtils {
private static DataSource dataSource = null;
static {
//初始化连接池对象
Properties properties = new Properties();
InputStream inputStream = JdbcUtils.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
//运行时异常
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
*/
public static Connection getConnection() throws SQLException {
//这么写,不能保证同一个线程,两次getConnection()得到的是同一个Connection对象
//如果不能保证是同一个连接对象,就无法保证事务的管理
return dataSource.getConnection();
}
public static void freeConnection(Connection connection) throws SQLException {
connection.close();
}
}
jdbc工具类封装v.2.0
优化工具类v1.0版本,考虑事务的情况下!如何一个线程的不同方法获取同一个连接!
ThreadLocal的介绍
JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
1、ThreadLocal对象.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal对象.remove: 移除ThreadLocal中当前线程共享变量的值。
v2.0版本工具类
package com.jdbc.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author : Aurora
* @version : 2.0
* <p>
* 建议:工具类中的方法,推荐写成静态,外部调用会更加方便!
* 实现:属性 连接池对象[实例化一次]
* 单例模式
* static{
* 全局调用一次
* }
* 方法:对外提供连接的方法。回收外部传入连接方法
* @Date : 2023/1/5
* @Describe : 内部包含一个连接池对象,并且对外提供获取连接和回收连接的方法!
* <p>
* <p>
* 利用线程本地变量,存储连接信息,确保一个线程的多个方法可以获取同一个connection!
* 优势:事务操作的时候 service 和 dao 属于同一个线程,不同再传递参数了
* 大家都建议调用getConnection() 自动获取的是相同的连接池
*/
public class JdbcUtilsV2 {
//连接池对象
private static DataSource dataSource = null;
private static ThreadLocal<Connection> t1 = new ThreadLocal<>();
static {
//初始化连接池对象
Properties properties = new Properties();
InputStream inputStream = JdbcUtilsV2.class.getClassLoader().getResourceAsStream("druid.properties");
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
//运行时异常
throw new RuntimeException(e);
}
try {
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 对外提供连接的方法
*/
public static Connection getConnection() throws SQLException {
//这么写,不能保证同一个线程,两次getConnection()得到的是同一个Connection对象
//如果不能保证是同一个连接对象,就无法保证事务的管理
//线程本地变量中是否存在
Connection connection = t1.get();
//第一次没有
if (connection == null) {
//线程本地变量没有,那就从连接池里获取
connection = dataSource.getConnection();
t1.set(connection);
}
return connection;
}
public static void freeConnection() throws SQLException {
Connection connection = t1.get();
if (connection != null) {
t1.remove(); //清空线程本地变量数据
connection.setAutoCommit(true); // 事务回滚状态
connection.close(); //线程池的连接,调用close就是回收
}
}
}
高级应用层封装BaseDao
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,我们称为BaseDao
工具类代码
package com.jdbc.utils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author : Aurora
* @Date : 2023/1/5
* @Describe : 封装dao数据库重复代码
* <p>
* 封装两个方法:
* 1,简化DQL
* 2,简化非DQL
*/
public abstract class BaseDao<T> {
/**
* 封装简化非DQL语句
*
* @param sql 带占位符的sql语句
* @param params 占位符的值
* @return 执行影响的行数
*/
public int executeUpdate(String sql, Object... params) throws SQLException {
//获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//可变参数可以当数组使用
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
//发送sql语句
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
//connection.close(); //不一定合适
//是否回收连接,需要考虑是不是事务
if (connection.getAutoCommit()) {
//没有开启事务
//没有开启事务,正常回收连接
JdbcUtilsV2.freeConnection();
}
//开启事务了,不需要连接,业务层处理
//connection.setAutoCommit(false);
return rows;
}
/**
* 非DQL语句封装方法 -> 返回值 固定为 int
* DQL语句封装方法 -> 返回值,是什么类型呢?
* 并不是list<Map> map key和value自定义!不用先设定好!
* map没有数据校验机制,不支持反射操作
* <p>
* 数据库数据 -> Java的实体类
* 表中一行 -> Java一个实体类 -> 多行 -> List<Java实体类> list;
* <p>
* DQL -> List<Map> -> 一行 -> map -> List<Map>
* <T> 声明一个泛型,不确定类型
* 1.确定泛型 User.class T = User
* 2.要使用反射技术属性赋值
* public <T> List<T> executeQuery(Class<T> clazz,String sql,Object... params);
*/
/**
* 将查询结果封装到一个实体类集合
*
* @param clazz 要接值的实体类集合的模板对象
* @param sql 查询语句,要求列名或者别名等于实体类的属性名! u_id as Uid => Uid
* @param params 占位符的值,要和 ?位置对象,进行传递
* @param <T> 声明结果的类型
* @return 查询类型集合
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (params == null && params.length != 0) {
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
}
//发送sql语句
ResultSet resultSet = preparedStatement.executeQuery();
//结果集解析
List<T> list = new ArrayList<>();
//获取列信息对象
ResultSetMetaData metaData = resultSet.getMetaData();
//有了它之后,可以水平遍历列!
int columnCount = metaData.getColumnCount();
while (resultSet.next()) {
//一行数据对应一个 T 类型的数据
T r = clazz.newInstance(); //调用类的无参构造函数实例化对象!
//自动遍历列,注意,要从1开始,并且小于等于总列数!
for (int i = 0; i <= columnCount; i++) {
//对象的属性值
Object value = resultSet.getObject(i);
//获取指定下角标的列的名称!resultSetMetaData
//getColumnLabel:会获取别名,如果没有别名才是列的名称
String propertyName = metaData.getColumnLabel(i);
//反射,给对象属性赋值
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true); //属性可以设置,打破private的修饰限制
/**
* 参数1:要赋值的对象,如果属性是静态,第一个参数可以为null
* 参数2:具体的属性值
*/
field.set(r, value);
}
list.add(r);
}
//关闭资源
resultSet.close();
preparedStatement.close();
if (connection.getAutoCommit()) {
//没有事务,可以关闭
JdbcUtilsV2.freeConnection();
}
return list;
}
}