一.概述
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
二. Statement操作数据表的弊端:
- 问题一:存在拼串操作,繁琐
- 问题二:存在SQL注入问题 SQL
注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段 或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 SQL 引擎完成恶意行为的做法。
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了,原因是 PreparedStatement使用占位符'?'来来写SQL语句所以会先预编译SQL文件,导致运行的时候不会产生错误运行过程。
三. 增删改操作
- 连接数据库
- 预编译SQL语句并填充占位符
- 执行
- 关闭资源
- 获取test数据库连接的通用方法getConnection
//获取test数据库连接的通用方法
public static Connection getConnection() throws Exception {
//1.读取配置文件
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String driverClass=pros.getProperty("driverClass");
String url = pros.getProperty("url");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection con = DriverManager.getConnection(url,user,password);
return con;
}
- 关闭数据库连接的通用方法closeConnection
//关闭数据库连接的通用方法1
public static void closeConnection(Connection con,Statement ps){
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
- 增删改操作的通用方法update
//增删改操作的通用方法
public static void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = getConnection();
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);//小心参数声明错误!!
}
//4.执行
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.资源的关闭
closeConnection(conn, ps);
}
}
注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始。
四.查询操作
Java与SQL的类型转换
4.1 ResultSet与ResultSetMetaData
4.1.1 ResultSet
ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,返回的实际上就是一张数据表。有一个指针指向数据表的第一条记录的前面。
- next()方法:一个指向当前数据行的游标,初始的时候,游标在第一行之前,可以通过 ResultSet 对象 的 next() 方法移动到下一行。调用 next()方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。 相当于Iterator对象的 hasNext() 和 next() 方法的结合体。
4.1.2 ResultSetMetaData
可用于获取关于 ResultSet 对象中列的类型和属性信息的对象(获取元数据)
- 常用方法
- getColumnName(int column):获取指定列的名称
- getColumnLabel(int column):获取指定列的别名 }
- getColumnCount():返回当前 ResultSet 对象中的列数。
- getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
- getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
- isNullable(int column):指示指定列中的值是否可以为 null。
- isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
注意:若bean中声明的属性名与表中字段名不相同,则需要在SQL语句中给字段起别名为其对应的属性名,此时只能调用getColumnLabel(int column),若相同也可以使用该方法不会报错,所以建议在开发时直接使用getColumnLabel(int column)即可;
- 实际问题
- 如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可
- 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法
- 获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的getColumnLabel() 方法
4.2 单行查询操作
- 连接数据库
- 预编译SQL文件并设置占位符
- 执行SQL并获取结果集
- 通过结果集获取表的元数据并获取列数
- 获取数据表对应的类的运行时对象
- 通过结果集获取各个字段的值
- 通过元数据获取各个字段的名称(别名)
- 获运行时类的属性
- 给属性赋上表中对应字段的值
- 关闭资源
//Select语句查找单条记录的通用方法
public static <T> T getInstanceQuery(Class<T> clazz,String sql,Object ...args) {
Connection con =null;
PreparedStatement ps =null;
ResultSet rs =null;
try {
//1.连接数据库
con = getConnection();
//2.预编译sql语句
ps = con.prepareStatement(sql);
if(ps!=null){
for (int i=0;i<args.length;i++){
//设置通配符值(填充占位符)
ps.setObject(i+1,args[i]);
}
}
//3.执行sql并获取sql语句返回的结果集
rs =ps.executeQuery();
//4.获取结果集的元数据并获取列数
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();//列数
if (rs.next()){//指针下移,若数据为空则返回false,反之true
//5.获取数据表对应的类的运行时对象
T t = clazz.newInstance();
for (int i=0;i<columnCount;i++){
//6.获取各个字段的值
Object columnVlaue = rs.getObject(i+1);
//7.获取各个字段的名称
String columnName=rsmd.getColumnLabel(i+1);
//8.获运行时类的属性
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//打开权限
//9.给属性赋上表中对应字段的值
field.set(t,columnVlaue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeConnection(con,ps,rs);
}
return null;
}
4.3 多行查询操作
只需要把单行查询时的 if (rs.next())改为while(rs.next()),在返回一个list即可
//Select语句查找多条记录的通用方法
public static <T> List<T> getInstanceQuerys(Class<T> clazz, String sql, Object ...args){
ArrayList<T> list = null;
Connection con =null;
ResultSet rs =null;
PreparedStatement ps =null;
try {
con =getConnection();
ps = con.prepareStatement(sql);
if(ps!=null){
for (int i=0;i<args.length;i++){
ps.setObject(i+1,args[i]);
}
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
list = new ArrayList<T>();
while (rs.next()){//修改的地方
T t = clazz.newInstance();
for (int i=0 ;i<columnCount;i++){
String columnName = rsmd.getColumnLabel(i+1);
Object columnValue = rs.getObject(i+1);
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t,columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
closeConnection(con,ps,rs);
}
return null;
}