Java 之 JDBC:(三)使用 PreparedStatement 实现 CRUD 操作

一、操作和访问数据库

    数据库连接用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个 Socket 连接。

    在 java.sql 包中有3个接口分别定义了对数据库的调用的不同方式:

      (1)Statement:用于执行静态 SQL 语句并返回它所生成结果的对象;

      (2)PreparedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句;

      (3)CallableStatement:用于执行 SQL 存储过程;

    

二、使用 Statement 操作数据库表

  1、使用 Statement 操作表

    通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。

    Statement 接口中定义了下列方法用于执行 SQL 语句:

int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE

ResultSet executeQuery(String sql):执行查询操作SELECT

    使用 Statement 实现对数据表的查询操作案例:

    (1)根据 User 表创建JavaBean

 1 public class User {
 2 
 3     private String user;
 4     private String password;
 5 
 6     public User() {
 7     }
 8 
 9     public User(String user, String password) {
10         super();
11         this.user = user;
12         this.password = password;
13     }
14 
15     @Override
16     public String toString() {
17         return "User [user=" + user + ", password=" + password + "]";
18     }
19 
20     public String getUser() {
21         return user;
22     }
23 
24     public void setUser(String user) {
25         this.user = user;
26     }
27 
28     public String getPassword() {
29         return password;
30     }
31 
32     public void setPassword(String password) {
33         this.password = password;
34     }
35 
36 }

    (2)Statement 实现对数据表的查询方法(支持查询一条数据)

 1         // 使用Statement实现对数据表的查询操作
 2     public <T> T get(String sql, Class<T> clazz) {
 3         T t = null;
 4 
 5         Connection conn = null;
 6         Statement st = null;
 7         ResultSet rs = null;
 8         try {
 9             // 1.加载配置文件
10             InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
11             Properties pros = new Properties();
12             pros.load(is);
13 
14             // 2.读取配置信息
15             String user = pros.getProperty("user");
16             String password = pros.getProperty("password");
17             String url = pros.getProperty("url");
18             String driverClass = pros.getProperty("driverClass");
19 
20             // 3.加载驱动
21             Class.forName(driverClass);
22 
23             // 4.获取连接
24             conn = DriverManager.getConnection(url, user, password);
25 
26             st = conn.createStatement();
27 
28             rs = st.executeQuery(sql);
29 
30             // 获取结果集的元数据
31             ResultSetMetaData rsmd = rs.getMetaData();
32 
33             // 获取结果集的列数
34             int columnCount = rsmd.getColumnCount();
35 
36             if (rs.next()) {
37 
38                 t = clazz.newInstance();
39 
40                 for (int i = 0; i < columnCount; i++) {
41                     // //1. 获取列的名称
42                     // String columnName = rsmd.getColumnName(i+1);
43 
44                     // 1. 获取列的别名
45                     String columnName = rsmd.getColumnLabel(i + 1);
46 
47                     // 2. 根据列名获取对应数据表中的数据
48                     Object columnVal = rs.getObject(columnName);
49 
50                     // 3. 将数据表中得到的数据,封装进对象
51                     Field field = clazz.getDeclaredField(columnName);
52                     field.setAccessible(true);
53                     field.set(t, columnVal);
54                 }
55                 return t;
56             }
57         } catch (Exception e) {
58             e.printStackTrace();
59         } finally {
60             // 关闭资源
61             if (rs != null) {
62                 try {
63                     rs.close();
64                 } catch (SQLException e) {
65                     e.printStackTrace();
66                 }
67             }
68             if (st != null) {
69                 try {
70                     st.close();
71                 } catch (SQLException e) {
72                     e.printStackTrace();
73                 }
74             }
75 
76             if (conn != null) {
77                 try {
78                     conn.close();
79                 } catch (SQLException e) {
80                     e.printStackTrace();
81                 }
82             }
83         }
84 
85         return null;
86     }

    (3)测试

 1     @Test
 2     public void testLogin() {
 3         Scanner scanner = new Scanner(System.in);
 4         
 5         System.out.print("请输入用户名:");
 6         String user = scanner.nextLine();
 7         System.out.print("请输入密码:");
 8         String password = scanner.nextLine();
 9         String sql = "SELECT user,password FROM user_table WHERE user = '"+ user +"' AND password = '"+ password +"'";
10         User returnUser = get(sql,User.class);
11         if(returnUser != null){
12             System.out.println("登录成功");
13         }else{
14             System.out.println("用户名不存在或密码错误");
15         }
16     }

    通过控制台由用户输入用户名和密码,然后和SQL语句拼接在一起,然后让 Statement 对象去执行 SQL 语句并返回结果。

  2、Statement 的弊端

    通过上面的 Statement 操作数据表存在弊端:

    (1)问题一:存在拼串操作,繁琐

    (2)问题二:存在 SQL 注入问题

    问题一的现象显而易见,下面针对问题二进行说明。

    SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' ='1'),从而利用系统的 SQL 引擎完成恶意行为的做法。

    案例:

 1     // 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
 2     @Test
 3     public void testLogin() {
 4         Scanner scan = new Scanner(System.in);
 5 
 6         System.out.print("用户名:");
 7         String userName = scan.nextLine();
 8         System.out.print("密   码:");
 9         String password = scan.nextLine();
10 
11         // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
12         String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
13                 + "'";
14         User user = get(sql, User.class);
15         if (user != null) {
16             System.out.println("登陆成功!");
17         } else {
18             System.out.println("用户名或密码错误!");
19         }
20     }

     这样一来,原来且的关系,经过 SQL 注入后,变成了 或 的关系,而且只要有一个为真,整个语句都会执行。

    预防:对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来)取代 Statement 就可以了。

    

  3、综上

    

三、PreparedStatement 的介绍

  1、PreparedStatement 介绍

    可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象;

    PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句;

    PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象 setXxx() 方法来设置这些参数。

    setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从1开始),第二个是设置的 SQL 语句中的参数的值;

  2、PreparedStatement  VS Statement

    (1)代码的可读性和可维护性;

    (2)PreparedStatement 的最大可能提高性能

      ① DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer
编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。

      ② statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。

      ③ (语法检查,语义检查,翻译成二进制命令,缓存)

    (3)PreparedStatement 可以防止 SQL 注入

  3、Java 与 SQL 对应数据类型转换表

      

四、PreparedStatement 的使用(增删改)

  1、向 Customers 表中添加一条记录

    表结构:

 

    代码实现:

 1     @Test
 2     public void testInsert() {
 3         Connection conn = null;
 4         PreparedStatement ps = null;
 5         try {
 6             // 1.读取配置文件中的4个基本信息
 7             InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
 8 
 9             Properties pros = new Properties();
10             pros.load(is);
11 
12             String user = pros.getProperty("user");
13             String password = pros.getProperty("password");
14             String url = pros.getProperty("url");
15             String driverClass = pros.getProperty("driverClass");
16 
17             // 2.加载驱动
18             Class.forName(driverClass);
19 
20             // 3.获取连接
21             conn = DriverManager.getConnection(url, user, password);
22             
23             
24             //4.预编译sql语句,返回PreparedStatement的实例
25             String sql = "insert into customers(name,email,birth)values(?,?,?)";//?:占位符
26             ps = conn.prepareStatement(sql);
27             //5.填充占位符
28             ps.setString(1, "哪吒");
29             ps.setString(2, "nezha@gmail.com");
30             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
31             java.util.Date date = sdf.parse("1000-01-01");
32             ps.setDate(3, new Date(date.getTime()));
33             
34             //6.执行操作
35             ps.execute();
36         } catch (Exception e) {
37             e.printStackTrace();
38         }finally{
39             //7.资源的关闭
40             try {
41                 if(ps != null)
42                     ps.close();
43             } catch (SQLException e) {
44                 e.printStackTrace();
45             }
46             try {
47                 if(conn != null)
48                     conn.close();
49             } catch (SQLException e) {
50                 e.printStackTrace();
51             }
52             
53         }
54         
55     }

  2、封装工具类

    上面的案例中,每次执行数据库连接和关闭资源,都需要重复的操作,可以把这些重复的步骤抽取放到工具类中:

    JDBCUtils 操作数据库的工具类:

 1 public class JDBCUtils {
 2     
 3     /**
 4      * 
 5      * @Description 获取数据库的连接
 6      * @return
 7      * @throws Exception
 8      */
 9     public static Connection getConnection() throws Exception {
10         // 1.读取配置文件中的4个基本信息
11         InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
12 
13         Properties pros = new Properties();
14         pros.load(is);
15 
16         String user = pros.getProperty("user");
17         String password = pros.getProperty("password");
18         String url = pros.getProperty("url");
19         String driverClass = pros.getProperty("driverClass");
20 
21         // 2.加载驱动
22         Class.forName(driverClass);
23 
24         // 3.获取连接
25         Connection conn = DriverManager.getConnection(url, user, password);
26         return conn;
27     }
28     /**
29      * 
30      * @Description 关闭连接和Statement的操作
31      * @param conn
32      * @param ps
33      */
34     public static void closeResource(Connection conn,Statement ps){
35         try {
36             if(ps != null)
37                 ps.close();
38         } catch (SQLException e) {
39             e.printStackTrace();
40         }
41         try {
42             if(conn != null)
43                 conn.close();
44         } catch (SQLException e) {
45             e.printStackTrace();
46         }
47     }
48 }

  3、修改 Customers 表的一条记录

 1     @Test
 2     public void testUpdate(){
 3         Connection conn = null;
 4         PreparedStatement ps = null;
 5         try {
 6             //1.获取数据库的连接
 7             conn = JDBCUtils.getConnection();
 8             //2.预编译sql语句,返回PreparedStatement的实例
 9             String sql = "update customers set name = ? where id = ?";
10             ps = conn.prepareStatement(sql);
11             //3.填充占位符
12             ps.setObject(1,"莫扎特");
13             ps.setObject(2, 18);
14             //4.执行
15             ps.execute();
16         } catch (Exception e) {
17             e.printStackTrace();
18         }finally{
19             //5.资源的关闭
20             JDBCUtils.closeResource(conn, ps);
21         }
22     }

  4、通用的增删改操作

    实现:

 1     //通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
 2     public void update(String sql,Object ... args){ //sql中占位符的个数与可变形参的长度相同
 3         Connection conn = null;
 4         PreparedStatement ps = null;
 5         try {
 6             //1.获取数据库的连接
 7             conn = JDBCUtils.getConnection();
 8             
 9             //2.获取PreparedStatement的实例 (或:预编译sql语句)
10             ps = conn.prepareStatement(sql);
11             //3.填充占位符
12             for(int i = 0;i < args.length;i++){
13                 ps.setObject(i + 1, args[i]);
14             }
15             
16             //4.执行sql语句
17             ps.execute();
18         } catch (Exception e) {
19             
20             e.printStackTrace();
21         }finally{
22             //5.关闭资源
23             JDBCUtils.closeResource(conn, ps);
24             
25         }
26     }

    测试:

1     @Test
2     public void testCommonUpdate(){
3         //String sql = "delete from customers where id = ?";
4         //update(sql,3);
5         
6         String sql = "update `order` set order_name = ? where order_id = ?";
7         update(sql,"DD","2");
8         
9     }

    注意

      ① SQL 中占位符的个数与可变形参的长度相同;

      ② 第二个SQL语句中表名与关键字一样了,用着重好(`)进行区分;

      ③ 对于 PreparedStatement 占位符的赋值,可以使用 setXXX() 方式或者 setObject()方式

---方式一---
//ps.setString(1, "李四");
//ps.setString(2, "lisi@126.com");
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-DD");
java.util.Date date = sdf.parse("1997-10-14");
//ps.setDate(3, new Date(date.getTime()));

---方式二---
ps.setObject(1, "李四");
ps.setObject(2, "lisi@126.com");
ps.setObject(3, new Date(date.getTime()));

  

五、PreparedStatement的使用(查询)

  1、实现对 Customers 表的查询操作

 1     @Test
 2     public void testQuery() {
 3         Connection conn = null;
 4         PreparedStatement ps = null;
 5         ResultSet resultSet = null;
 6         try {
 7             conn = JDBCUtils.getConnection();
 8             String sql = "select id,name,email,birth from customers where id = ?";
 9             ps = conn.prepareStatement(sql);
10             ps.setObject(1, 1);
11             
12             //执行,并返回结果集
13             resultSet = ps.executeQuery();
14             //处理结果集
15             if(resultSet.next()){//next():判断结果集的下一条是否有数据,如果有数据返回true,并指针下移;如果返回false,指针不会下移。
16                 
17                 //获取当前这条数据的各个字段值
18                 int id = resultSet.getInt(1);
19                 String name = resultSet.getString(2);
20                 String email = resultSet.getString(3);
21                 Date birth = resultSet.getDate(4);
22                 
23             //方式一:
24             //System.out.println("id = " + id + ",name = " + name + ",email = " + email + ",birth = " + birth);
25                 
26             //方式二:
27             //Object[] data = new Object[]{id,name,email,birth};
28             //方式三:将数据封装为一个对象(推荐)
29             Customer customer = new Customer(id, name, email, birth);
30             System.out.println(customer);
31                 
32             }
33         } catch (Exception e) {
34             e.printStackTrace();
35         }finally{
36             //关闭资源
37             JDBCUtils.closeResource(conn, ps, resultSet);
38             
39         }
40         
41     }

  2、实现针对于 Customers 表的通用的查询操作

    实现:

 1     public Customer queryForCustomers(String sql,Object...args){
 2         Connection conn = null;
 3         PreparedStatement ps = null;
 4         ResultSet rs = null;
 5         try {
 6             conn = JDBCUtils.getConnection();
 7             
 8             ps = conn.prepareStatement(sql);
 9             for(int i = 0;i < args.length;i++){
10                 ps.setObject(i + 1, args[i]);
11             }
12             
13             rs = ps.executeQuery();
14             //获取结果集的元数据 :ResultSetMetaData
15             ResultSetMetaData rsmd = rs.getMetaData();
16             //通过ResultSetMetaData获取结果集中的列数
17             int columnCount = rsmd.getColumnCount();
18             
19             if(rs.next()){
20                 Customer cust = new Customer();
21                 //处理结果集一行数据中的每一个列
22                 for(int i = 0;i <columnCount;i++){
23                     //获取列值
24                     Object columValue = rs.getObject(i + 1);
25                     
26                     //获取每个列的列名
27                     String columnName = rsmd.getColumnName(i + 1);
28                     
29                     
30                     //给cust对象指定的columnName属性,赋值为columValue:通过反射
31                     Field field = Customer.class.getDeclaredField(columnLabel);
32                     field.setAccessible(true);
33                     field.set(cust, columValue);
34                 }
35                 return cust;
36             }
37         } catch (Exception e) {
38             e.printStackTrace();
39         }finally{
40             JDBCUtils.closeResource(conn, ps, rs);
41             
42         }
43         return null;
44         
45     }

    测试:

 1     @Test
 2     public void testQueryForCustomers(){
 3         String sql = "select id,name,birth,email from customers where id = ?";
 4         Customer customer = queryForCustomers(sql, 13);
 5         System.out.println(customer);
 6         
 7         sql = "select name,email from customers where name = ?";
 8         Customer customer1 = queryForCustomers(sql,"周杰伦");
 9         System.out.println(customer1);
10     }

    注意:对于查询结果的操作,可以调用 ResultSet中的  getXXX() 方法和 getObject()方法来获取值:

---方式一---
 //获取当前这条数据的各个字段值
int id = rs.getInt(1);
String name = rs.getString(2);
String email = rs.getString(3);
Date birth = rs.getDate(4);

---方式二---
Object columValue = rs.getObject(i + 1);

  

  3、实现对 Order 表的查询操作

    Order 表结构:

   

    代码实现:

 1     @Test
 2     public void testQuery1(){
 3         
 4         Connection conn = null;
 5         PreparedStatement ps = null;
 6         ResultSet rs = null;
 7         try {
 8             conn = JDBCUtils.getConnection();
 9             String sql = "select order_id,order_name,order_date from `order` where order_id = ?";
10             ps = conn.prepareStatement(sql);
11             ps.setObject(1, 1);
12             
13             rs = ps.executeQuery();
14             if(rs.next()){
15                 int id = (int) rs.getObject(1);
16                 String name = (String) rs.getObject(2);
17                 Date date = (Date) rs.getObject(3);
18                 
19                 Order order = new Order(id, name, date);
20                 System.out.println(order);
21             }
22         } catch (Exception e) {
23             e.printStackTrace();
24         }finally{
25             
26             JDBCUtils.closeResource(conn, ps, rs);
27         }
28         
29     }

  4、针对于 Order 表的查询操作

    根据表创建 Order 类

 1 public class Order {
 2     private int orderId;
 3     private String orderName;
 4     private Date orderDate;
 5     
 6     
 7     public Order() {
 8         super();
 9     }
10     public Order(int orderId, String orderName, Date orderDate) {
11         super();
12         this.orderId = orderId;
13         this.orderName = orderName;
14         this.orderDate = orderDate;
15     }
16     public int getOrderId() {
17         return orderId;
18     }
19     public void setOrderId(int orderId) {
20         this.orderId = orderId;
21     }
22     public String getOrderName() {
23         return orderName;
24     }
25     public void setOrderName(String orderName) {
26         this.orderName = orderName;
27     }
28     public Date getOrderDate() {
29         return orderDate;
30     }
31     public void setOrderDate(Date orderDate) {
32         this.orderDate = orderDate;
33     }
34     @Override
35     public String toString() {
36         return "Order [orderId=" + orderId + ", orderName=" + orderName + ", orderDate=" + orderDate + "]";
37     }
38 }

    针对 order 表的查询操作

 1     public Order orderForQuery(String sql,Object...args){
 2         
 3         Connection conn = null;
 4         PreparedStatement ps = null;
 5         ResultSet rs = null;
 6         try {
 7             conn = JDBCUtils.getConnection();
 8             ps = conn.prepareStatement(sql);
 9             for(int i = 0;i < args.length;i++){
10                 ps.setObject(i + 1, args[i]);
11             }
12             
13             //执行,获取结果集
14             rs = ps.executeQuery();
15             //获取结果集的元数据
16             ResultSetMetaData rsmd = rs.getMetaData();
17             //获取列数
18             int columnCount = rsmd.getColumnCount();
19             if(rs.next()){
20                 Order order = new Order();
21                 for(int i = 0;i < columnCount;i++){
22                     //获取每个列的列值:通过ResultSet
23                     Object columnValue = rs.getObject(i + 1);
24                     //通过ResultSetMetaData
25                     //获取列的列名:getColumnName() --不推荐使用
26                     //获取列的别名:getColumnLabel()
27                     //String columnName = rsmd.getColumnName(i + 1);
28                     String columnLabel = rsmd.getColumnLabel(i + 1);
29                     
30                     //通过反射,将对象指定名columnName的属性赋值为指定的值columnValue
31                     Field field = Order.class.getDeclaredField(columnLabel);
32                     field.setAccessible(true);
33                     field.set(order, columnValue);
34                 }
35                 
36                 return order;
37             }
38         } catch (Exception e) {
39             e.printStackTrace();
40         }finally{
41             
42             JDBCUtils.closeResource(conn, ps, rs);
43         }
44         return null;
45     }

    测试:

1     @Test
2     public void testOrderForQuery(){
3         String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
4         Order order = orderForQuery(sql,1);
5         System.out.println(order);
6     }

    注意:针对于表的字段名与类的属性名不相同的情况:

      (1)必须声明 SQL 时,使用类的属性名来命名字段的别名;

      (2)使用 ResultSetMetaData 时,需要使用 getColumnLabel() 来替换 getColumnName()获取列的别名;

      (3)如果 SQL 中没有给字段起别名,getColumnLabel() 获取的就是列名;

  5、针对于不同的表的通用的查询操作,返回表中的一条记录

 1   public <T> T getInstance(Class<T> clazz,String sql, Object... args) {
 2         Connection conn = null;
 3         PreparedStatement ps = null;
 4         ResultSet rs = null;
 5         try {
 6             conn = JDBCUtils.getConnection();
 7 
 8             ps = conn.prepareStatement(sql);
 9             for (int i = 0; i < args.length; i++) {
10                 ps.setObject(i + 1, args[i]);
11             }
12 
13             rs = ps.executeQuery();
14             // 获取结果集的元数据 :ResultSetMetaData
15             ResultSetMetaData rsmd = rs.getMetaData();
16             // 通过ResultSetMetaData获取结果集中的列数
17             int columnCount = rsmd.getColumnCount();
18 
19             if (rs.next()) {
20                 T t = clazz.newInstance();
21                 // 处理结果集一行数据中的每一个列
22                 for (int i = 0; i < columnCount; i++) {
23                     // 获取列值
24                     Object columValue = rs.getObject(i + 1);
25 
26                     // 获取每个列的列名
27                     // String columnName = rsmd.getColumnName(i + 1);
28                     String columnLabel = rsmd.getColumnLabel(i + 1);
29 
30                     // 给t对象指定的columnName属性,赋值为columValue:通过反射
31                     Field field = clazz.getDeclaredField(columnLabel);
32                     field.setAccessible(true);
33                     field.set(t, columValue);
34                 }
35                 return t;
36             }
37         } catch (Exception e) {
38             e.printStackTrace();
39         } finally {
40             JDBCUtils.closeResource(conn, ps, rs);
41 
42         }
43 
44         return null;
45     }

    测试:

 1     @Test
 2     public void testGetInstance(){
 3         String sql = "select id,name,email from customers where id = ?";
 4         Customer customer = getInstance(Customer.class,sql,12);
 5         System.out.println(customer);
 6         
 7         String sql1 = "select order_id orderId,order_name orderName from `order` where order_id = ?";
 8         Order order = getInstance(Order.class, sql1, 1);
 9         System.out.println(order);
10     }

  6、针对于不同的表的通用的查询操作,返回表中的多条记录

    代码实现:

 1     public <T> List<T> getForList(Class<T> clazz,String sql, Object... args){
 2         Connection conn = null;
 3         PreparedStatement ps = null;
 4         ResultSet rs = null;
 5         try {
 6             conn = JDBCUtils.getConnection();
 7 
 8             ps = conn.prepareStatement(sql);
 9             for (int i = 0; i < args.length; i++) {
10                 ps.setObject(i + 1, args[i]);
11             }
12 
13             rs = ps.executeQuery();
14             // 获取结果集的元数据 :ResultSetMetaData
15             ResultSetMetaData rsmd = rs.getMetaData();
16             // 通过ResultSetMetaData获取结果集中的列数
17             int columnCount = rsmd.getColumnCount();
18             //创建集合对象
19             ArrayList<T> list = new ArrayList<T>();
20             while (rs.next()) {
21                 T t = clazz.newInstance();
22                 // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
23                 for (int i = 0; i < columnCount; i++) {
24                     // 获取列值
25                     Object columValue = rs.getObject(i + 1);
26 
27                     // 获取每个列的列名
28                     // String columnName = rsmd.getColumnName(i + 1);
29                     String columnLabel = rsmd.getColumnLabel(i + 1);
30 
31                     // 给t对象指定的columnName属性,赋值为columValue:通过反射
32                     Field field = clazz.getDeclaredField(columnLabel);
33                     field.setAccessible(true);
34                     field.set(t, columValue);
35                 }
36                 list.add(t);
37             }
38             
39             return list;
40         } catch (Exception e) {
41             e.printStackTrace();
42         } finally {
43             JDBCUtils.closeResource(conn, ps, rs);
44 
45         }
46 
47         return null;
48     }

    测试:

 1     @Test
 2     public void testGetForList(){
 3         
 4         String sql = "select id,name,email from customers where id < ?";
 5         List<Customer> list = getForList(Customer.class,sql,12);
 6         list.forEach(System.out::println);
 7         
 8         String sql1 = "select order_id orderId,order_name orderName from `order`";
 9         List<Order> orderList = getForList(Order.class, sql1);
10         orderList.forEach(System.out::println);
11     }

  7、 使用 PreparedStatement 替换 Statement,解决 SQL 注入问题

    代码实现:

 1     public <T> T getInstance(Class<T> clazz,String sql, Object... args) {
 2         Connection conn = null;
 3         PreparedStatement ps = null;
 4         ResultSet rs = null;
 5         try {
 6             conn = JDBCUtils.getConnection();
 7 
 8             ps = conn.prepareStatement(sql);
 9             for (int i = 0; i < args.length; i++) {
10                 ps.setObject(i + 1, args[i]);
11             }
12 
13             rs = ps.executeQuery();
14             // 获取结果集的元数据 :ResultSetMetaData
15             ResultSetMetaData rsmd = rs.getMetaData();
16             // 通过ResultSetMetaData获取结果集中的列数
17             int columnCount = rsmd.getColumnCount();
18 
19             if (rs.next()) {
20                 T t = clazz.newInstance();
21                 // 处理结果集一行数据中的每一个列
22                 for (int i = 0; i < columnCount; i++) {
23                     // 获取列值
24                     Object columValue = rs.getObject(i + 1);
25 
26                     // 获取每个列的列名
27                     // String columnName = rsmd.getColumnName(i + 1);
28                     String columnLabel = rsmd.getColumnLabel(i + 1);
29 
30                     // 给t对象指定的columnName属性,赋值为columValue:通过反射
31                     Field field = clazz.getDeclaredField(columnLabel);
32                     field.setAccessible(true);
33                     field.set(t, columValue);
34                 }
35                 return t;
36             }
37         } catch (Exception e) {
38             e.printStackTrace();
39         } finally {
40             JDBCUtils.closeResource(conn, ps, rs);
41 
42         }
43 
44         return null;
45     }

    测试:

 1     @Test
 2     public void testLogin() {
 3         Scanner scanner = new Scanner(System.in);
 4         
 5         System.out.print("请输入用户名:");
 6         String user = scanner.nextLine();
 7         System.out.print("请输入密码:");
 8         String password = scanner.nextLine();
 9         //SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
10         String sql = "SELECT user,password FROM user_table WHERE user = ? and password = ?";
11         User returnUser = getInstance(User.class,sql,user,password);
12         if(returnUser != null){
13             System.out.println("登录成功");
14         }else{
15             System.out.println("用户名不存在或密码错误");
16         }
17     }

    思考:为什么 PreparedStatement 能防止 SQL 注入呢?

    因为在获取 PreparedStatement 对象时已经传入 SQL 语句了,这个时候已经确定了 SQL 语句(比如 and 关系、or 关系),只是在等待参数进行填充。而 Statement 语句执行的都是静态的 SQL 语句,所以会发生 SQL 注入。

    除了解决Statement的拼串、sql问题之外,PreparedStatement还有哪些好处呢?

    (1)PreparedStatement 可以操作 Blob 的数据,而 Statement 做不到;

    (2)PreparedStatement 可以实现更高效的批量操作;

六、ResultSet 与 ResultMetaData

  1、ResultSet

    查询需要调用 PreparedStatement 的 executeQuery() 方法,查询结果是一个 ResultSet 对象;

    ResultSet 对象以逻辑表格的形式封装了执行数据库操作的结果集,ResultSet 接口由数据库厂商实现;

    ResultSet 返回的实际上就是一张数据表,有一个指针指向数据表的第一条记录的前面;

    ResultSet 对象维护了一个指向当前数据行的游标,初始的时候,游标在一行之前,可以通过ResultSet 对象的 next() 方法移动到下一行。调用 next() 方法检测下一行是否有效。若有效,该方法返回 true,且指针下移。相当于Iterator 对象的 hasNext() 和 next(0 方法的结合体;

    当指针指向一行时,可以通过调用 getXxx(int index) 或 getXxx(String cloumnName) 获取每一列的值;

      例如:

getInt(1);
getString("name");

       注意:Java 与数据库交互涉及到的相关Java API 中的索引都从1开始。

    ResultSet 接口的常用方法:

boolean next();
getString();

  

    

  2、ResultSetMetaData

    可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

ResultMetaData meta = rs.getMetaData();

getColumnName(int column): 获取指定列的名称
getColumnLabel(int column):获取指定列的别名
getColumnCount():返回当前 ResultSet 对象中的列数
getColumnTypeName(int column):检索指定列的数据库特定的类型名称
getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位
isNullable(int column):指示指定列中的值是否可以为 null
isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的

      

     问题1:得到结果后,如果知道该结果集中有哪些列?列名是什么?

      需要使用一个描述 ResultSet 的对象,即 ResultSetMetaData

    问题2:关于 ResultSetMetaData

      (1)如何获取 ResultSetMetaData:调用ResultSet 的 getMetaData() 方法即可;

      (2)获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法;

      (3)获取 ResultSet 每一列的列的别名是什么:调用 ResultSetMetaData 的 getColumnLabel() 方法

    

七、资源的释放

    释放 ResultSet、Statement、Connection;

    数据库连接(Connection) 是非常稀有的资源,用完必须马上释放,如果 Connection 不能及时正确的关闭将导致系统宕机。Connection 的使用原则是尽量晚创建,尽量早的释放

    可以在 finally 中关闭,保证及时其他代码出现异常,资源也一定能被关闭;

    关闭资源:

 1     public static void closeResource(Connection conn,Statement ps,ResultSet rs){
 2         try {
 3             if(ps != null)
 4                 ps.close();
 5         } catch (SQLException e) {
 6             e.printStackTrace();
 7         }
 8         try {
 9             if(conn != null)
10                 conn.close();
11         } catch (SQLException e) {
12             e.printStackTrace();
13         }
14         try {
15             if(rs != null)
16                 rs.close();
17         } catch (SQLException e) {
18             e.printStackTrace();
19         }
20     }

    

八、JDBC API 小结

    两种思想:

      (1)面向接口编程的思想

      (2)ORM 思想(Object Relational mapping)

        ① 一个数据表对应一个 Java 类;

        ② 表中一条记录对应 Java 类的一个对象;

        ③ 表中的一个字段对应 Java 类的一个属性;

        注意:SQL 是需要结合列名和表的属性名来写,注意起别名。

    两种技术

      (1)JDBC 结果集的元素(ResultSetMetaData)

        ① 获取列数:getColumnCount()

        ② 获取列的别名:getColumnLabel()

      (2)通过反射,创建指定类的对象,获取指定的属性并赋值

原文地址:https://www.cnblogs.com/niujifei/p/14995157.html