五、PreparedStatement的优势
01-PreparedStatement替换Statement实现CRUD操作
1.PreparedStatement的理解:
① PreparedStatement 是Statement的子接口
② An object that represents a precompiled SQL statement.
③ 可以解决Statement的sql注入问题,拼串问题
2.使用PreparedStatement实现通用的增、删、改的方法:version 1.0
//通用的增删改操作
public void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同!
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.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.资源的关闭
JDBCUtils.closeResource(conn, ps);
}
}
3.使用PreparedStatement实现通用的查询操作:version 1.0
* @Description 针对于不同的表的通用的查询操作,返回表中的一条记录
public <T> T getInstance(Class<T> clazz,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();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
* @Description 针对于不同的表的通用的查询操作,返回表中的多条记录
public <T> List<T> getForList(Class<T> clazz,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();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<T>();
while (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
【总结】
两种思想:
面向接口编程的思想
ORM编程思想:(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
两种技术:
1.使用结果集的元数据:ResultSetMetaData
getColumnCount():获取列数
getColumnLabel():获取列的别名
说明:如果sql中没给字段其别名,getColumnLabel()获取的就是列名
- 反射的使用(①创建对应的运行时类的对象 ② 在运行时,动态的调用指定的运行时类的属性、方法)
02-PreparedStatement可以操作Blob类型的变量
写入操作的方法:setBlob(InputStream is);
读取操作的方法:
Blob blob = getBlob(int index);
InputStream is = blob.getBinaryStream();
具体的insert:
//向数据表customers中插入Blob类型的字段
@Test
public void testInsert() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = “insert into customers(name,email,birth,photo)values(?,?,?,?)”;
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,"袁浩");
ps.setObject(2, "yuan@qq.com");
ps.setObject(3,"1992-09-08");
FileInputStream is = new FileInputStream(new File("girl.jpg"));
ps.setBlob(4, is);
ps.execute();
JDBCUtils.closeResource(conn, ps);
}
具体的query:
//查询数据表customers中Blob类型的字段
@Test
public void testQuery(){
Connection conn = null;
PreparedStatement ps = null;
InputStream is = null;
FileOutputStream fos = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
String sql = “select id,name,email,birth,photo from customers where id = ?”;
ps = conn.prepareStatement(sql);
ps.setInt(1, 21);
rs = ps.executeQuery();
if(rs.next()){
// 方式一:
// int id = rs.getInt(1);
// String name = rs.getString(2);
// String email = rs.getString(3);
// Date birth = rs.getDate(4);
//方式二:
int id = rs.getInt(“id”);
String name = rs.getString(“name”);
String email = rs.getString(“email”);
Date birth = rs.getDate(“birth”);
Customer cust = new Customer(id, name, email, birth);
System.out.println(cust);
//将Blob类型的字段下载下来,以文件的方式保存在本地
Blob photo = rs.getBlob("photo");
is = photo.getBinaryStream();
fos = new FileOutputStream("zhangyuhao.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer)) != -1){
fos.write(buffer, 0, len);
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(is != null)
is.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if(fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
JDBCUtils.closeResource(conn, ps, rs);
}
}
03-测试使用PreparedStatement实现批量操作:
层次一:使用Statement实现
- Connection conn = JDBCUtils.getConnection();
- Statement st = conn.createStatement();
- for(int i = 1;i <= 20000;i++){
-
String sql = "insert into goods(name)values('name_" + i + "')";
-
st.execute(sql);
- }
层次二:使用PreparedStatement替换Statement
略。
层次三:
1.addBatch()、executeBatch()、clearBatch()
2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。?rewriteBatchedStatements=true 写在配置文件的url后面
3.使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
层次四:设置连接不允许自动提交数据
最终版的代码体现:
@Test
public void testInsert3() {
Connection conn = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
//设置不允许自动提交数据
conn.setAutoCommit(false);
String sql = "insert into goods(name)values(?)";
ps = conn.prepareStatement(sql);
for(int i = 1;i <= 1000000;i++){
ps.setObject(1, "name_" + i);
//1."攒"sql
ps.addBatch();
if(i % 500 == 0){
//2.执行batch
ps.executeBatch();
//3.清空batch
ps.clearBatch();
}
}
//提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start)); //20000:83065 -- 565
} catch (Exception e) { //1000000:16086 -- 5114
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps);
}
}
总结:PreparedStatement与Statement的异同?
① 指出二者的关系? 接口 与 子接口的关系
② 开发中,PreparedStatement替换Statement
③ An object that represents a precompiled SQL statement.
04-数据库事务
1.事务的四大特点
2.数据操作过程中可能出现的问题(针对一致性)
数据库的四种隔离级别:(一致性和并发性:一致性越好,并发性越差)
如何查看并设置隔离级别: