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

被折叠的 条评论
为什么被折叠?



