JDBC 获取连接、增删改查操作

举报
Chloe. 发表于 2022/05/23 17:19:32 2022/05/23
【摘要】 康师傅的程序里总要把数据库操作用try/catch包起来,不很理解 操作和访问数据库数据库连接被用于向数据库发送命令和SQL语句,并接受数据库服务器返回的结果。其实数据库连接就是一个Socket连接。在java.sql包中有三个接口分别定义了对数据库的调用的不同方式:Statement:用于执行静态SQL语句并返回他所生成的;PreparedStatement: SQL语句被 预编译并储存在...

康师傅的程序里总要把数据库操作用try/catch包起来,不很理解

操作和访问数据库

  • 数据库连接被用于向数据库发送命令和SQL语句,并接受数据库服务器返回的结果。其实数据库连接就是一个Socket连接。
  • 在java.sql包中有三个接口分别定义了对数据库的调用的不同方式:
    • Statement:用于执行静态SQL语句并返回他所生成的;
    • PreparedStatement: SQL语句被 预编译并储存在此对象中,可以使用此对象多次高效地执行该条语句;
    • CallableStatement: 用于执行SQL存储过程。

JAVA与SQL对应数据类型转换表

JAVA类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
java.sql.Timestamp BINARY,VAR BINARY
byte array DATE
java.sql.Date TIME
java.sql.Time TIMESTAMP

Statement

方法

  • 通过调用Connection对象的createStatement()方法创建该对象;
  • Statement接口中定义了下列方法用于执行SQL语句
int excuteUpdate(String sql)
    //执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql)
    //执行查询操作SELECT

使用Statement操作的弊端

  • 拼接字符串操作繁琐

  • 存在SQL注入问题

    • 利用系统里没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句,完成恶意操作。

    • eg. SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’ (恒成立)

示例代码

public class StatementTest {

	// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
	@Test
	public void testLogin() {
		Scanner scan = new Scanner(System.in);

		System.out.print("用户名:");
		String userName = scan.nextLine();
		System.out.print("密   码:");
		String password = scan.nextLine();

        //恶意注入操作
		// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
		String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
				+ "'";
        //字符串连接操作繁琐
        
		User user = get(sql, User.class);
		if (user != null) {
			System.out.println("登陆成功!");
		} else {
			System.out.println("用户名或密码错误!");
		}
	}

	// 使用Statement实现对数据表的查询操作
	public <T> T get(String sql, Class<T> clazz) {
		T t = null;

		Connection conn = null;
		Statement st = null;
		ResultSet rs = null;
		try {
			// 1.加载配置文件
			InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
			Properties pros = new Properties();
			pros.load(is);

			// 2.读取配置信息
			String user = pros.getProperty("user");
			String password = pros.getProperty("password");
			String url = pros.getProperty("url");
			String driverClass = pros.getProperty("driverClass");

			// 3.加载驱动
			Class.forName(driverClass);

			// 4.获取连接
			conn = DriverManager.getConnection(url, user, password);
			st = conn.createStatement();
			rs = st.executeQuery(sql);

			// 获取结果集的元数据
			ResultSetMetaData rsmd = rs.getMetaData();

			// 获取结果集的列数
			int columnCount = rsmd.getColumnCount();

			if (rs.next()) {
				t = clazz.newInstance();
				for (int i = 0; i < columnCount; i++) {
					// //1. 获取列的名称
					// String columnName = rsmd.getColumnName(i+1);

					// 1. 获取列的别名
					String columnName = rsmd.getColumnLabel(i + 1);
					
                    // 2. 根据列名获取对应数据表中的数据
					Object columnVal = rs.getObject(columnName);

					// 3. 将数据表中得到的数据,封装进对象
					Field field = clazz.getDeclaredField(columnName);
					field.setAccessible(true);
					field.set(t, columnVal);
				}
				return t;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 关闭资源
			if (rs != null) {
				try {
					rs.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
			if (st != null) {
				try {
					st.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}

			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
			}
		}

		return null;
	}
}

PreparedStatement

  • 是Connection对象中的方法,可以通过调用这个方法对sql语句进行操作,包括预编译、设置占位符等等;
  • 可以通过调用 Connection 对象的 PreparedStatement(String sql) 方法获取PreparedStatement对象;
  • PreparedStatement是Statement子接口,表示一条预编译SQL语句;
  • PreparedStatement 对象所代表SQL语句中的参数用 ’ ?‘ 表示(占位符),通过调用 PreparedStatement 中的set方法来设置占位符。setXxx(索引,参数的值),注意数据库表格中的索引是从1开始的。

PreparedStatement优点

  • 代码的可读性和可维护性
  • 防止 SQL 注入
  • 可以最大可能提高性能
    • 数据库服务器会对编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被数据库服务器编译后执行代码会被缓存下来,那么下次调用相同的预编译语句时就不要编译,是需要将参数直接传入编译过的语句执行代码中,就会得到执行;
    • 在Statement语句中即使是相同的操作,但会因为数据内容的不一样,所以整个语句本身不能匹配,没有缓存语句的意义,数据库服务器也不会对普通语句编译后的执行代码缓存,这样每执行一次就要对传入的语句编译一次。

打开和关闭资源的封装

  • 执行每一个对数据库的操作之前,都需要打开驱动和连接,操作完之后都需要关闭资源,因此可以对这两步操作进行封装——封装进工具类(JDBCUtils)里。
public class JDBCUtils(){
    public static Connection getConnection() throws Exception {
        //1.加载配置文件
        InputStream is= ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(is);

        //2.读取配置信息
        String user=pros.getProperty("user");
        String password = pros.getProperty("password");
        String url= pros.getProperty("url");
        String driverClass=pros.getProperty("driverClass");

        //3.加载驱动
        Class.forName(driverClass);

        //4.获取连接
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url,user,password);
            System.out.println("数据库连接创建成功!");
        } catch (SQLException e) {
            e.printStackTrace();
            System.out.println("数据库连接创建失败!");
        }
        return conn;
    }
    
    //public static void closeResourse(Connection conn, PreparedStatement ps){}
    //PreparedStatement是statement子接口,则定义的时候可以用大一些的类来定义
    public static void closeResourse(Connection conn, Statement ps){
        try {
            if (ps != null) {
                ps.close();
            }
        }catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn != null){
                conn.close();
            }
        }catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    //重载关闭资源的方法
    //针对查询操作还要多关闭一个结果集
    public static void closeResourse(Connection conn, Statement ps,ResultSet rs){
        try {
            if (ps != null) {
                ps.close();
            }
        }catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (rs != null) {
                rs.close();
            }
        }catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if(conn != null){
                conn.close();
            }
        }catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

增删改

  • 思考无返回值的增删改查操作,每次操作无非是获取连接、预编译SQL语句、填充占位符、执行语句、关闭资源,则可以对这三个操作进行封装,操作时将增删改查不同的SQL语句和占位符传进方法即可。

    package indi.jdbc.demo;
    
    import utility.JDBCutils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    
    /**
     * Created by Intellij IDEA.
     * User:  ZouYue
     * Date:  2022/4/23  18:55
     * @author ZouYue
     */
    
    public class PreparedStatementUpdateTest {
    
        JDBCutils jdbCutils= new JDBCutils();
    
        public static void main(String[] args) {
            /*String sql1 = "insert into student (name,email,birth,id) values(?,?,?,?)";
            if (update(sql1,"米老鼠","MLS@milao.com","3001-01-01",17)) {
                System.out.println("数据添加成功!");
            }*/
    
            /*String sql2 = "insert into student (name,email,birth,id) values(?,?,?,?)";
            if (update(sql2,"灰太狼","HTL@huitai.com","4001-01-01",18)) {
                System.out.println("数据添加成功!");
            }*/
    
            /*String sql3 = "delete from student where name=?";
            if (update(sql3,"灰太狼")) {
                System.out.println("数据删除成功!");
            }*/
    
            String sql4="update student set birth=? where name=?";
            if (update(sql4,"4021-09-09","米老鼠")) {
                System.out.println("数据修改成功!");
            }
    
    
        }
    
        public static boolean update(String sql, Object... args){
            //用可变形参获取占位符
            //可变形参可以看成一个数组
            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++){
                    //注意下标:数据库中是从1开始,而数组中是从0开始
                    ps.setObject(i+1,args[i]);
                }
                //4.执行语句
                ps.execute();
            }catch (Exception e) {
                e.printStackTrace();
                return false;
            }finally {
                //5.关闭资源
                JDBCutils.closeResourse(conn,ps);
            }
            return true;
        }
    
    }
    
    

查询

OMR编程思想(object relational mapping)

  • 一个数据表对应一个java类
  • 表中的一条记录对应java类的一个对象
  • 表中的一个字段对应java类的一个属性

针对某张表中单条数据的查询操作

package indi.jdbc.demo;

import bean.Customer;
import utility.JDBCutils;

import java.lang.reflect.Field;
import java.sql.*;

/**
 * Created by Intellij IDEA.
 * User:  ZouYue
 * Date:  2022/4/23  20:17
 * @author ZouYue
 */
public class PreparedStatementQuery {
    static JDBCutils jdbcutils=new JDBCutils();

    public static void main(String[] args) throws Exception {
        //testQuery1();
        String sql = "select * from student where id=?";
        System.out.println(testQuery2(sql,2).print());
    }

    //针对student表数据的通用查询操作
    public static Customer testQuery2(String sql,Object...args) throws Exception {
        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()) {
                Customer customer=new Customer();
                for (int j = 0; j < columnCount; j++) {
                    //获取列值,因为不知道数据类型,则直接用object获取
                    Object columnValue=rs.getObject(j+1);
                    //获取每个列的名字
                    String columnName=rsmd.getColumnName(j+1);
                    //通过反射,给customer对象中给定的columnName属性,赋值为columnValue
                    Field field=Customer.class.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(customer,columnValue);
                }
                return customer;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCutils.closeResourse(conn,ps,rs);
        }
        return null;
    }


    //针对student表的数据的查询操作
    public static void testQuery1(){
        Connection conn = null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        try {
            //1.获取连接
            conn = JDBCutils.getConnection();
            //2.SQL语句,通过PreparedStatement进行SQL语句的操作
            String sql = "select * from student";
            ps=conn.prepareStatement(sql);

            //3.执行语句:返回结果集(表格中的一行数据)
            rs=ps.executeQuery();
            //4.处理结果集
            //while用于多条记录的查询
            //if用于单条记录的查询
            //next判断是否有下一条数据,有true,没有false
            while (rs.next()) {
                //获取当前记录中的各个字段
                String name = rs.getString(1);
                String email = rs.getString(2);
                Date birth=rs.getDate(3);
                int id=rs.getInt(4);
                //将些数据封装进一个类里
                Customer customer = new Customer(name,email,birth,id);
                System.out.println(customer.print());
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //5.关闭资源
            JDBCutils.closeResourse(conn,ps,rs);
        }
    }
}
通过getColumnLabel解决表中列名与属性名不同的情况
  • SQL语句对列名重命名;

  • 当遇到表格列明和类中属性不同名的情况,获取列名的时候,直接使用getColumnLabel获取相应属性名(此方法更常用也更安全);

package indi.jdbc.demo;

import bean.CustomerTest;
import utility.JDBCutils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 * Created by Intellij IDEA.
 * User:  ZouYue
 * Date:  2022/4/24  16:03
 * @author ZouYue
 */
public class PreparedStatementQuery2 {

    static JDBCutils jdbcutils=new JDBCutils();

    public static void main(String[] args){
        //对表格和类中名字不同的属性,进行重命名
        String sql="select name name1,email email1,birth birth1,id id1 from student where id=?";
        System.out.println(testQuery3(sql,1).print());
    }

    // 针对表格中列名与类中属性不同的情况
    // getColumnLabel()的使用
    public static CustomerTest testQuery3(String sql,Object...args) throws Exception {

        Connection conn=null;
        PreparedStatement ps=null;
        ResultSet rs = null;
        try{
            //1.获取连接
            conn = JDBCutils.getConnection();
            ps= conn.prepareStatement(sql);
            //2.填充占位符
            for(int i = 0; i < args.length; i++){
                ps.setObject(i+1, args[i]);
            }
            //3.获取数据元
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount=rsmd.getColumnCount();
            if (rs.next()){
                CustomerTest ct=new CustomerTest();
                for(int j = 0; j < columnCount; j++){
                    //获取数据元
                    Object columnValue=rs.getObject(j+1);
                    //获取列名
                    //String columnName = rsmd.getColumnName(j+1);
                    //由于表格中列名和CustomerTest中属性的名字不一样,报错:NoSuchFieldException
                    //更安全的用法应当用getColumnLabel()获取列的标签,也就是CustomerTest中定义的属性名
                    //且当名称不一样时,对应sql语句中也要对筛选的数据进行重命名
                    String columnLabel= rsmd.getColumnLabel(j+1);
                    //通过反射,将获取的数据封装到类中
                    Field field=CustomerTest.class.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(ct,columnValue);
                }
                return ct;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCutils.closeResourse(conn,ps,rs);
        }
        return null;
    }

}

针对某张表中查询的多条数据

	// 通用的针对于不同表的查询:返回一个对象 (version 1.0)
	public <T> T getInstance(Class<T> clazz, String sql, Object... args) {

		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = 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.执行executeQuery(),得到结果集:ResultSet
			rs = ps.executeQuery();

			// 5.得到结果集的元数据:ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();

			// 6.1通过ResultSetMetaData得到columnCount,columnLabel;通过ResultSet得到列值
			int columnCount = rsmd.getColumnCount();
			if (rs.next()) {
				T t = clazz.newInstance();
				for (int i = 0; i < columnCount; i++) {// 遍历每一个列

					// 获取列值
					Object columnVal = rs.getObject(i + 1);
					// 获取列的别名:列的别名,使用类的属性名充当
					String columnLabel = rsmd.getColumnLabel(i + 1);
					// 6.2使用反射,给对象的相应属性赋值
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columnVal);

				}

				return t;

			}
		} catch (Exception e) {

			e.printStackTrace();
		} finally {
			// 7.关闭资源
			JDBCUtils.closeResource(conn, ps, rs);
		}

		return null;

	}

查询操作的流程

针对不同表的单条记录的查询操作

package indi.jdbc.demo;

import bean.Customer;
import utility.JDBCutils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 * Created by Intellij IDEA.
 * User:  ZouYue
 * Date:  2022/4/25  20:42
 * @author ZouYue
 */


public class PreparedStatementQuery3 {

    public static void main(String[] args) {
        String sql="select * from customer where id=?";
        System.out.println(testQuery3(Customer.class,sql, 1).print());

    }

    // 使用PreparedStatement针对不同表的查询单条数据的操作
    // 使用泛型
    public static <T> T testQuery3(Class<T> clazz, String sql, Object...args){

        Connection conn=null;
        PreparedStatement ps=null;
        ResultSet rs = null;
        try{
            //1.获取连接
            conn = JDBCutils.getConnection();
            ps= conn.prepareStatement(sql);
            //2.填充占位符
            for(int i = 0; i < args.length; i++){
                ps.setObject(i+1, args[i]);
            }
            //3.获取数据元
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount=rsmd.getColumnCount();
            if (rs.next()){
                //对于java中任何一个类,通过此方法调用一个空参构造器
                //但是在我的IDEA中说是已过时,不懂
                T t= clazz.newInstance();
                for(int j = 0; j < columnCount; j++){
                    Object columnValue=rs.getObject(j+1);
                    String columnLabel= rsmd.getColumnLabel(j+1);
                    Field field= clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCutils.closeResourse(conn,ps,rs);
        }
        return null;
    }
}

针对不同表的多条记录的查询操作

  • 将查找到的多条数据存放到集合中返回
package indi.jdbc.demo;

import bean.Customer;
import utility.JDBCutils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Intellij IDEA.
 * User:  ZouYue
 * Date:  2022/4/25  20:42
 * @author ZouYue
 */


public class PreparedStatementQuery3 {

    public static void main(String[] args) {

    String sql2="select * from customer where id<?";
        List<Customer> listCustomer = testQuery4(Customer.class,sql2,5);
        for (Customer c : listCustomer) {
            System.out.println(c.print());
        }
    }

    // 使用PreparedStatement针对不同表的查询多条数据的操作
    // 将多条数据存到集合里
    // 查找不到数据:①list.size()=0;②异常
    public static <T> List<T> testQuery4(Class<T> clazz, String sql, Object...args){

        Connection conn=null;
        PreparedStatement ps=null;
        ResultSet rs = null;
        try{
            //1.获取连接
            conn = JDBCutils.getConnection();
            ps= conn.prepareStatement(sql);
            //2.填充占位符
            for(int i = 0; i < args.length; i++){
                ps.setObject(i+1, args[i]);
            }
            //3.获取数据元
            rs = ps.executeQuery();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount=rsmd.getColumnCount();

            ArrayList<T> list = new ArrayList<T>();
            while (rs.next()){
                //对于java中任何一个类,通过此方法调用一个空参构造器
                T t= clazz.newInstance();
                for(int j = 0; j < columnCount; j++){
                    Object columnValue=rs.getObject(j+1);
                    String columnLabel= rsmd.getColumnLabel(j+1);
                    Field field= clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCutils.closeResourse(conn,ps,rs);
        }
        return null;
    }

}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。