目录:
一、可滚动、可更新的结果集
二、处理Blob类型数据
三、使用ResultSetMetaData分析结果集
一、可滚动、可更新的结果集
可以使用next()、absolute()、afterLast()、previous()等方法自由移动记录指针的ResultSet被称为可滚动的结果集。
以默认方式打开的ResultSet是不可更新的,如果希望创建可更新的ResultSet,则必须在创建Statement或prepareStatement时传入额外参数。Connection在创建Statement或PrepareStatement时还可额外传入如下两个参数:
1、resultSetType:控制ResultSet的类型,该参数可以取如下三个值
(1)ResultSet.TYPE_FORWARD_ONLY:该常量控制记录指针只能向前移动。这是JDK1.4以前的默认值。
(2)ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制指针可以自由移动(可滚动结果集),但底层数据的改变不会影响ResultSet的内容。
(3)ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自由记录(可滚动的结果集),而且底层数据的改变会影响ResultSet的内容。
2、resultSetConcurrency:控制ResultSet的并发类型,该参数可以接受如下两个值:
(1)ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet是只读的并发模式(默认)。
(2)ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet是可更新的并发模式。
下面创建一个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集。
1 //使用Connection创建一个PreparedStatement对象 2 //传入控制结果集可滚动、可更新的参数: 3 pstmt=conn.preparedStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE);
可更新的结果集还需要满足如下两个条件:
★所有数据都应该来自一个表
★选出的数据集必须包含主键列
在创建了可滚动、可更新的ResultSet时,程序可调用ResultSet的updateXxx(int columnIndex,Xxx value)方法来修改执政所指记录、特定列的值,最后调用ResultSet的updateRow()方法来提交修改。
Java 8为ResultSet添加了updateObject(String columnLable,Object x,SQLType targetSqlType)和updateObject(int columnIndex,Object x,SQLType targetSqlObject)两个默认方法,这两个方法可以直接使用Object来修改记录指针所指记录、特定列的值,其中SQLType用于指定该数据列的类型。
下面示范创建这种可滚动、可更新的结果集的方法:
1 package section5; 2 3 import java.io.FileInputStream; 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.PreparedStatement; 7 import java.sql.ResultSet; 8 import java.util.Properties; 9 10 public class ResultSetTest 11 { 12 private String driver; 13 private String url; 14 private String user; 15 private String pass; 16 public void initParam(String paramFile) 17 throws Exception 18 { 19 //使用Properties类加载属性 20 var props=new Properties(); 21 props.load(new FileInputStream(paramFile)); 22 driver=props.getProperty("driver"); 23 url=props.getProperty("url"); 24 user=props.getProperty("user"); 25 pass=props.getProperty("pass"); 26 } 27 public void query(String sql) 28 throws Exception 29 { 30 //加载驱动 31 Class.forName(driver); 32 try( 33 //创建连接 34 Connection conn= DriverManager.getConnection(url,user,pass); 35 //创建结果集可滚动、可更新的参数 36 PreparedStatement pstmt=conn.prepareStatement(sql, 37 ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_UPDATABLE); 38 ResultSet rs=pstmt.executeQuery() 39 ) 40 { 41 rs.last();//指针记录定位到最后一行 42 int rowCount=rs.getRow();//查询由多少条记录 43 for(var i = rowCount;i>0;i--) 44 { 45 rs.absolute(i);//将记录指针移动到第i行 46 System.out.println(rs.getString(1)+" " 47 +rs.getString(2)+" "+rs.getString(3)); 48 //修改记录指针所指记录第二列的值 49 rs.updateString(2,"学生名"+i); 50 //提交修改 51 rs.updateRow(); 52 } 53 } 54 } 55 public static void main(String[] args) 56 throws Exception 57 { 58 var rt=new ResultSetTest(); 59 rt.initParam("src\mysql.ini"); 60 rt.query("select *from student_table;"); 61 } 62 }
执行结果后查询数据表:
二、处理Blob类型数据
Blob(Binary Long Object)指的是二进制长对象,Blob列通常用于存储大文件,典型的Blob内容就是:图片、声音文件。使用Blob列可以把图片、声音等文件的二进制数据保存在数据库里,并可以从数据库里恢复指定文件。
回顾表的插入:
1 insert into teacher_table(teacher_id,teacher_name) 2 values(1,'Mike');
试想如果有一列是Blob列,我们无法直接通过上面的SQL语句完成,因为Blob常量无法表示。
2.1 插入Blob数据
将Blob数据插入数据库需要使用PreparedStatement,该对象有一个方法:
1 setBinaryStream(int paramterIndex,inputStream x); 2 //该方法可以为指定参数传入二进制输入流
2.2 取出Blob数据
当需要从ResultSet中取出Blob数据时,可以调用ResultSet的getBlob(int columnIndex)方法:
1 getBlob(int columnIndex); 2 //该方法返回一个Blob对象
在获取Blob对象后,Blob对象提供getBinaryStream()方法来获取该Blob数据的输入流,也可以使用Blob对象提供的getBytes()方法直接取出该Blob对象封装的二进制数据。
程序示例:
为了把图片放入数据库,先使用SQL语句创建一个数据表:
1 mysql> use select_test; 2 Database changed 3 mysql> create table img_table 4 -> (img_id int auto_increment primary key, 5 -> img_name varchar(255), 6 -> img_data mediumblob); 7 Query OK, 0 rows affected (1.10 sec) 8 9 mysql> desc img_table; 10 +----------+--------------+------+-----+---------+----------------+ 11 | Field | Type | Null | Key | Default | Extra | 12 +----------+--------------+------+-----+---------+----------------+ 13 | img_id | int(11) | NO | PRI | NULL | auto_increment | 14 | img_name | varchar(255) | YES | | NULL | | 15 | img_data | mediumblob | YES | | NULL | | 16 +----------+--------------+------+-----+---------+----------------+ 17 3 rows in set (0.03 sec)
下面程序可以实现“上传”——实际上就是将图片保存到数据库,并在右边列表框中显式图片的名字,当用户双击列表框的图片名1时,左边窗口将显式该图片——实质上就是根据选中的ID从数据库里查找图片,并将其显示出来。
1 package section5; 2 import java.sql.*; 3 import javax.swing.*; 4 import java.awt.*; 5 import java.awt.event.*; 6 import java.util.Properties; 7 import java.util.ArrayList; 8 import java.io.*; 9 import javax.swing.filechooser.FileFilter; 10 11 public class BlobTest 12 { 13 JFrame jf = new JFrame("图片管理程序"); 14 private static Connection conn; 15 private static PreparedStatement insert; 16 private static PreparedStatement query; 17 private static PreparedStatement queryAll; 18 // 定义一个DefaultListModel对象 19 private DefaultListModel<ImageHolder> imageModel 20 = new DefaultListModel<>(); 21 private JList<ImageHolder> imageList = new JList<>(imageModel); 22 private JTextField filePath = new JTextField(26); 23 private JButton browserBn = new JButton("..."); 24 private JButton uploadBn = new JButton("上传"); 25 private JLabel imageLabel = new JLabel(); 26 // 以当前路径创建文件选择器 27 JFileChooser chooser = new JFileChooser("."); 28 // 创建文件过滤器 29 ExtensionFileFilter filter = new ExtensionFileFilter(); 30 static 31 { 32 try 33 { 34 var props = new Properties(); 35 props.load(new FileInputStream("src\mysql.ini")); 36 var driver = props.getProperty("driver"); 37 var url = props.getProperty("url"); 38 var user = props.getProperty("user"); 39 var pass = props.getProperty("pass"); 40 41 Class.forName(driver); 42 // 获取数据库连接 43 conn = DriverManager.getConnection(url, user, pass); 44 // 创建执行插入的PreparedStatement对象 45 // 该对象执行插入后可以返回自动生成的主键 46 insert = conn.prepareStatement("insert into img_table" 47 + " values(null, ?, ?)", Statement.RETURN_GENERATED_KEYS); 48 // 创建两个PreparedStatement对象,用于查询指定图片,查询所有图片 49 query = conn.prepareStatement("select img_data from img_table" 50 + " where img_id = ?"); 51 queryAll = conn.prepareStatement("select img_id, " 52 + "img_name from img_table"); 53 } 54 catch (Exception e) 55 { 56 e.printStackTrace(); 57 } 58 } 59 public void init() throws SQLException 60 { 61 // -------初始化文件选择器-------- 62 filter.addExtension("jpg"); 63 filter.addExtension("jpeg"); 64 filter.addExtension("gif"); 65 filter.addExtension("png"); 66 filter.setDescription("图片文件(*.jpg, *.jpeg, *.gif, *.png)"); 67 chooser.addChoosableFileFilter(filter); 68 // 禁止“文件类型”下拉列表中显示“所有文件”选项 69 chooser.setAcceptAllFileFilterUsed(false); 70 // ---------初始化程序界面--------- 71 fillListModel(); 72 filePath.setEditable(false); 73 // 只能单选 74 imageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 75 var jp = new JPanel(); 76 jp.add(filePath); 77 jp.add(browserBn); 78 browserBn.addActionListener(event -> { 79 // 显示文件对话框 80 int result = chooser.showDialog(jf, "浏览图片文件上传"); 81 // 如果用户选择了APPROVE(赞同)按钮,即打开,保存等效按钮 82 if (result == JFileChooser.APPROVE_OPTION) 83 { 84 filePath.setText(chooser.getSelectedFile().getPath()); 85 } 86 }); 87 jp.add(uploadBn); 88 uploadBn.addActionListener(avt -> { 89 // 如果上传文件的文本框有内容 90 if (filePath.getText().trim().length() > 0) 91 { 92 // 将指定文件保存到数据库 93 upload(filePath.getText()); 94 // 清空文本框内容 95 filePath.setText(""); 96 } 97 }); 98 var left = new JPanel(); 99 left.setLayout(new BorderLayout()); 100 left.add(new JScrollPane(imageLabel), BorderLayout.CENTER); 101 left.add(jp, BorderLayout.SOUTH); 102 jf.add(left); 103 imageList.setFixedCellWidth(160); 104 jf.add(new JScrollPane(imageList), BorderLayout.EAST); 105 imageList.addMouseListener(new MouseAdapter() 106 { 107 public void mouseClicked(MouseEvent e) 108 { 109 // 如果鼠标双击 110 if (e.getClickCount() >= 2) 111 { 112 // 取出选中的List项 113 ImageHolder cur = (ImageHolder) imageList. 114 getSelectedValue(); 115 try 116 { 117 // 显示选中项对应的Image 118 showImage(cur.getId()); 119 } 120 catch (SQLException sqle) 121 { 122 sqle.printStackTrace(); 123 } 124 } 125 } 126 }); 127 jf.setSize(620, 400); 128 jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 129 jf.setVisible(true); 130 } 131 // ----------查找img_table填充ListModel---------- 132 public void fillListModel() throws SQLException 133 { 134 try ( 135 // 执行查询 136 ResultSet rs = queryAll.executeQuery()) 137 { 138 // 先清除所有元素 139 imageModel.clear(); 140 // 把查询的全部记录添加到ListModel中 141 while (rs.next()) 142 { 143 imageModel.addElement(new ImageHolder(rs.getInt(1), 144 rs.getString(2))); 145 } 146 } 147 } 148 // ---------将指定图片放入数据库--------- 149 public void upload(String fileName) 150 { 151 // 截取文件名 152 String imageName = fileName.substring(fileName.lastIndexOf('\') 153 + 1, fileName.lastIndexOf('.')); 154 var f = new File(fileName); 155 try ( 156 var is = new FileInputStream(f)) 157 { 158 // 设置图片名参数 159 insert.setString(1, imageName); 160 // 设置二进制流参数 161 insert.setBinaryStream(2, is, (int) f.length()); 162 int affect = insert.executeUpdate(); 163 if (affect == 1) 164 { 165 // 重新更新ListModel,将会让JList显示最新的图片列表 166 fillListModel(); 167 } 168 } 169 catch (Exception e) 170 { 171 e.printStackTrace(); 172 } 173 } 174 // ---------根据图片ID来显示图片---------- 175 public void showImage(int id) throws SQLException 176 { 177 // 设置参数 178 query.setInt(1, id); 179 try ( 180 // 执行查询 181 ResultSet rs = query.executeQuery()) 182 { 183 if (rs.next()) 184 { 185 // 取出Blob列 186 Blob imgBlob = rs.getBlob(1); 187 // 取出Blob列里的数据 188 var icon = new ImageIcon(imgBlob.getBytes(1L, (int) imgBlob.length())); 189 imageLabel.setIcon(icon); 190 } 191 } 192 } 193 public static void main(String[] args) throws SQLException 194 { 195 new BlobTest().init(); 196 } 197 } 198 // 创建FileFilter的子类,用以实现文件过滤功能 199 class ExtensionFileFilter extends FileFilter 200 { 201 private String description = ""; 202 private ArrayList<String> extensions = new ArrayList<>(); 203 // 自定义方法,用于添加文件扩展名 204 public void addExtension(String extension) 205 { 206 if (!extension.startsWith(".")) 207 { 208 extension = "." + extension; 209 extensions.add(extension.toLowerCase()); 210 } 211 } 212 // 用于设置该文件过滤器的描述文本 213 public void setDescription(String aDescription) 214 { 215 description = aDescription; 216 } 217 // 继承FileFilter类必须实现的抽象方法,返回该文件过滤器的描述文本 218 public String getDescription() 219 { 220 return description; 221 } 222 // 继承FileFilter类必须实现的抽象方法,判断该文件过滤器是否接受该文件 223 public boolean accept(File f) 224 { 225 // 如果该文件是路径,接受该文件 226 if (f.isDirectory()) return true; 227 // 将文件名转为小写(全部转为小写后比较,用于忽略文件名大小写) 228 String name = f.getName().toLowerCase(); 229 // 遍历所有可接受的扩展名,如果扩展名相同,该文件就可接受 230 for (var extension : extensions) 231 { 232 if (name.endsWith(extension)) 233 { 234 return true; 235 } 236 } 237 return false; 238 } 239 } 240 // 创建一个ImageHolder类,用于封装图片名、图片ID 241 class ImageHolder 242 { 243 // 封装图片的ID 244 private int id; 245 // 封装图片的名字 246 private String name; 247 public ImageHolder(){} 248 public ImageHolder(int id, String name) 249 { 250 this.id = id; 251 this.name = name; 252 } 253 // id的setter和getter方法 254 public void setId(int id) 255 { 256 this.id = id; 257 } 258 public int getId() 259 { 260 return this.id; 261 } 262 // name的setter和getter方法 263 public void setName(String name) 264 { 265 this.name = name; 266 } 267 public String getName() 268 { 269 return this.name; 270 } 271 // 重写toString()方法,返回图片名 272 public String toString() 273 { 274 return name; 275 } 276 }
三、使用ResultSetMetaData分析结果集
提示:MetaData意思就是元数据,即描述其他数据的数据,因此ResultSetMetaData封装了描述ResultSet对象的数据。
ResultSet里包含一个getMetaData()方法,该方法返回改ResultSetMetaData对象。通过ResultSetMetaData对象提供的大量方法来返回ResultSet的描述信息:
(1)int getColumnCount():返回该ResultSet列数量。
(2)String getColumnName(int column):返回指定索引的列类型。
(3)int getColumnType(int column):返回指定索引的列类型。
ResultSet里有一个方法返回记录条数:
(1)getRow():返回数据表的记录指针前面的记录条数。先执行ResultSet的方法last()将记录指针移到最后。
1 package section5; 2 3 import java.io.FileInputStream; 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.ResultSet; 7 import java.sql.ResultSetMetaData; 8 import java.util.Properties; 9 10 public class MetaDataTest 11 { 12 private String driver; 13 private String url; 14 private String user; 15 private String pass; 16 17 public void initParam(String fileName) 18 throws Exception 19 { 20 //使用Properties类加载属性 21 Properties props=new Properties(); 22 props.load(new FileInputStream(fileName)); 23 driver=props.getProperty("driver"); 24 url=props.getProperty("url"); 25 user=props.getProperty("user"); 26 pass=props.getProperty("pass"); 27 } 28 public void queryMetaData(String sql) 29 throws Exception 30 { 31 //加载驱动 32 Class.forName(driver); 33 try( 34 //获取连接 35 Connection conn=DriverManager.getConnection(url,user,pass); 36 var cstmt=conn.createStatement(); 37 ResultSet rs=cstmt.executeQuery(sql) 38 ) 39 { 40 rs.last(); 41 ResultSetMetaData rsmd=rs.getMetaData(); 42 int columns=rsmd.getColumnCount(); 43 System.out.println("记录条数:"+rs.getRow()); 44 System.out.println("数据表的列数:"+columns); 45 for(int i=1;i<=columns;i++) 46 { 47 System.out.print(i+":"+rsmd.getColumnName(i)+" "); 48 } 49 System.out.println(); 50 //遍历数据表 51 rs.beforeFirst(); 52 while(rs.next()) 53 { 54 System.out.println(rs.getInt(1)+" "+rs.getString(2)); 55 } 56 } 57 } 58 public static void main(String[] args) 59 throws Exception 60 { 61 var mdt=new MetaDataTest(); 62 mdt.initParam("src\mysql.ini"); 63 mdt.queryMetaData("select * from teacher_table;"); 64 65 } 66 } 67 执行结果: 68 记录条数:3 69 数据表的列数:2 70 1:teacher_id 2:teacher_name 71 1 Yeeku 72 2 Leegang 73 3 Martine 74 75 Process finished with exit code 0