使用JDBC进行增删改查

前一天给大家分享了一些基本的JDBC知识,今天给大家做了一分最详尽的增删改查总结。Let’s go!

复习一下MySQL的语法

作用

关键字

举例

是否需要返回数据

添加(增)

INSERT

INSERT INTO customers(id)VALUES(9)

显示受到影响,不返回值

删除(删)

DELETE

DELETE FROM customers WHERE id=9

同上

修改(改)

UPDATE

update customers set name = ‘周星驰’ where id = 9

同上

查询(查)

SELECT

SELECT * FROM customers

有结果返回

对上述表进行分析,发现对表的添加、删除以及修改都是不需要返回值的,按照长期对于Java代码的理解,那么,我们就像代码中不需要返回值的一律为void方法,我们可以试着把它归为一类,而查询是需要由返回值的,我们就只能将其单独划分一类。

添加、删除以及修改

一、我们先来分析一下我们如果需要对一个数据库的表进行操作,需要那几步叭

  • ①对数据库进行连接(昨天讲过了)
  • ②写sql语句(因为这个肯定也是需要自己编写sql语句进行操作的)
  • ③执行sql语句
  • ④关闭资源

二、以添加为例子先看看

/**添加*/
	/**因为涉及到关闭资源,这里就不采用throw来抛出异常,采用的是try……catch的形式*/
    public void connection(){
        PreparedStatement ps=null;
        Connection conn =null;
        //读取配置文件中的4个基本信息
        try {
            InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(inputStream);


            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");

            //加载区间
            Class.forName(driverClass);

            //获取连接
            conn = DriverManager.getConnection(url,user,password);
            

            //4预编译sql语句,返回PreparedStatement的实例
            String sql = "insert into customers(name,email,birth)values(?,?,?)";
            ps = conn.prepareStatement(sql);

            //填充占位符
            ps.setString(1,"小阿七");
            ps.setString(2,"xiaoaqi@gmail.com");
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            java.util.Date date = sdf.parse("1996-09-08");
            ps.setDate(3,new Date(date.getTime()));


            //执行操作
            //execute()执行sql语句,返回的是一个布尔值
            ps.execute();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            //释放资源
            try {
                if(conn!=null && ps!=null) {
                    conn.close();
                    ps.close();
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
    }

结果展示

  • 添加前的数据

java开发增删改查流程图 java增删改查思路_database

  • 添加后的数据

java开发增删改查流程图 java增删改查思路_java_02

分析一下,上面的的代码,我们不管做哪一个操作,我们都需要的是连接,执行还有释放资源,仅仅只是语句和占位符不一样,这里我们完全可以把她的 代码单独用一个方法包装起来。下面我们试着取写一个通用的方法。

将代码升级到通用的情况

jdbc.properties文件

user=你的用户名

password=用户名对应的密码

url=连接数据库的地址

java开发增删改查流程图 java增删改查思路_java_03

driverClass=com.mysql.jdbc.Driver

注:文件里面的配置文件尽量不要使用空格,否则会出现报错嫌疑!!!

user=root
password=123456
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver

通用的工具类

看下面的代码想一想为什么本人会在设置连接时选择的是用throws关键字抛出异常,而在关闭资源时选择用try……catch来处理异常呢?

/**设置连接*/
    public static Connection getConnection() throws Exception{
        //读取配置文件中的4个基本信息
        InputStream inputStream = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
        pros.load(inputStream);
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");
        //加载区间
        Class.forName(driverClass);

        //获取连接
        Connection conn = DriverManager.getConnection(url,user,password);
        return conn;
    }
    /**关闭资源*/
    public static void closeResoure (Connection conn,PreparedStatement ps){
        try {
            if(ps!=null) {
                ps.close();
            }
        } catch (SQLException throwables) {
        	//
            throwables.printStackTrace();
        }
        try {
            if(conn!=null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
	/**重载一下关闭流,方便查询操作时使用*/
	public static void closeResoure (Connection conn,PreparedStatement ps,ResultSet rs) {
        try {
            if(ps!=null) {
                ps.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(conn!=null) {
                conn.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        try {
            if(rs!=null) {
                rs.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

原因:在连接时也可以使用try……catch抛出,但是为了便捷不使用,而在关闭资源时,是因为使用try-with-resource时,如果对外部资源的处理和对外部资源的关闭均遭遇了异常,“关闭异常”将被抑制,“处理异常”将被抛出,但“关闭异常”并没有丢失,而是存放在“处理异常”的被抑制的异常列表中。所以采用try……catch可以将关闭异常也抛出。

通用的增删改

因为通用,所以对应的SQL语句也会不同,所以我们需要传入不同的数据参数,所以需要利用一个字符串,将我们的SQL语句传入,因为我们采用的是PreparedStatement或者Statement来进行语句的编译,所以还需要填充占位符来进行传入。

/**通用的增删改操作*/
    public void update(String sql,Object ...args) throws Exception {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = JDBCUtils.getConnection();
            ps = conn.prepareStatement(sql);

            //填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1,args[ i ]);
            }
            //执行
            ps.execute();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        JDBCUtils.closeResoure(conn,ps);
    }

测试一下

有人会发现我这串代码其实是有一点小瑕疵的吗?要是你发现了,请偷偷自己改正,不用说出来,留给大家自己想想瑕疵在何处哟。

@Test
    /**修改*/
    public void testUpdate() throws Exception{
        Connection conn = JDBCUtils.getConnection();
        String sql = "update customers set name = ? where id = ?";
        PreparedStatement ps = conn.prepareStatement(sql);
        //填充占位符
        ps.setObject(1,"莫扎特");
        ps.setObject(2,18);
        //大家觉得这里需要ps.execute();吗?
        //其实可以要起,方便理解,但是你不要也问题不大,自己去探索这个小秘密呢
        JDBCUtils.closeResoure(conn,ps);
    }

这里就写修改了,因为修改执行忘记截图了,反正大家的数据库又不一样,需要使用的自行修改一下jdbc.properties文件路径,还有sql语句哟。

查询操作

一、同上我们一起来分析一下 查询操作又可以分为几步呢?

  • ①对数据库进行连接(昨天讲过了)
  • ②写sql语句(因为这个肯定也是需要自己编写sql语句进行操作的)
  • ③执行sql语句并得到返回值这里需要使用这个executeQuery()方法,因为它具有返回结果集的作用
  • ④对得到的结果集进行遍历输出处理
  • ⑤关闭资源

二、这里就直接先上分析

因为我们返回的结果肯定是对 某一个表中的元素值进行返回,所以我们定义一个对象来进行保存我们结果值,顺便可以直接重写tostring方法来进行遍历打印!nice!干!

普通版查询

/**返回的一条记录*/
    public  <T> T getInstance(Class<T> tClass, String sql,Object...args){
        Connection conn =null;
        ResultSet rs =null;
        PreparedStatement ps = 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 columnCount = rsmd.getColumnCount();
            if(rs.next()){
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
                    //获取列的列名,不推荐使用
                    //不采用列名时,不符合sql语句特点,因为有时候我们需要对数据取别名嘛
//                    String columnName = rsmd.getColumnName(i + 1);
                    //获取列的别名,推荐使用,因为当我们未采用别名时也会获取到本身得名字
                    String columnLable = rsmd.getColumnLabel(i + 1);
                    //利用反射为每一个对象进行赋值操作赋值
                    Field field = tClass.getDeclaredField(columnLable);
                    //不知道是否为私有变量
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                return t;
            }
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                JDBCUtils.closeResoure(conn,ps,rs);
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
        return null;
    }

分析,因为这样子我们的结果只能一条语句一样的输出,于是想到了使用集合来保存每一条数据,但是集合类型不一致,所以得使用泛型定义。

升级改进版

/**返回多条记录*/
    public  <T> List<T> getInstanceList(Class<T> tClass,String sql,Object...args){
        Connection conn =null;
        ResultSet rs =null;
        PreparedStatement ps = null;
        //创建集合对象
        ArrayList<T> list = new ArrayList<>();
        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 columnCount = rsmd.getColumnCount();
            while (rs.next()){
                T t = tClass.newInstance();
                for (int i = 0; i < columnCount; i++) {
                    //获取列值
                    Object columnValue = rs.getObject(i + 1);
                    //获取列的列名,不推荐使用
                    //String columnName = rsmd.getColumnName(i + 1);
                    //获取列的别名
                    String columnLable = rsmd.getColumnLabel(i + 1);
                    
                    //利用反射为每一个对象进行赋值操作赋值
                    Field field = tClass.getDeclaredField(columnLable);
                    field.setAccessible(true);
                    field.set(t,columnValue);
                }
                list.add(t);
            }
            return list;
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                JDBCUtils.closeResoure(conn,ps,rs);
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
        return null;
    }

测试一下升级版查询

@Test
    public void testGetInstanceList(){
        String sql = "select id,name from Customers where id < ?";
        List<Customer> list = getInstanceList(Customer.class,sql,12);
        list.forEach(System.out::println);
    }

System.out::println是我偶然发现的,我的猜测来源如下:

List<String> list = Arrays.asList("AA", "BB");
//第一种
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
//第二种
for (String s : list) {
    System.out.println(s);
}
//第三种 lambda 表达式
list.forEach(s-> System.out.println(s));

Blob数据类型

你以为结束了吗?不?难道你的数据库就保存一些简单得东西吗?就存储一些类似于记事本的东西吗?你乐意,我不行,毕竟我还得给自己的数据保存下来,云端太贵,自己加密,手机坏了咋办,上传数据库,嗯需要直接拷走!nice!

Blob类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

类型

大小(单元:字节)

TinyBlob

最大 255

Blob

最大 65K

MediumBlob

最大 16M

LongBlob

最大 4G

  • 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
  • 需要注意的是:如果存储的文件过大,数据库的性能会下降。
  • 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。

Blob操作

因为删除数据我们基本上就可以直接用正常的数据删除就可以了,因为删除时肯定一整条数据就没了呀,所以我们主要是涉及到数据的上传、修改和删除操作。下面的代码没有去写通用的操作,如果有需要的可以自行去理解,可以验证你上面的代码是否🆗了。没有结果展示,不是没结果,而是博主觉得没有必要放这样子没有意义的结果。因为看结果反正也是在数据库里面去查看的。下面对于数据流有不懂的小伙伴可以看我io流的博客,有详解。

  • 添加
/**添加*/
@Test
public void test1(){
    Connection conn =null;
    PreparedStatement ps =null;
    try {
        conn = JDBCUtils.getConnection();
        String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
        ps = conn.prepareStatement(sql);
        //name
        ps.setString(1, "pier");
        //email
        ps.setString(2, "pier@163.com");
        //日期
        ps.setDate(3, new Date(new java.util.Date().getTime()));
        //使用数据流的方式对数据上传
        FileInputStream stream = new FileInputStream(new File("src/com/File/359e19687b3ad3aa908919095148cf8d.gif"));
        ps.setBlob(4,stream);
		//没有数据返回所以还是使用execute()方法
        ps.execute();
        stream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        try{
            JDBCUtils.closeResoure(conn,ps);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}
  • 修改
/**修改*/
@Test
public void test2(){
    Connection conn =null;
    PreparedStatement ps =null;
    try {
        conn = JDBCUtils.getConnection();
        String sql = "update customers set photo = ? where id = ?";
        ps = conn.prepareStatement(sql);

        // 填充占位符
        // 操作Blob类型的变量
        FileInputStream fis = new FileInputStream("src/com/File/微信图片扭头.jpg");
        ps.setBlob(1, fis);
        ps.setInt(2, 6);
        ps.execute();

        fis.close();
    }catch (Exception e){
        System.out.println(e.getMessage());
    }finally {
        try {
            JDBCUtils.closeResoure(conn,ps);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}
  • 查看
/**读取Blob文件采用流的方式读取下载**/
@Test
public void aVoid() {
    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
        String sql = "SELECT id, name, email, birth, photo FROM customers WHERE id = ?";
        conn = JDBCUtils.getConnection();
        ps = conn.prepareStatement(sql);
        ps.setInt(1,6);
        //这里有返回值,所以使用executeQuery方法,获取结果
        rs = ps.executeQuery();
        if ( rs.next() ) {
            int id = rs.getInt("id");
            String email = rs.getString("email");
            String name = rs.getString("name");
            Date birth = rs.getDate("birth");
            Customer cust = new Customer(id,name,email,birth);
            System.out.println(cust);
            //读取Blob类型的字段保存在本地
            Blob photo = rs.getBlob("photo");
            InputStream is = photo.getBinaryStream();
            OutputStream os = new FileOutputStream("copy.jpg");
            byte[] buffer = new byte[ 1024 ];
            int len = 0;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer,0,len);
            }
            JDBCUtils.closeResoure(conn,ps,rs);

            if ( is != null ) {
                is.close();
            }
            if ( os != null ) {
                os.close();
            }
        }
    }catch (Exception e){
        System.out.println(e.getMessage());
    }
}

批量插入

有人说,博主!博主!我学会了基本的增删改了,可以下课了嘛?

java开发增删改查流程图 java增删改查思路_数据库_04

我问一个问题,假如说我要是一下子需要插入两万条数据怎么办呢?

啊!这。

是不是懵圈了,继续叭!接下来我们一起看看批量插入。

博主博主,那待会是不是还有批量删除批量修改呀?

你!嗯!真是个大聪明。 批量操作Update、delete本身就具有批量操作的效果insert此时批量操作就主要指的是批量插入。

嗯,那我们应该有两种思路叭,Statement和PrepareStatement,但是你之前提过PrepareStatement效率更高,应该是采用它来写叭。

既然你都提了,我们就先来看看Statement的代码,看看为什么叭?

  • 批量执行SQL语句

当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

JDBC的批量处理语句包括下面三个方法:

  • addBatch(String):添加需要批量处理的SQL语句或是参数;
  • executeBatch():执行批量处理语句;
  • clearBatch():清空缓存的数据

通常我们会遇到两种批量执行SQL语句的情况:

  • 多条SQL语句的批量处理;
  • 一个SQL语句的批量传参;
  • Statement
* 方式一:Statement**/
@Test
public void InsertTest1() throws Exception{
    long start = System.currentTimeMillis();
    //使用statement
    Connection conn = null;
    conn = JDBCUtils.getConnection();
    Statement st = conn.createStatement();
    for (int i = 0; i < 20000; i++) {
        String sql = "insert into goods(name)values('name_"+"')";
        st.execute(sql);
    }
    conn.close();
    st.close();
    long end = System.currentTimeMillis();
    //结束时间
    //两次测试耗时时间
    //①27297ms
    //②29062ms
    System.out.println(end-start+"ms");
}
  • PrepareStatement
@Test
public void testInsert1() throws Exception{
    long start = System.currentTimeMillis();
    Connection conn = JDBCUtils.getConnection();
    String sql = "insert into goods(name)values(?)";
    PreparedStatement ps = conn.prepareStatement(sql);
    for (int i = 0; i < 20000; i++) {
        ps.setObject(1,"name_"+i);
        ps.execute();
    }
    conn.close();
    ps.close();
    long end = System.currentTimeMillis();
    //结束时间
    //两次测试耗时时间
    //①28991ms
    //②27088ms
    System.out.println(end-start+"ms");
}

这个代码的比较可能看不出来差距,但是这里面的差距,还算是有的

PreparedStatement 能最大可能提高性能:

  • DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
  • 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
  • (语法检查,语义检查,翻译成二进制命令,缓存)

将其升级

/*
 * 修改1: 使用 addBatch() / executeBatch() / clearBatch()
 * 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
 * 		 ?rewriteBatchedStatements=true 写在配置文件的url后面
 * 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
 * 
 */
@Test
public void testInsert1() throws Exception{
	long start = System.currentTimeMillis();
		
	Connection conn = JDBCUtils.getConnection();
		
	String sql = "insert into goods(name)values(?)";
	PreparedStatement ps = conn.prepareStatement(sql);
		
	for(int i = 1;i <= 1000000;i++){
		ps.setString(1, "name_" + i);	
		//1.“攒”sql
		ps.addBatch();
		if(i % 500 == 0){
			//2.执行
			ps.executeBatch();
			//3.清空
			ps.clearBatch();
		}
	}	
	long end = System.currentTimeMillis();
	System.out.println("花费的时间为:" + (end - start));//20000条:625                                                                         //1000000条:14733  
	JDBCUtils.closeResource(conn, ps);
}

最终理解版

/*
* 层次四:在层次三的基础上操作
* 使用Connection 的 setAutoCommit(false)  /  commit()
*/
@Test
public void testInsert2() throws Exception{
	long start = System.currentTimeMillis();
	Connection conn = JDBCUtils.getConnection();
	//1.设置为不自动提交数据
	conn.setAutoCommit(false);
	String sql = "insert into goods(name)values(?)";
	PreparedStatement ps = conn.prepareStatement(sql);
	for(int i = 1;i <= 1000000;i++){
		ps.setString(1, "name_" + i);
		//1.“攒”sql
		ps.addBatch();
			
		if(i % 500 == 0){
			//2.执行
			ps.executeBatch();
			//3.清空
			ps.clearBatch();
		}
	}
	//2.提交数据
	conn.commit();
	long end = System.currentTimeMillis();
	System.out.println("花费的时间为:" + (end - start));//1000000条:4978 
	JDBCUtils.closeResource(conn, ps);
}

终于搞定利用Java对数据进行完整的增删改查了,接下来,开始干它!毕竟我们还知道数据库可不仅仅是增删改查,但是基本的数据我们不会有问题了叭!

下课

java开发增删改查流程图 java增删改查思路_sql_05