JDBC通用的数据访问模块
通用数据访问模块的包结构及主要文件
各个文件的作用如下:
DatabasesUtil类:数据库连接管理类,包含创建连接,销毁连接等方法;
ConfigManager类:负责读取保存有数据库连接参数的配置文件,配置文件默认为存储在src根目录下的
database.properties文件。
CommonDao类:定义了通用的数据访问方法,并对查询条件和结果的处理进行封装。
TypeConstant类: 定义了java数据类型与数据库数据类型的对应关系,以便在CommonDao中进行自动数据处理.
database.properties文件
driver=com.mysql.cj.jdbc.Driver
#在和mysql传递数据的过程中,使用unicode编码格式,并且字符集设置为utf-8
url=jdbc\:mysql\://127.0.0.1\:3306/onlineauctionsystem?serverTimezone\=GMT%2B8&useUnicode\=true&characterEncoding\=utf-8
user=root
password=root
ConfigManager类
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* 读取数据库的配置信息
*
*/
public class ConfigManager {
//初始化配置对象
private static Properties properties=new Properties();
static {
try {
//读取相关配置文件并返回输入流对象
InputStream inputStream=ConfigManager.class.getClassLoader()
.getResourceAsStream("database.properties");
//让配置对象获取输入流的信息
properties.load(inputStream);
//关闭输入流
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 根据key得到对应的值的方法
* @param key
* @return
*/
public static String getValue(String key){
return properties.getProperty(key);
}
}
DatabasesUtil类
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 数据库连接与关闭工具类。
*/
public class DatabaseUtil {
// 获取配置文件信息
private static String driverClassName=ConfigManager.getValue("driver");
private static String user=ConfigManager.getValue("user");
private static String url=ConfigManager.getValue("url");
private static String password=ConfigManager.getValue("password");
//初始化本地线程对象---利用get(),set()方法 获取设置当前线程对应的线程内部变量---Connection对象
private static final ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
/**
* 获取数据库连接对象。
*
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
Connection connection = threadLocal.get(); //从当前线程中获得数据库的链接
if(connection==null){ //如果数据库链接为null(当前线程中没有链接)
try {
//加载驱动,获得链接
Class.forName(driverClassName);
connection=DriverManager.getConnection(url,user,password);
threadLocal.set(connection);//当前线程中保存链接
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return connection;// 返回连接对象
}
/**
* 关闭语句容器。
*
* @param stmt
* Statement对象
* @param rs
* 结果集
*/
public static void closeStatement(Statement stmt, ResultSet rs) {
try {
if(stmt!=null){
stmt.close();
}
if(rs!=null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 关闭数据库连接。
*/
public static void closeConnection() {
try {
//从当前线程中获得数据库的链接
Connection connection = threadLocal.get();
//将当前线程中的链接设置为null
threadLocal.set(null);
//当前线程中的链接不为null,关闭
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
TypeConstant类
import java.util.HashMap;
import java.util.Map;
/**
* 实现数据库类型与Java类型的映射
*/
public class TypeConstant {
/***
* 数据库类型和Java类型映射,key为数据库类型,value为Java类型
*/
private static Map<String, Class<?>> typeMap = new HashMap<String, Class<?>>();
static {
typeMap.put("BIGINT", Long.class);
typeMap.put("INT", Integer.class);
typeMap.put("VARCHAR", String.class);
typeMap.put("TEXT", String.class);
typeMap.put("DATETIME", java.util.Date.class);
typeMap.put("DECIMAL", Double.class);
typeMap.put("TINYINT", Integer.class);
typeMap.put("BIT", Boolean.class);
typeMap.put("TIMESTAMP", java.util.Date.class);
}
/**
* 新增类型
* @param columnType
* @param javaType
*/
public static void addType(String columnType, Class<?> javaType) {
typeMap.put(columnType, javaType);
}
/**
* 根据数据库类型返回相应的java类型
* @param columnType
* @return
*/
public static Class<?> getJavaType(String columnType) {
return typeMap.get(columnType);
}
}
CommonDao类
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 执行数据库操作的工具类。
*/
public class CommonDao {
/**
* 增删改的通用方法
* @param sql sql语句
* @param params 参数数组
* @return
* @throws SQLException
*/
public int executeUpdate(String sql, Object[] params) throws SQLException {
Connection connection=DatabaseUtil.getConnection();//获得数据库的链接
PreparedStatement pstmt=connection.prepareStatement(sql); //预编译sql语句
if(params!=null && params.length>0){
for(int i=0;i<params.length;i++){
pstmt.setObject(i+1,params[i]);
}
}
int result= pstmt.executeUpdate(); //执行sql语句
DatabaseUtil.closeStatement(pstmt,null); //关闭资源
DatabaseUtil.closeConnection();//关闭连接
return result;
}
/**
* 增删改的通用方法
* @param sql sql语句
* @param entity 对象
* @return
* @throws SQLException
*/
public int executeUpdate(String sql, Object entity) throws SQLException {
Map<String, Object[]> map=
handleParameterMapping(sql,entity);
//map中的key是执行的sql语句(转换后的sql语句,?占位符的sql语句)
//map中的value是执行的sql语句对应的数组参数
//1 map.entrySet 方法获得是 map中的key value的集合
//2 map.entrySet().iterator() 方法获得是 map中的key value的集合的迭代器
//3 迭代器中的每条记录都是key-value。
//4 map.entrySet().iterator().next() 迭代器中的一条记录
Entry<String,Object[]> entry= map.entrySet().iterator().next();
return executeUpdate(entry.getKey(),entry.getValue());
}
/**
* 将带#{}占位符 的sql语句 转为带 ?占位符的sql语句
* 将entity的参数 转为 数组参数
* @param sql
* @param entity
* @return
* @throws SQLException
*/
protected Map<String, Object[]> handleParameterMapping(
String sql,Object entity) throws SQLException {
Map<String, Object[]> returnResultMap=new HashMap<>();//最后需要返回的集合以{sql-数组参数}的形式
List<String> placeholders=new ArrayList<>(); //保存的是 sql语句中的#{..}内容
//找出sql语句中的#{..}内容,将其保存到集合中
int offset=0; //从sql语句的哪个位置开始查找#{
while(true){
int start=sql.indexOf("#{",offset); //查找sql语句中的#{的位置
if(start>=offset){
int end=sql.indexOf("}",offset); //查找sql语句中的}的位置
String placeHolder=sql.substring(start+2,end);
placeholders.add(placeHolder);
offset=end+1;
}else{ //sql语句中已经找不到#{,start的返回值是-1,退出循环
break;
}
}
Object [] values=new Object[placeholders.size()]; //传递的参数值
//将#{}sql语句 改成?sql语句
for(int i=0;i<placeholders.size();i++){
String temp=placeholders.get(i); //userName
sql=sql.replaceFirst("\\#\\{"+temp+"\\}","?");
//entity对象中的属性的值 占位符上的值
//占位符的名字(temp) 就是 entity对象的属性的名字(temp)
//获得属性的值,怎么获得属性的值? 通过get方法获得属性的值
//怎么得到get方法???
// 找个例子:获得属性名的get方法叫作getUserName
// 占位符的名字(属性的名字)userName
try {
Method getMethod= entity.getClass()
.getMethod("get"+temp.substring(0,1).toUpperCase()
+temp.substring(1));
Object value=getMethod.invoke(entity);//执行get方法,获取entity对象中的参数值
values[i]=value;//将此值存入数组中
} catch (Exception e) {
e.printStackTrace();
}
}
returnResultMap.put(sql,values);//将拿到的sql,和参数数组都存入要返回的集合中
return returnResultMap;
}
/**
* 查询操作
*
* @param sql
* sql语句
* @param params
* 参数数组
* @return 查询结果
* @throws SQLException
*/
public <T> List<T> executeQuery(Class<T> clz, String sql, Object[] params) throws SQLException {
//获取连接
Connection connection=DatabaseUtil.getConnection();
//初始化PreparedStatement对象,进行预编译
PreparedStatement preparedStatement= connection.prepareStatement(sql);
if(params!=null && params.length>0){
for(int i=0;i<params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
//执行查询操作,并返回结果集
ResultSet resultSet=preparedStatement.executeQuery();
try {
//将结果集以对象存入集合的方式返回集合
return handleResultMapping(clz,resultSet);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}finally{
//关闭PreparedStatement对象,但是rs对象不关闭
DatabaseUtil.closeStatement(preparedStatement,null);
//关闭连接
DatabaseUtil.closeConnection();
}
return null;
}
/**
* sql:的占位符是#{}, entity:对象信息
* @param clz
* @param sql
* @param entity
* @param <T>
* @return
* @throws SQLException
*/
public <T> List<T> executeQuery(Class<T> clz, String sql, Object entity) throws SQLException {
//将【sql-entity】格式的集合通过handleParameterMapping()方法转换为【sql-Object[]】,然后执行查询方法
Map<String,Object[]> map= handleParameterMapping(sql,entity);
//获取map集合中第一个sql,及其对应的值(Object[])
sql=map.entrySet().iterator().next().getKey(); //转换后的sql语句
Object [] params=map.entrySet().iterator().next().getValue(); //转换后的参数
return executeQuery(clz,sql,params);
}
/**
* 将查询出来的结果集转成List集合
* @param clz 返回集合的元素类型
* @param rs
* @param <T>
* @return
* @throws SQLException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
protected <T> List<T> handleResultMapping(Class<T> clz, ResultSet rs)
throws SQLException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//如果目标类型是整型的,直接返回整型的集合
if(clz.equals(Integer.class)){ //select count(1) from ......
List<Integer> list=new ArrayList<>();
while(rs.next()){
list.add(rs.getInt(1));
}
return (List<T>)list;
}
//如果返回的结果不是整型的, 获得返回结果类型中所有的set方法(为啥? 因为通过set方法将查询的结果封装到对象的属性中)
ResultSetMetaData resultSetMetaData=rs.getMetaData(); //结果集的摘要信息
int count=resultSetMetaData.getColumnCount(); //结果集中的字段的数量
//结果集中字段的数量就是set方法的数量(为啥?通过set方法 将查询出的字段的信息封装到对象的属性中)
Method [] methods=new Method[count];
//获得返回结果类型中所有set方法的参数类型
Class [] typesClass=new Class[count]; //有多少个set方法,就有多少个参数的类型(为啥?每个set方法 都只有一个参数)
//给set方法的数组赋值
for(int i=0;i<count;i++){
//假设数据库中有一个userId字段 在类中就有一个userId的属性 应该就有一个setUserId方法
//根据数据中字段的名字 推导出 set方法的名字
//只要知道字段的名字就可以了 推导出 set方法的名字
//找字段(列)的名字---列名字和属性名字一致
String columnLable=resultSetMetaData.getColumnLabel(i+1);
//找字段的类型(为啥? 根据字段的类型找到字段对应的属性的类型)
String columnTypeName=resultSetMetaData.getColumnTypeName(i+1);
//根据字段的类型 获得字段类型对应的java类型
Class javaClass=TypeConstant.getJavaType(columnTypeName);
typesClass[i]=javaClass;//将此类型存入类型参数数组中
try {
//得到具体的set()方法,并放入methods数组中
methods[i]= clz.getMethod("set"+columnLable.substring(0,1).toUpperCase()
+columnLable.substring(1),javaClass);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//使用反射将结果集中的每条记录封装到结果对象中,将结果对象添加到集合中返回
List<T> resultList=new ArrayList<>(); //返回的最终结果
T instance; //每一条记录对应的 对象信息
while(rs.next()){ //判断结果集中是否有记录
//如果有记录,每一条记录 对应的是一个对象
instance=clz.newInstance();
//对象中的属性值都是默认的,将数据库中查询出来的数据赋值给对象中的属性
//怎么给对象中的属性赋值??? 通过set方法 调用set方法
for(int i=0;i<methods.length;i++){
if(methods[i]==null){ //如果数组中的某个set方法为空,set方法不能执行
continue;
}
//判断set方法中传递的参数的值的类型,如果是日期类型,进行一下特殊的处理
if(typesClass[i].equals(Date.class)){
Date date=new Date(rs.getTimestamp(i+1).getTime());
methods[i].invoke(instance,date);
}else{
//为啥是i,因为i是set方法的下标,set方法和字段对应的。数据库中列的下标从1开始。
methods[i].invoke(instance,rs.getObject(i+1,typesClass[i]));
}
}
//执行完赋值后,将对象添加到要返回的集合中。
resultList.add(instance);
}
return resultList;
}
}
每日一句: 学而不知道,与不学同;知而不能行,与不知同。