目录
调用获取到的连接对象conn的preparedStatement(sql)方法获取preparedstatement对象——传入sql语句;
调用PS对象的excute()或excuteQuery()方法执行sql语句,
关闭各个资源,这个操作我么也是封装到JDBCUtils工具类中;
目前刚刚学完servlet知识点,其中在完成一个使用纯servlet完成的对dept1表格进行增删改查操作的web项目时,要使用jdbc连接数据库并修改其中的数据,所以回顾知识点;
存在问题:复盘jdbc相关的知识点,才发现自己已经将大部分知识点忘记了;
获取数据库连接步骤:
在项目的src下建立lib文件夹并导入jdbc的jar包;
在项目的src下新建类,并在类中加载并注册数据库驱动,
通常我们会将获取数据库连接的这个操作,封装到 【JDBCUtils】这个类当中;
-
加载使用到反射机制【
class.forname(类名);
】-
问题引入:什么是反射机制?反射机制是如何作用的?
-
问题回答:反射机制就是在Java代码的运行期动态的获取类的信息以及动态调用对象的方法及属性的能力叫做反射机制;,java的反射机制就是通过类名或者对象名来直接获取或修改任一类或者任一对象的属性和方法
-
-
在获取数据库连接之前,我们还会用到数据库的密码,和用户等信息,而这些信息我们通常会封装到一个后缀名为properties文件中,文件内容如下;
url=jdbc:mysql://localhost:3306/user user=root password=123456 driverClass=com.mysql.cj.jdbc.Driver
- 然而在获取数据库连接之前,我们将这些属性信息放进properties文件中后,我们需要一一读出,那么我们如何读出这些信息呢?
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
//getResourceAsStream:返回用于读取指定资源的输入流。
Properties pros = new Properties();
pros.load(is);
//读取配置文件
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass")
-
这里面用到了一个输入流
InputStream is = ClassLoder.getSystemClassLoader().getResourceAsStream("jdbc.properties");
-
这个输入流和我们之前获取输入流的方式有所不同,这个是使用到类的加载器【用来加载Java类到 Java 虚拟机中的一种加载器。】;
-
然后new了一个properties【属性配置文件】对象pros,然后使用
pros.load(is);
-
load();方法作用【从指定的输入流中读取属性列表(键和元素对)到pros对象中的map集合中】,如果我们想获取对应的值,就要通过方法getProperties("key");
值=pros.getProperties("键")
;-
注册是使用到driver类中的静态代码块中的
Connection conn=DriverManager.getConnection(url,user,password);
-
问题引入:driver类是什么?
-
问题回答:驱动类
-
-
调用获取到的连接对象conn的preparedStatement(sql)方法获取preparedstatement对象——传入sql语句;
-
知识点:为什么不使用Statement对象?preparedstatement的作用是什么?
-
解释一:因为Statement不能够避免SQL注入问题;
-
解释二:preparedstatement和Statement的作用一样,都是向数据库发送需要执行的sql语句;
填充占位符:使用for循环填充占位符
-
for (int i = 0; i < args.length; i++) { ps.setObject(i+1,args[i]);//有注意点、易出错,第一个参数是从1开始 }
-
这里有个注意点:填充占位符的第一个参数是从1开始的;
调用PS对象的excute()或excuteQuery()方法执行sql语句,
这两个方法的区别就是一个无法拿到结果集另一个可以拿到结果集,或者一个有返回值另一个没有返回值;
-
这里牵扯到一个问题:结果集的行和列数是不是在rs中?什么是结果集的元数据?
-
问题解决一:我们在获取到结果集之后我们如何将它输出在屏幕上,我们想要输出结果集我们就需要知道结果集的行和列数?解决:结果集的列数没有封装在rs中而是封装在一个叫结果集的元数据(metaData)中;
-
问题解决二:*结果集中存放的是纯数据,而结果集的元数据中存放的是对数据的修饰,比如数据的行数和列数。。。
获取结果集
在拿到结果集之后,我们就要把结果集中的数据赋值给对应对象的指定属性,并输出它们;
-
此时就遇见两个问题,我们输出结果集的元数据时,①首先我们需要知道结果集对应我们的那个对象,②其次需要知道结果集的行数和列数便于我们控制输出和赋值的次数,③我们还要通过结果集的数据的类型去判断它对应类的哪一个属性以至于正确赋值;
-
问题①:通过泛型机制(?)
-
问题②:结果集和行数和列数被封装在结果集的元数据Metadate中,可以通过getColumnCount()获取结果集的列数,然后通过rs.next()来判断是否有下一条数据;
-
问题③:通过反射技术,
Field field = clazz.getDeclaredField(columnName);
来获取数据对应的属性值,然后设置该属性的访问权限,以防该属性是私有的无法修改,最后对应赋值field.set(t, columnvalue);
;
-
关闭各个资源,这个操作我么也是封装到JDBCUtils工具类中;
完整代码实例:
public <T> List<T> getForList(Class<T> clazz, String sql, Object ... args) {
/*
*@auther 卢永鹏
*Description 针对不同表,写一个通用的查询操作,返回多条记录;
*@Date 10:39 2023/1/3
*@param [sql, args]
*@Return void
**/
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]);
}
//执行
rs = ps.executeQuery();
//遇见问题:获得结果集之后我们如何将它输出在屏幕上,如何获取他的行和列数?
//解决:结果集的列数没有封装在rs中而是封装在一个叫结果集的元数据(metaData)中;
ResultSetMetaData metaData = rs.getMetaData();
int col = metaData.getColumnCount();
//rs.next():判断结果集的下一条是否有元数据,有返回true,否则返回false;
// emp emp = new emp();错误:写到这里不妥,如果没查到数据,那么对象还是会被创造,有点浪费;
//创建集合
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
// emp emp = new emp();
T t = clazz.newInstance();
//处理一行数据中的每一个列
for (int i = 0; i < col; i++) {
Object columnvalue = rs.getObject(i + 1);
//遇见问题:我们如何将value保存到对应的属性中;
//解决。先建造一个空参的对象,然后通过value的列名判断出对应属性并赋值
//获取每个列的列名,rs中也是没有封装这个获取列名的方法,所以我们还是通过结果记得元数据来获取列名;
String columnName = metaData.getColumnName(i + 1);
//遇见问题:如何通过列名拿到Emp对象中与之对应的属性
//解决:使用反射技术来获取属性名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这个属性可能是私有的,所以设置访问权限
field.set(t, columnvalue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
大概陈述一下以上过程:
我们首先获取了结果集的元数据metadata的对象,然后从中获取到了rs结果集数据的列数(作用,控制for循环的次数),然后通过if(rs.next())来控制while循环的次数,然后在while循环中获取columnValue,然后通过columnValue获取到对应的列名columnName,然后通过列名找到对应的属性名 Field field = clazz.getDeclaredFiled(columnName);然后通过filed.setAccessible(true);设置属性的设置权限,防止属性私有无法从外面设置;