一、操作和访问数据库
数据库连接用于向数据库服务器发送命令和 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)通过反射,创建指定类的对象,获取指定的属性并赋值