Java数据库编程【处理大对象LOB】【DAO模式】【九】
15.8 数据库处理大对象LOB
现在数据库基本都可以处理大对象(Large Objects, LOB)。
大对象是指那些尺寸超过常规数据类型限制的数据,如图像、音频、视频、长文本等二进制数据。数据库系统通常提供两种主要的大对象类型:
- BLOB (Binary Large Object):用于存储二进制的大对象数据。例如,图像、音频、视频、PDF文件等信息。
- CLOB (Character Large Object):用于存储字符型的大对象数据。例如,长文档、XML文档、JSON数据等。
Derby提供了对大对象(LOB)的支持。Derby实现了标准的SQL大对象数据类型,包括BLOB和CLOB。Derby数据库的BLOB和CLOB最大大小都是2GB。
Derby的ResultSet使用方法getBlob和getClob可分别读取BLOB和CLOB。存取大对象数据还需要用到SQL上定义的BLOB和CLOB数据类型的变量。
1,Blob createBlob()方法可创建一个空的BLOB类型的变量。
2,Clob createClob()方法可创建一个空的Clob类型的变量。
可用如下两种方式,把二进制大对象Blob imgBlob转换成图像、图标:
Image image = ImageIO.read(imgBlob.getBinaryStream()); //Bolb字段转成图像
ImageIcon icon = new ImageIcon(imgBlob.getBytes(1L,(int)imgBlob.length()));
同样地,从Clob可调用getSubString或getCharacterStream方法来获取文本。
【例程15-13】数据库处理大对象例程ImageFrame
下面的程序演示了在数据库字段中存储二进制大数据,演示从图像文件读取数据保存到数据库中,然后再从数据库读取数据另存到文件。
该程序正常执行有二个前提条件:
1,需要导入上面已学过的类bank.DBConnectManager.java
2,在D:\DB\image目录有二个图像文件,cat.gif和flower.jpg
程序运行后,将在D:\DB目录生成数据库存储文件夹ImageDB,里面是数据库文件。另外会在D:\DB\image目录,新产生两个图像文件:cat2.gif和flower2.jpg
package blob; //程序DbBlobOperate.java开始
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import bank.DBConnectManager; //需要导入上一章节的类
public class DbBlobOperate {
private Connection con = null;
private PreparedStatement pstmt = null ;
private ResultSet rs = null;
//绝对路径的数据库名,将在指定的路径创建数据库。如用相对路径则数据库创建在工作目录下
private static final String dbName = "D:\\DB\\ImageDB";
private static final String createImageTableSQL =
"CREATE TABLE blob.image (" +
"ImageId INT NOT NULL " +
" GENERATED BY DEFAULT AS IDENTITY (START WITH 100,INCREMENT BY 1)," +
"ImageName VARCHAR(10)," +
"ImageData BLOB )" ;
private static final String getImageSQL =
"SELECT ImageId, ImageName, ImageData " +
"FROM blob.image WHERE ImageName = ?" ;
private static final String insertImageSQL =
"INSERT INTO blob.image(ImageName, ImageData) " +
"VALUES(?, ?)" ;
public boolean WritBlobToDB( String rdFileName, String imageName )
{ //从磁盘文件读入二进制图像数据,写入数据库表中
boolean rtn = false;
try(FileInputStream fileInputStream = new FileInputStream(rdFileName))//打开文件
{
Connection con=DBConnectManager.GetConnection(dbName); //获取数据库连接
PreparedStatement pstmt = con.prepareStatement(insertImageSQL) ;
System.out.println("图片文件SIZE: " + fileInputStream.available());
byte[] bytes_image = new byte[fileInputStream.available()];
int i = fileInputStream.read(bytes_image);
/*** Blob数据准备 Begin:定义一个Blob数据 ***/
Blob image_data=con.createBlob();
//将图片文件信息写入Blob中
i = image_data.setBytes(1L, bytes_image); //起始位置是1L
System.out.println("写入Blob字节数: " + i);
/*********** Blob数据准备 End *********/
pstmt.clearParameters() ;
pstmt.setString(1, imageName) ; //pstmt.setString(2, "鲜花") ;
pstmt.setBlob(2, image_data);
pstmt.execute(); //执行数据库更新
pstmt.close();
con.close();
con = null;
System.out.println("Blob数据插入数据库成功!!!");
} catch (Exception e) {
// TODO: handle exception
}
return rtn;
}
public void ReadBlobFromDB( String wrFileName, String imageName )
{ //从表中读图像数据,写入文件中
InputStream inStream=null;
int ImageId = 0;
String imgName = null;
try( FileOutputStream fileOutputStream = new FileOutputStream(wrFileName) )
{
con = DBConnectManager.GetConnection(dbName); //获取数据库连接
pstmt = con.prepareStatement(getImageSQL) ;
pstmt.clearParameters() ;
pstmt.setString(1, imageName) ;
rs = pstmt.executeQuery(); //执行数据库查询
if (rs.next()) {
ImageId = rs.getInt("ImageId");
imgName = rs.getString("ImageName") ;
Blob imgData = rs.getBlob("ImageData");
System.out.println("Blob读:ImageID = "+ImageId +";ImageName = "+ imgName);
System.out.println("Blob读:ImageID.length() = " + imgData.length() );
// 建立输入流,从Blob获得二进制数据
inStream = imgData.getBinaryStream();//从Blob imgData获得输入流
long len = imgData.length();
System.out.println("Blob数据inStream.available() = " + len );
byte[] ImageByte = new byte[(int)len];
inStream.read(ImageByte); // 从输入流中读取数据到字节数组ImageByte
fileOutputStream.write(ImageByte); //写输出文件
inStream.close(); //关闭输入流
}
rs.close() ;
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static void main(String[] args) {
DbBlobOperate dbOpr = new DbBlobOperate();
String[] name = {"cat","flower"};
String[] rFilename = {"D:\\DB\\image\\cat.gif","D:\\DB\\image\\flower.jpg"};
String[] wFilename = {"D:\\DB\\image\\cat2.gif","D:\\DB\\image\\flower2.jpg"};
if (DBConnectManager.connectDb(dbName)==false) //连接不存在,新建数据库
DBConnectManager.CreateDbTable(dbName, createImageTableSQL);
for (int i = 0; i < name.length; i++) {
dbOpr.WritBlobToDB(rFilename[i], name[i]);
}
for (int i = 0; i < name.length; i++) {
dbOpr.ReadBlobFromDB(wFilename[i], name[i]);
}
}
} //程序DbBlobOperate.java结束
Blob大对象演示主控程序:ImageFrame.java从数据库读图像数据并显示到按钮。
测试环境准备:先执行DbBlobOperate.java程序生成ImageDB数据库。
package blob; //程序ImageFrame.java开始
import java.awt.GridLayout;
import java.awt.Image;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import bank.DBConnectManager;
public class ImageFrame extends JFrame {
private JButton[][] buttons = new JButton[2][3];
private static final String dbName = "D:\\DB\\ImageDB";
private static final String getImageSQL = //查询所有记录
"SELECT ImageId, ImageName, ImageData FROM blob.image";
public ImageFrame() {
setSize(640,480);
GridLayout gLayout = new GridLayout(2, 3);
setLayout(gLayout); //设置为2*3网格布局
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++)
add(buttons[i][j] = new JButton(""));
rdBlobFrmDB( ); //从数据库读入图像
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public void rdBlobFrmDB( ) //从表中读图像数据
{
int imageId = 0;
String imgName = null;
try ( Connection con = DBConnectManager.GetConnection(dbName); //数据库连接
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(getImageSQL); /*执行数据库查询*/ )
{
for (int i=0; rs.next()&&i<2; i++) { //只显示前二个图像
imageId = rs.getInt("ImageId");
imgName = rs.getString("ImageName") ;
Blob imgBlob = rs.getBlob("ImageData");
//Blob生成图标图像。可以直接从Blob得到字节序列
ImageIcon icon = new ImageIcon(imgBlob.getBytes(1L,(int)imgBlob.length()));
buttons[i][0].setText(""+imageId);
buttons[i][1].setText(imgName);
buttons[i][2].setIcon(icon);
}
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static void main(String[] args) {
ImageFrame frame = new ImageFrame();
}
} //程序ImageFrame.java结束。
程序演示结果如下:
15.9 数据库的DAO模式
DAO(Data Access Object)模式是数据库编程中常用的一种架构模式。 它是一种设计模式,用于将底层数据访问操作与业务逻辑分离。其核心思想是将对数据库的操作封装在一个独立的层中,使得业务逻辑层不需要直接与数据库交互,而是通过 DAO 层访问数据。这种分离提高了代码的可维护性、可扩展性和可测试性。
虽然在现代的数据库应用开发中,DAO模式常与ORM框架(如Hibernate、MyBatis)结合使用,这些框架本身已经实现了许多DAO的功能,开发者可以更专注于业务逻辑。
如果对数据库DAO模式有深入了解,将会对现代的数据库应用开发有所帮助。因此,本文将介绍数据库纯DAO模式的用法。
- DAO 模式的核心组件
DAO 模式通常包括以下几个核心组件:
(1) 实体类(Entity Class),即业务对象(Business Object):使用DAO接口访问数据源(库)的客户端对象。
- 可映射到数据库表的数据,每个实例对应表中的一行数据。
- 通常包含属性(对应数据库表中的列)和 getter/setter 方法。
一个实体类的示例:
package database.DAO;
public class User {
private int id;
private String name;
private String email;
// Getter 和 Setter 方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
(2) DAO 接口(DAO Interface):定义了对数据源进行操作的标准方法
- 定义了对实体类进行 CRUD(创建、读取、更新、删除)操作的方法。
- 业务逻辑层通过DAO接口访问数据,而不是直接操作数据库。
示例:
package database.DAO;
import java.util.List;
public interface UserDAO {
void addUser(User user); // 插入用户
User getUserById(int id); // 根据 ID 查询用户
List<User> getAllUsers(); // 查询所有用户
void updateUser(User user); // 更新用户
void deleteUser(int id); // 删除用户
}
(3) DAO 实现类(DAO Implementation):具体实现接口中定义的方法,包含数据访问细节
- 实现 DAO 接口,封装对数据库的具体操作(如 JDBC 代码)。
- 负责与数据库交互,执行 SQL 语句。
package database.DAO;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDAOImpl implements UserDAO {
private Connection conn;
public UserDAOImpl(Connection conn) {
this.conn = conn;
}
@Override
public void addUser(User user) {
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public User getUserById(int id) {
String sql = "SELECT * FROM users WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
return user;
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public List<User> getAllUsers() {
List<User> users = new ArrayList<>();
String sql = "SELECT * FROM users";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
users.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return users;
}
@Override
public void updateUser(User user) {
String sql = "UPDATE users SET name = ?, email = ? WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, user.getId());
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void deleteUser(int id) {
String sql = "DELETE FROM users WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(4) 业务逻辑层(Service Layer)
- 调用 DAO 接口的方法,执行业务逻辑。
- 不直接与数据库交互,而是通过 DAO 层访问数据。
package database.DAO;
import java.util.List;
public class UserService {
private UserDAO userDAO;
public UserService(UserDAO userDAO) {
this.userDAO = userDAO;
}
public void addUser(String name, String email) {
User user = new User();
user.setName(name);
user.setEmail(email);
userDAO.addUser(user);
}
public User getUserById(int id) {
return userDAO.getUserById(id);
}
public List<User> getAllUsers() {
return userDAO.getAllUsers();
}
public void updateUser(int id, String name, String email) {
User user = new User();
user.setId(id);
user.setName(name);
user.setEmail(email);
userDAO.updateUser(user);
}
public void deleteUser(int id) {
userDAO.deleteUser(id);
}
}
- DAO 模式的优点
(1)分离关注点:
将数据访问逻辑与业务逻辑分离,使代码更清晰、更易维护。
(2)提高可扩展性:
如果需要更换数据库(如从 MySQL 切换到 Oracle),只需修改 DAO 实现类,而不需要修改业务逻辑层。
(3)提高可测试性:
可以通过 Mock 对象测试业务逻辑层,而无需依赖真实的数据库。
(4)代码复用:
将数据访问逻辑封装在 DAO 层,可以在多个业务逻辑中复用。
- DAO 模式的缺点
(1)增加代码量:
需要编写更多的类(如实体类、DAO 接口、DAO 实现类)。
(2)复杂性增加:
对于小型项目,使用 DAO 模式可能会显得过于复杂。
- DAO 模式的应用场景
- 中大型项目,需要清晰的代码结构和良好的可维护性。
- 需要支持多种数据库的项目。
- 需要单元测试和模块化设计的项目。
- 测试主例程示例
这是一个没有完善的例程。把完善这个程序作为一个综合练习题吧!请自己再设计一些需求,自行搭建Derby和MySQL两种数据库的网络服务器环境,自行进行测试。
如果读者能完美解决这个综合练习题,那祝贺你,你的数据库程序设计已经登堂入室了。
package database.DAO;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DAOMain {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/testdb";
String dbUser = "root";
String password = "password";
try (Connection conn = DriverManager.getConnection(url, dbUser, password)) {
UserDAO userDAO = new UserDAOImpl(conn);
UserService userService = new UserService(userDAO);
// 添加用户
userService.addUser("John Doe", "john.doe@example.com");
// 查询用户
User user = userService.getUserById(1);
System.out.println("User: " + user.getName() + ", Email: " + user.getEmail());
// 更新用户
userService.updateUser(1, "Jane Doe", "jane.doe@example.com");
// 删除用户
userService.deleteUser(1);
} catch (SQLException e) {
e.printStackTrace();
}
}
}