Java面向对象系列[v1.0.0][管理结果集]

本文深入探讨JDBC中的ResultSet,包括如何使用ResultSetMetaData获取元数据信息,如何滚动和更新结果集,以及处理Blob类型数据。通过示例代码展示了如何创建可滚动、可更新的结果集,并进行数据操作。同时,讲解了Blob数据的存储与检索,以及如何在Java中显示Blob数据。

使用ResultSetMetaData分析结果集元数据

JDBC使用ResultSet来封装执行查询得到的查询结果,然后通过移动ResultSet的记录指针来取出结果集的内容,此外JDBC还允许通过ResultSet来更新记录,并提供了ResultSetMetaData来获取ResultSet对象的相关信息

可滚动可更新的结果集

ResultSet定位记录指针的方法有absolute()、previous()等方法,通常来说可以使用absolute()、previous()、afterLast()等方法自由移动记录指针的ResultSet被称为可滚动的结果集,在JDK1.4之前默认打开的ResultSet是不可滚动的,必须在创建Statement或PreparedStatement时传入额外的参数,在JDK5.0以后,默认打开的ResultSet就是可滚动的,无须传入额外参数

以默认方式打开的ResultSet是不可更新的,如果希望其可更新,则必须在创建Statement或PreparedStatement时传入额外的参数

  • resultSetType:控制ResultSet的类型
    • ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针智能向前移动,这是JDK1.4之前的默认值
    • ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动(可滚动的结果集),但底层数据的改变不会影响ResultSet的内容【需要数据库支持】
    • ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自由移动(可滚动的结果集),而且底层数据的改变会影响ResultSet的内容【需要数据库支持】
  • resultSetConcurrency:控制ResultSet的并发类型
    • ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式(默认)
    • ResultSet.CONCUR_UPDATABLE:该常量指示REsultSet是可更新的并发模式
// 使用Connection创建一个PreparedStatement对象
// 传入控制结果集可滚动、可更新的参数
pstmt = conn.preparedStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);

可更新的结果集还需满足如下两个条件:

  • 所有数据都应该来自一个表
  • 选出的数据集必须包含主键列
更新结果集

程序可调用ResultSet的updateXxx(int columnIndex, Xxx value)方法来修改记录指针所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改
Java8为ResultSet添加了updateObject(String columnLabel, Object x, SQLType targetSqlType)updateObject(int columnIndex, Object x, SQLType targetSqlType)两个默认方法,这两个方法可以直接用Object来修改记录指针所指记录、特定列的值,其中SQLType用于指定该数据列的类型

代码示例
import java.util.*;
import java.io.*;
import java.sql.*;/**
 * Description:
 * 网站: <a href="http://www.crazyit.org">疯狂Java联盟</a><br>
 * Copyright (C), 2001-2020, Yeeku.H.Lee<br>
 * This program is protected by copyright laws.<br>
 * Program Name:<br>
 * Date:<br>
 * @author Yeeku.H.Lee kongyeeku@163.com
 * @version 5.0
 */
public class ResultSetTest
{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void query(String sql) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		try (
			// 获取数据库连接
			Connection conn = DriverManager.getConnection(url, user, pass);
			// 使用Connection来创建一个PreparedStatement对象
			// 传入控制结果集可滚动,可更新的参数。
			PreparedStatement pstmt = conn.prepareStatement(sql,
				ResultSet.TYPE_SCROLL_INSENSITIVE,
				ResultSet.CONCUR_UPDATABLE);
			ResultSet rs = pstmt.executeQuery())
		{
			rs.last();
			int rowCount = rs.getRow();
			for (var i = rowCount; i > 0; i--)
			{
				rs.absolute(i);
				System.out.println(rs.getString(1) + "\t"
					+ rs.getString(2) + "\t" + rs.getString(3));
				// 修改记录指针所有记录、第2列的值
				rs.updateString(2, "学生名" + i);
				// 提交修改
				rs.updateRow();
			}
		}
	}
	public static void main(String[] args) throws Exception
	{
		var rt = new ResultSetTest();
		rt.initParam("mysql.ini");
		rt.query("select * from student_table");
	}
}

CMD
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java ResultSetTest
cmd

处理Blob类型数据

  • Blob(Binary Long Object)二进制长对象,Blob列通常用于存储大文件,典型的Blob内容是一张图片或一个音频文件,使用Blob列可以把图片,音频等文件的二进制数据保存在数据库里,并可以从数据库里恢复指定文件
  • 如果需要将图片插入数据库,因为有一个关键问题Blob常量无法表示,显然不能直接使用普通的SQL来完成,因此将Blob数据插入数据库需要使用PreparedStatement,该对象有一个方法setBinaryStream(int parameterIndex, InputStream x)该方法可以为指定参数传入二进制输入流,从而将Blob数据保存到数据库里
  • 当需要从ResultSet里取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法,该方法将返回一个Blob对象,Blob对象提供了getBinaryStream()方法来获取该Blob数据的输入流,也可以使用Blob对象提供的getBytes()方法直接取出该Blob对象封装的二进制数据
create table img_table
(
	img_id int auto_increment primary key,
	image name varchar(255),
	img_data mediumblob
);

建个新表,其中img_data列用于存储图片数据,该列的数据类型为mediumblob类型,而不是blob类型,因为MySQL数据库里的blob类型最多只能存储64KB,mediumblob类型可以存储16MB

代码示例

import java.sql.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.Properties;
import java.util.ArrayList;
import java.io.*;
import javax.swing.filechooser.FileFilter;

public class BlobTest
{
	JFrame jf = new JFrame("图片管理程序");
	private static Connection conn;
	private static PreparedStatement insert;
	private static PreparedStatement query;
	private static PreparedStatement queryAll;
	// 定义一个DefaultListModel对象
	private DefaultListModel<ImageHolder> imageModel
		= new DefaultListModel<>();
	private JList<ImageHolder> imageList = new JList<>(imageModel);
	private JTextField filePath = new JTextField(26);
	private JButton browserBn = new JButton("...");
	private JButton uploadBn = new JButton("上传");
	private JLabel imageLabel = new JLabel();
	// 以当前路径创建文件选择器
	JFileChooser chooser = new JFileChooser(".");
	// 创建文件过滤器
	ExtensionFileFilter filter = new ExtensionFileFilter();
	static
	{
		try
		{
			var props = new Properties();
			props.load(new FileInputStream("mysql.ini"));
			var driver = props.getProperty("driver");
			var url = props.getProperty("url");
			var user = props.getProperty("user");
			var pass = props.getProperty("pass");
			Class.forName(driver);
			// 获取数据库连接
			conn = DriverManager.getConnection(url, user, pass);
			// 创建执行插入的PreparedStatement对象
			// 该对象执行插入后可以返回自动生成的主键
			insert = conn.prepareStatement("insert into img_table"
				+ " values(null, ?, ?)", Statement.RETURN_GENERATED_KEYS);
			// 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片
			query = conn.prepareStatement("select img_data from img_table"
				+ " where img_id = ?");
			queryAll = conn.prepareStatement("select img_id, "
				+ "img_name from img_table");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	public void init() throws SQLException
	{
		// -------初始化文件选择器--------
		filter.addExtension("jpg");
		filter.addExtension("jpeg");
		filter.addExtension("gif");
		filter.addExtension("png");
		filter.setDescription("图片文件(*.jpg, *.jpeg, *.gif, *.png)");
		chooser.addChoosableFileFilter(filter);
		// 禁止“文件类型”下拉列表中显示“所有文件”选项
		chooser.setAcceptAllFileFilterUsed(false);
		// ---------初始化程序界面---------
		fillListModel();
		filePath.setEditable(false);
		// 只能单选
		imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		var jp = new JPanel();
		jp.add(filePath);
		jp.add(browserBn);
		browserBn.addActionListener(event -> {
			// 显示文件对话框
			int result = chooser.showDialog(jf, "浏览图片文件上传");
			// 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮
			if (result == JFileChooser.APPROVE_OPTION)
			{
				filePath.setText(chooser.getSelectedFile().getPath());
			}
		});
		jp.add(uploadBn);
		uploadBn.addActionListener(avt -> {
			// 如果上传文件的文本框有内容
			if (filePath.getText().trim().length() > 0)
			{
				// 将指定文件保存到数据库
				upload(filePath.getText());
				// 清空文本框内容
				filePath.setText("");
			}
		});
		var left = new JPanel();
		left.setLayout(new BorderLayout());
		left.add(new JScrollPane(imageLabel), BorderLayout.CENTER);
		left.add(jp, BorderLayout.SOUTH);
		jf.add(left);
		imageList.setFixedCellWidth(160);
		jf.add(new JScrollPane(imageList), BorderLayout.EAST);
		imageList.addMouseListener(new MouseAdapter()
		{
			public void mouseClicked(MouseEvent e)
			{
				// 如果鼠标双击
				if (e.getClickCount() >= 2)
				{
					// 取出选中的List项
					ImageHolder cur = (ImageHolder) imageList.
					getSelectedValue();
					try
					{
						// 显示选中项对应的Image
						showImage(cur.getId());
					}
					catch (SQLException sqle)
					{
						sqle.printStackTrace();
					}
				}
			}
		});
		jf.setSize(620, 400);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
	}
	// ----------查找img_table填充ListModel----------
	public void fillListModel() throws SQLException
	{
		try (
			// 执行查询
			ResultSet rs = queryAll.executeQuery())
		{
			// 先清除所有元素
			imageModel.clear();
			// 把查询的全部记录添加到ListModel中
			while (rs.next())
			{
				imageModel.addElement(new ImageHolder(rs.getInt(1),
					rs.getString(2)));
			}
		}
	}
	// ---------将指定图片放入数据库---------
	public void upload(String fileName)
	{
		// 截取文件名
		String imageName = fileName.substring(fileName.lastIndexOf('\\')
			+ 1, fileName.lastIndexOf('.'));
		var f = new File(fileName);
		try (
			var is = new FileInputStream(f))
		{
			// 设置图片名参数
			insert.setString(1, imageName);
			// 设置二进制流参数
			insert.setBinaryStream(2, is, (int) f.length());
			int affect = insert.executeUpdate();
			if (affect == 1)
			{
				// 重新更新ListModel,将会让JList显示最新的图片列表
				fillListModel();
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	// ---------根据图片ID来显示图片----------
	public void showImage(int id) throws SQLException
	{
		// 设置参数
		query.setInt(1, id);
		try (
			// 执行查询
			ResultSet rs = query.executeQuery())
		{
			if (rs.next())
			{
				// 取出Blob列
				Blob imgBlob = rs.getBlob(1);
				// 取出Blob列里的数据
				var icon = new ImageIcon(imgBlob.getBytes(1L, (int) imgBlob.length()));
				imageLabel.setIcon(icon);
			}
		}
	}
	public static void main(String[] args) throws SQLException
	{
		new BlobTest().init();
	}
}
// 创建FileFilter的子类,用以实现文件过滤功能
class ExtensionFileFilter extends FileFilter
{
	private String description = "";
	private ArrayList<String> extensions = new ArrayList<>();
	// 自定义方法,用于添加文件扩展名
	public void addExtension(String extension)
	{
		if (!extension.startsWith("."))
		{
			extension = "." + extension;
			extensions.add(extension.toLowerCase());
		}
	}
	// 用于设置该文件过滤器的描述文本
	public void setDescription(String aDescription)
	{
		description = aDescription;
	}
	// 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本
	public String getDescription()
	{
		return description;
	}
	// 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件
	public boolean accept(File f)
	{
		// 如果该文件是路径,接受该文件
		if (f.isDirectory()) return true;
		// 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写)
		String name = f.getName().toLowerCase();
		// 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受
		for (var extension : extensions)
		{
			if (name.endsWith(extension))
			{
				return true;
			}
		}
		return false;
	}
}
// 创建一个ImageHolder类,用于封装图片名、图片ID
class ImageHolder
{
	// 封装图片的ID
	private int id;
	// 封装图片的名字
	private String name;
	public ImageHolder(){}
	public ImageHolder(int id, String name)
	{
		this.id = id;
		this.name = name;
	}
	// id的setter和getter方法
	public void setId(int id)
	{
		this.id = id;
	}
	public int getId()
	{
		return this.id;
	}
	// name的setter和getter方法
	public void setName(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return this.name;
	}
	// 重写toString()方法,返回图片名
	public String toString()
	{
		return name;
	}
}

这段代码实现了图片上传也就是保存到数据库,并在右边的列表框中显示图片的名字,双击列表框中的图片名时,左边窗口将显示该图片,实际上就是根据选中的ID从数据库里查找该图片,并显示

CMD
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java BlobTest
cmd

使用ResultSetMetaData分析结果集

当执行SQL查询后,可以通过移动记录指针来遍历ResultSet的每条记录,但程序可能不清楚该ResultSet里包含哪些数据列,以及每个数据列的数据类型,可以通过ResultSetMetaData来获取

MetaData的意思是元数据的意思,也就是描述其他数据的数据,ResultSetMetaData封装了描述ResultSet对象的数据

ResultSet提供了getMetaData()方法,该方法返回该ResultSet对应的ResultSetMetaData对象,一旦获得了ResultSetMetaData对象就可以通过ResultSetMetaData提供的方法来返回ResultSet的信息

  • int getColumnCount():返回该ResultSet的列数量
  • String getColumnName(int column): 返回指定索引的列名
  • int getColumnType(int column):返回指定索引的列类型
代码示例

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import java.util.*;
import java.io.*;
import java.sql.*;

public class QueryExecutor
{
	JFrame jf = new JFrame("查询执行器");
	private JScrollPane scrollPane;
	private JButton execBn = new JButton("查询");
	// 用于输入查询语句的文本框
	private JTextField sqlField = new JTextField(45);
	private static Connection conn;
	private static Statement stmt;
	// 采用静态初始化块来初始化Connection、Statement对象
	static
	{
		try
		{
			var props = new Properties();
			props.load(new FileInputStream("mysql.ini"));
			String drivers = props.getProperty("driver");
			String url = props.getProperty("url");
			String username = props.getProperty("user");
			String password = props.getProperty("pass");
			// 加载数据库驱动
			Class.forName(drivers);
			// 取得数据库连接
			conn = DriverManager.getConnection(url, username, password);
			stmt = conn.createStatement();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	// --------初始化界面的方法---------
	public void init()
	{
		var top = new JPanel();
		top.add(new JLabel("输入查询语句:"));
		top.add(sqlField);
		top.add(execBn);
		// 为执行按钮、单行文本框添加事件监听器
		execBn.addActionListener(new ExceListener());
		sqlField.addActionListener(new ExceListener());
		jf.add(top, BorderLayout.NORTH);
		jf.setSize(680, 480);
		jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		jf.setVisible(true);
	}
	// 定义监听器
	class ExceListener implements ActionListener
	{
		public void actionPerformed(ActionEvent evt)
		{
			// 删除原来的JTable(JTable使用scrollPane来包装)
			if (scrollPane != null)
			{
				jf.remove(scrollPane);
			}
			try (
				// 根据用户输入的SQL执行查询
				ResultSet rs = stmt.executeQuery(sqlField.getText()))
			{
				// 取出ResultSet的MetaData
				ResultSetMetaData rsmd = rs.getMetaData();
				Vector<String> columnNames = new Vector<>();
				Vector<Vector<String>> data = new Vector<>();
				// 把ResultSet的所有列名添加到Vector里
				for (var i = 0; i < rsmd.getColumnCount(); i++)
				{
					columnNames.add(rsmd.getColumnName(i + 1));
				}
				// 把ResultSet的所有记录添加到Vector里
				while (rs.next())
				{
					Vector<String> v = new Vector<>();
					for (var i = 0; i < rsmd.getColumnCount(); i++)
					{
						v.add(rs.getString(i + 1));
					}
					data.add(v);
				}
				// 创建新的JTable
				var table = new JTable(data, columnNames);
				scrollPane = new JScrollPane(table);
				// 添加新的Table
				jf.add(scrollPane);
				// 更新主窗口
				jf.validate();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args)
	{
		new QueryExecutor().init();
	}
}

一个简单的查询执行器,使用ResultSetMetaData查询ResultSet包含多少列,并把所有数据列的列名添加到一个Vector里,然后把ResultSet里的所有数据添加到Vector里,并使用这两个Vector来创建新的TableModel,再利用该TableModel生成一个新的JTable,最后显示出来


ResultSetMetaData可以准确的分析ResultSet,但是用它需要一定的系统开销,因此如果已知ResultSet的相关信息,就没必要使用ResultSetMetaData来分析该ResultSet对象

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Davieyang.D.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值