3.1操作和访问数据库
image.png
3.2使用StateMent操作数据表的弊端
sql注入.png
3.3PrepareStatement的使用
-
Statement接口的子接口PrepareStatement:代表一个预编译的sql语句,Connection的PrepareStatement(Stirng sql)返回该类型对象
image.png
3.3.1介绍
@Test
public void update() throws IOException, ClassNotFoundException, SQLException, ParseException {
//向custom表添加一条记录
InputStream is = PrepareStatementTest.class.getClassLoader().getResourceAsStream("" +
"jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String driverClass = properties.getProperty("driverClass");
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
Class.forName(driverClass);
//3.连接,mysql重写的方法
Connection conn = DriverManager.getConnection(
url, user, password
);
// System.out.println(conn);
//4.预编译sql语句,返回prepareStatement实例
String sql = "insert into customers(name," +
"email,birth)values(?,?,?)";//?占位符
PreparedStatement ps = conn.prepareStatement(sql);
//5.填充占位符,1开始
ps.setString(1,"哪吒");
ps.setString(2,"nezha@gmail.com");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse("1000-01-01");
ps.setDate(3,new Date(date.getTime()));
//6.执行
ps.execute();
//7.资源关闭
ps.close();
conn.close();
}
3.3.2封装数据库连接和关闭操作以及增删改
- 封装数据库连接和关闭集成到工具类
//1.连接数据库
Connection conn = util.JDBCUtils.getConnection();
//2.预编译sql语句,返回prepareStatement实例
String sql = "insert into customers(name," +
"email,birth)values(?,?,?)";//?占位符
PreparedStatement ps = conn.prepareStatement(sql);
//3.填充占位符,1开始
ps.setString(1,"哪吒");
ps.setString(2,"nezha@gmail.com");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse("1000-01-01");
ps.setDate(3,new Date(date.getTime()));
//4.执行
ps.execute();
//5.资源关闭
util.JDBCUtils.closeResource(conn,ps);
- 改进的更新操作
Connection conn = null;
PreparedStatement ps = null;
try {
//1.连接数据库
conn = util.JDBCUtils.getConnection();
//2.预编译sql语句,返回prepareStatement实例
String sql = "update customers set name = ? " +
"where id = ?";//?占位符
ps = conn.prepareStatement(sql);
//3.填充占位符,1开始
ps.setObject(1,"莫扎特");
ps.setObject(2,18);
//4.执行
ps.execute();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//5.资源关闭
util.JDBCUtils.closeResource(conn,ps);
}
- 实现通用的增删改操作
sql表名最好单引号修饰,避免歧义
insert into 表名(列名,。。。)
values(值,。。)
update 表名
set 列=新值,。。。
where 筛选条件;
语法:delete from 表名 where 条件;
@Test
public void update3() {
String sql = "delete from customers where id = ?";
update(sql, 3);
}
public static void update(String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.连接数据库
conn = util.JDBCUtils.getConnection();
//2.预编译sql语句,返回prepareStatement实例
ps = conn.prepareStatement(sql);
//3.填充占位符,1开始
for(int i = 0;i<args.length; i++)
{
ps.setObject(i+1, args[i]);
}
//4.执行
ps.execute();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//5.资源关闭
util.JDBCUtils.closeResource(conn, ps);
}
}
image.png
3.3.3实现查询操作
- 增删改不需要返回,而查询需要
- 单表查询初版
@Test
public void test1(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.连接数据库
conn = util.JDBCUtils.getConnection();
//2.预编译sql语句,返回prepareStatement实例
String sql = "select id,name,email,birth " +
"from customers where id = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1, 1);
//3 执行并返回结果集
resultSet = ps.executeQuery();
//4 处理结果集
if(resultSet.next())
{//判断结果集的下一条是否有数据,如果有返回true,并指针下移
//返回false,直接结束
//得到当前数据各自字段的值
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
java.sql.Date birth = resultSet.getDate(4);
// 封装为一个对象
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
//关闭
util.JDBCUtils.closeResource(conn, ps, resultSet);
}
}
- 单表通用查询
@Test
public void test2(){
String sql = "select id,name,email " +
"from customers where id = ?";
System.out.println(queryCustomers(sql, 13));
}
//单表通用查询
public static Customer queryCustomers(String sql, Object... args) {
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元数据
ResultSetMetaData rsmd = rs.getMetaData();
//获取结果集列数
int col = rsmd.getColumnCount();
if (rs.next()) {
Customer cust = new Customer();
//对每一列赋值给对象对应的成员属性
for (int i = 0; i < col; i++) {
Object value = rs.getObject(i + 1);
//获取每个列的别名+反射
String columnName = rsmd.getColumnLabel(i + 1);
Field field = Customer.class.getDeclaredField(columnName);
field.setAccessible(true);
field.set(cust, value);
}
return cust;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
- 创建表对应的类时候,属性名要和字段名完全相同,否则反射设置不成功,所以sql字段名要用类的属性名起别名,而且需要用rsmd的getColumnLabel()获取列的别名
- 针对不同表的通用查询
@Test
public void test2(){
String sql = "select id,name,email " +
"from customers where id = ? or id = ?";
System.out.println(getInstance(Customer.class, sql, 13, 1).toString());
}
//针对任意表的通用查询
public static <T> List<T> getInstance(Class<T> clazz, String sql, Object... args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = 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();
ResultSetMetaData rsmd = rs.getMetaData();
int col = rsmd.getColumnCount();
list = new ArrayList<>();
while(rs.next()){
//对每一行
T t = clazz.newInstance();
for(int i = 0; i < args.length; i++){
Object value = rs.getObject(i + 1);
String fieldName = rsmd.getColumnLabel(i + 1);
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(t, value);
}
list.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return list;
}
- 注意泛型方法的格式 返回值前<E, .....>
3.4ResultSet与ResultSetMetaData
image.png
image.png
image.png
- 结论:
用ResultSet得到各行和每行各列的值
用ResultSetMataData得到列数和每列对应的成员变量名,用反射设置对象对应Field
3.5资源释放
image.png
image.png
3.7 PrepareStatement解决sql注入问题
- 原理:创建对象时候已经预编译了,sql逻辑关系不会改变
- 好处:
1)可以操作blob类型数据
2)实现更高效的批量操作