一、不足之处
上一节讲到,jdbc连接数据库的操作是:
1、导入jar包;
2、反射加载驱动;
3、创建连接 ;
4、创建sql语句;
5、生成Statement对象;
6、执行sql语句;
7、获取结果,遍历ResultSet;
8、关闭连接。
其部分执行代码是:
//反射加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
connection = DriverManager.getConnection(url, username, password);
String sql="select * from user where id> ? ";
//生成PreparedStatement,可以防止sql注入
prestmt=connection.prepareStatement(sql);
//设置参数
prestmt.setString(1,"0"); //设置id条件
resultSet = prestmt.executeQuery(); //返回执行结果
if(resultSet!=null){
while (resultSet.next()){
//resultSet.next(): 指向返回结果的下一行数据
System.out.println(resultSet.getString(1)+resultSet.getString(2)); //通过columnIndex获取每一行的每一列数据
}
}
对于开发过程中,1、Jdbc操作数据库的时候如果每一个方法都这么繁琐的写,代码是很重复的、冗余的;2、代码中存在硬编码问题,对于url、username、password、driver这些常用变量,我们是写死在代码中,如果有变量发生变化,那我们还需要修改代码,很不友好。
解决措施:
1、我们可以建一个工具类JdbcUtil来封装JDBC数据库操作的代码,用到的时候调用工具的方法就可以。
2、将url、username、password、url写到一个文件jdbc.properties文件下,避免硬编码。
二、JdbcUtil工具类
1、jdbc.properties文件与反射加载驱动
// jdbc.properties的内容是:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/testPro?useSSL=false
jdbc连接数据库的操作第一步 是:1、反射加载驱动。所以,我们需要在第一步之前就获取到jdbc.properties文件的内容(而且仅加载一次)。在JdbcUtil工具类中,我们可以在static{}静态代码块里读取配置文件的内容,具体如下。
///静态代码块读取配置文件
public class JdbcUtil{
//1、创建properties集合类
Properties pro=new Properties();
/**static静态代码块属于类,会随着类的创建而创建,在方法之前,且仅执行一次*/
static{
try{
//2、类加载器加载文件
//jdbc.properties的文件在src或则resources目录下
InputStream resourceAsStream = JdbcUtil3.class.getClassLoader().getResourceAsStream("jdbc.properties");
//3、pro加载输入流
pro.load(resourceAsStream);
//4、可以通过pro.getProperty(String key)获取对应的值
String driver=pro.getProperty("jdbc.driver");
//5、反射加载驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2、创建连接
在JdbcUtil工具类中创建获取连接的静态方法,可以通过:类名.方法名进行调用。
可以通过JdbcUtil.getConnection();方法获取数据库连接;(前提是读取jdbc.properties里面的信息)
public static Connection getConnection(){
String username=pro.getProperty("jdbc.username");
String password=pro.getProperty("jdbc.password");
String url=pro.getProperty("jdbc.url");
Connection connection=null;
try {
//获取连接
connection=DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
3、创建Statement
//创建Statement不需要单独写在JdbcUtil方法里面
Statement statement=null;
String sql="select * from user where username=? and password=? ";
try {
//connection通过JdbcUtil.getConnection()获取
statement=connection.prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
4、执行sql
在JdbcUtil类中,执行sql有两种方法(查询和更新):executeQuery()和executeUpdate()
/***
* 查询操作
* @param prestmt prestmt 包含sql的PreparedStatement
* @param params Object...params ,可变参,相当于数组,因为长度不确定,放在方法参数最后
* @return 查询结果的List<Map>格式
*/
public static List<Map> executeQuery(PreparedStatement prestmt, Object...params){
List<Map> result=new ArrayList<>();
ResultSet set=null;
try {
/**给查询的sql设置参数,替换sql中的 ? */
for(int i=1;i<=params.length;i++){
prestmt.setObject(i,params[i-1]);
}
set=prestmt.executeQuery();
//获取查询结果的虚拟表的结构(列的类型,列的个数,列字段名)
ResultSetMetaData metaData=set.getMetaData();
//获取查询结果的虚拟表的列数
int countColumn=metaData.getColumnCount();
if(set!=null){
//通过set.next() 遍历每一条查询结果
while (set.next()){
Map map =new HashMap();
//遍历每一行结果的 每一列,将每一条结果封装在map中
for(int i=1;i<=countColumn;i++){
//获取 {key:value}
map.put(metaData.getColumnLabel(i),set.getObject(i));
}
result.add(map);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
/***
* 更新操作
* @param prestmt 包含sql的PreparedStatement
* @param params Object...params ,可变参,相当于数组
* @return sql更新影响的条数
*/
public static int executeUpdate(PreparedStatement prestmt,Object...params){
int result=0;
try {
for(int i=1;i<=params.length;i++){
prestmt.setObject(i,params[i-1]);
}
//更新操作
result=prestmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
5、关闭连接
对于Connection、Statement、ResultSet的对象,在使用完之后是必须关闭的。遵循的原则是:先开启后关闭,后开启先关闭。其对象新建顺序是:Connection、Statement、ResultSet,所以关闭顺序是:ResultSet、Statement、Connection 对象。
/**
* 关闭所有连接
* @param connection
* @param statement
* @param set
*/
public static void closeAll(Connection connection, Statement statement, ResultSet set){
try {
if(set!=null){
set.close();
}
if(statement!=null){
statement.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
二、注意事项
(1)jdbc.properties配置文件的位置应该是src目录下, 或则resources目录下。
/// 注意:jdbc的格式, = 左右两侧没有 "",
//eg:"jdbc.driver"="com.mysql.cj.jdbc.Driver"是不合适的
jdbc.driver=com.mysql.cj.jdbc.Driver
(2)jdbc.properties文件中 = 的 两侧一般没有双引号
//jdbc.properties文件
jdbc.driver=com.mysql.cj.jdbc.Driver
//错误的取值,取到是null
String driver = pro.getProperty("driver");
//正确的取值
String driver = pro.getProperty("jdbc.driver");
(3)ExceptionInInitializerError
静态初始化程序中发生意外异常的信号。抛出 ExceptionInInitializerError
表明在计算静态初始值或静态变量的初始值期间发生异常。
在JdbcUtil工具类中,static{}静态代码块可能出现错误
(4)PreparedStatement的对象 prestmt可以使用占位符 ? ,设置参数的时候是从 1 开始,而数组索引是从0 开始
/**给查询的sql设置参数,替换sql中的 ? */
for(int i=1;i<=params.length;i++){
prestmt.setObject(i,params[i-1]);
}
(5)Statement与PrepaedStatement对象
关系:PrepaedStatement继承Statement,都是接口
区别:Statement执行静态sql,不能够设置参数
PreparedStatement执行动态sql,可以设置参数。而且基于预编译sql,可以防止sql注入。
注:sql预编译存储在PreparedStatement对象中。
///1、Statement执行静态sql
String sql="select * from user where username= 'admin' and password= 'admin' ";
Statement statement = JdbcUtil.getConnection().createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println(resultSet.getString(2));
}
///2、PrepaedStatement执行动态sql
String sql="select * from user where username= ? and password= ?";
PreparedStatement preparedStatement=JdbcUtil3.getConnection().prepareStatement(sql);
preparedStatement.setString(1,"admin");
preparedStatement.setString(2,"admin");
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
System.out.println(resultSet.getString(2));
}
(7)java.sql.SQLException: Before start of result set
出现问题的代码:
String sql="select * from user where username= 'admin' and password= 'admin' ";
Statement statement = JdbcUtil.getConnection().createStatement();
ResultSet resultSet = statement.executeQuery(sql);
System.out.println(resultSet.getString(2));
出现的原因:
符合条件的结果只有一条,忽略了循环。
ResultSet的结果的初始游标位置是 查询到结果数组的前一位,忽略next(),此时去取值就会出现: Before start of result set
resultSet.next()方法可以使得游标每次下移一行,只有下移一行才能找到数据。
修改:
while(resultSet.next()){
System.out.println(resultSet.getString(2));
}