1. 使用PreparedStatement实现CRUD操作基础知识

1.1. 操作和访问数据库

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

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

    • Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
    • PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
    • CallableStatement:用于执行 SQL 存储过程。

1.2. PreparedStatement概念

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

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

  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值

1.3. Java和SQL数据类型

1.4. ResultSet与ResultSetMetaData

1.4.1. ResultSet

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

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

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

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

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

例如: getInt(1), getString("name")
Java与数据库交互涉及到的相关Java API中的索引都从1开始。

  • ResultSet 接口的常用方法:
    • boolean next()
    • getString()
    • getObject()

1.4.2. ResultSetMetaData

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

    • ResultSetMetaData meta = rs.getMetaData();
    • getColumnName(int column):获取指定列的名称
    • getColumnLabel(int column):获取指定列的别名
    • getColumnCount():返回当前 ResultSet 对象中的列数。
    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。
    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。
    • isNullable(int column):指示指定列中的值是否可以为 null。
    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。
  • 得到结果集后需要使用一个描述 ResultSet 的对象, 即 ResultSetMetaData.

    • 调用 ResultSet 的 getMetaData() 方法获取 ResultSetMetaData
    • 调用 ResultSetMetaData 的 getColumnCount() 方法获取 ResultSet 中有多少列
    • 调用 ResultSetMetaData 的getColumnLabel() 方法获取 ResultSet 每一列的列的别名是什么

1.4.3. 资源的释放

  • 释放ResultSet, Statement,Connection。

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

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

  • 通用增删改操作

public void update(String sql,Object... args){
		Connection conn = JDBCUtils.getConnection ("jdbc.properties");
		PreparedStatement statement = null;
		try {
			 statement = conn.prepareStatement (sql);
			 for (int i = 0;i < args.length;i++){
			 	statement.setObject (i+1,args[i]);
			 }
			 statement.execute ();
		} catch (SQLException e) {
			e.printStackTrace ();
		}finally {
			JDBCUtils.closeResource (conn,statement);
		}

}
  • 通用查询操作
public static List<ClassInfo> query(String sql, Object... args) throws SQLException, NoSuchFieldException, IllegalAccessException {
		//连接数据库
		Connection conn = JDBCUtils.getConnection ("jdbc.properties");

		PreparedStatement preparedStatement = conn.prepareStatement (sql);
		for (int i = 0;i < args.length;i++){
			preparedStatement.setObject (i + 1,args[i]);
		}

		ResultSet resultSet = preparedStatement.executeQuery();
		//结果集的元数据
		ResultSetMetaData metaData = resultSet.getMetaData ();
		int columnCount = metaData.getColumnCount ();

		List resList = new ArrayList ();

		while(resultSet.next ()){
			ClassInfo temp = new ClassInfo ();
			for (int i = 0;i < columnCount;i++){
				//获取列值
				Object value = resultSet.getObject (i + 1);
				//获取列名
				String columnName = metaData.getColumnName (i + 1);
				//利用反射设置值
				Field valueField = ClassInfo.class.getDeclaredField (columnName);
				valueField.setAccessible (true);
				valueField.set (temp,value);
				//添加到返回集合中
				resList.add (temp);
			}
		}
		return resList;
}

2. 操作BLOB字段

2.1. MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。

  • 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。

  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)

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

2.2. 插入Blob字段

public static int insertBlob() throws IOException, ClassNotFoundException, SQLException {
		InputStream inputStream = ClassLoader.getSystemResourceAsStream ("jdbc.properties");

		Properties properties = new Properties ();
		assert inputStream != null;
		properties.load (inputStream);

		String url = properties.getProperty ("url");
		Class.forName ("com.mysql.jdbc.Driver");
		Connection conn = DriverManager.getConnection (url,properties);

		String sql = "insert into testBlob(name,email,age,photo) values(?,?,?,?)";
		PreparedStatement statement = conn.prepareStatement (sql);

		statement.setObject (1,"孙悟空");
		statement.setObject (2,"sunwukong@qq.com");
		statement.setObject (3,1500);

		FileInputStream in = new FileInputStream ("G:\\Java_WorkSpace\\JDBC\\TestPreparedStatement\\src\\sunwukong.jpg");
		statement.setBlob (4,in);

		return statement.executeUpdate ();
}

2.3. 修改/更新Blob字段

public static int updateBlob() throws Exception {
		InputStream in = ClassLoader.getSystemResourceAsStream ("jdbc.properties");

		Properties properties = new Properties ();
		properties.load (in);

		String url = properties.getProperty ("url");
		Class.forName ("com.mysql.jdbc.Driver");
		Connection conn = DriverManager.getConnection (url,properties);
		String sql = "update testblob set photo = ? where name = ? ";
		PreparedStatement statement = conn.prepareStatement (sql);
		InputStream inputStream = new FileInputStream("G:\\Java_WorkSpace\\JDBC\\TestPreparedStatement\\beijing123.jpg");
		statement.setBlob (1,inputStream);
		statement.setObject (2,"金克丝");

		return statement.executeUpdate ();
	}
2.4. 读取Blob字段
public static void readBlob() throws Exception {
		Connection conn = JDBCUtils.getConnection ("jdbc.properties");
		String sql = "select id,name,email,age,photo from testblob where email = ?";
		assert conn != null;
		PreparedStatement statement = conn.prepareStatement (sql);
		statement.setObject (1,"787645957@qq.com");

		ResultSet resultSet = statement.executeQuery ();

		if (resultSet.next ()){

			int id = resultSet.getInt ("id");
			String name = resultSet.getString ("name");
			String email = resultSet.getString ("email");
			int age = resultSet.getInt ("age");
			Student student = new Student (id,name,email,age);
			Blob photo = resultSet.getBlob ("photo");

			InputStream inputStream = photo.getBinaryStream ();
			//保存为liyao.jpg
			OutputStream outputStream = new FileOutputStream ("liyao.jpg");

			byte[] buf = inputStream.readAllBytes ();
			outputStream.write (buf,0,buf.length);

			System.out.println (student);
		}
}

3. 批量插入/批量执行SQL语句

3.1. 批量执行SQL语句

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

  • mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。

  • 把**?rewriteBatchedStatements=true写在配置文件的url后面或者&rewriteBatchedStatements=true**写在其他参数后面。

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

    • addBatch(String):添加需要批量处理的SQL语句或是参数;
    • executeBatch():执行批量处理语句;
    • clearBatch():清空缓存的数据
  • 通常我们会遇到两种批量执行SQL语句的情况:

    • 多条SQL语句的批量处理;
    • 一个SQL语句的批量传参;

3.2. 批量插入

public static void piInsert() throws SQLException {
		long start = System.currentTimeMillis ();
		//JDBCUtils是一个工具类,封装了数据库连接,返回一个连接
		Connection conn = JDBCUtils.getConnection ("jdbc.properties");
		conn.setAutoCommit (false);

		String sql = "insert into pichuli(name) values(?)";
		PreparedStatement statement = conn.prepareStatement (sql);

		for (int i = 0;i < 20000;i++){
			statement.setObject (1,"student" + (i+1));
			//积攒sql语句
			statement.addBatch ();
			if (i % 500 == 0){
				//执行
				statement.executeBatch ();
				//清空
				statement.clearBatch ();
			}
			//执行最后一条
			if (i == 19999){
				statement.executeBatch ();
				statement.clearBatch ();
			}

		}
		//提交数据
		conn.commit ();
		//设置为默认的自动提交
		conn.setAutoCommit (true);
		long end = System.currentTimeMillis ();
		System.out.println ("执行时间: " + (end - start) / 1000);
}

4. 数据库事务

4.1. 注意事项和操作

  • 数据一旦提交,就不可回滚。
  • 数据什么时候意味着提交?
  • 当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
  • **关闭数据库连接,数据就会自动的提交。**如果多个操作,每个操作使用的是自己单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
  • JDBC程序中为了让多个 SQL 语句作为一个事务执行:
    • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
    • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
    • 在出现异常时,调用 rollback(); 方法回滚事务
    • 若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,恢复自动提交状态。

举例:

	public static void updateBalance()  {
		Connection conn = null;
		try {
			conn = JDBCUtils.getConnection ("jdbc.properties");
			conn.setAutoCommit (false);
			String sql1 = "update `pichuli` set balance = balance - 100 where `name` = 'lux' ";
			String sql2 = "update `pichuli` set balance = balance + 100 where `name` = 'jinx' ";

			PreparedStatement statement1 = conn.prepareStatement (sql1);
			PreparedStatement statement2 = conn.prepareStatement (sql2);

			try {
				statement1.executeUpdate ();
				statement2.executeUpdate ();
			} catch (SQLException e) {
				conn.rollback ();
				e.printStackTrace ();
			}
			try {
				conn.commit ();
				System.out.println ("修改余额成功!");
			} catch (SQLException e) {
				conn.rollback ();

				e.printStackTrace ();
			}
		} catch (SQLException e) {
			e.printStackTrace ();
		} finally {
			try {
				conn.setAutoCommit (true);
			} catch (SQLException e) {
				e.printStackTrace ();
			}
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace ();
			}
		}
	}

4.2. 事务的ACID属性

  • 原子性(Atomicity)
    原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  • 一致性(Consistency)
    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

  • 隔离性(Isolation)
    事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性(Durability)
    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。