Java数据库编程之【JDBC数据库例程】【处理大对象LOB】【DAO模式】【九】

发布于:2025-08-14 ⋅ 阅读:(20) ⋅ 点赞:(0)

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模式的用法。

  1. 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);
    }
}
  1. DAO 模式的优点
    (1)分离关注点:
    将数据访问逻辑与业务逻辑分离,使代码更清晰、更易维护。

(2)提高可扩展性:
如果需要更换数据库(如从 MySQL 切换到 Oracle),只需修改 DAO 实现类,而不需要修改业务逻辑层。

(3)提高可测试性:
可以通过 Mock 对象测试业务逻辑层,而无需依赖真实的数据库。

(4)代码复用:
将数据访问逻辑封装在 DAO 层,可以在多个业务逻辑中复用。

  1. DAO 模式的缺点
    (1)增加代码量:
    需要编写更多的类(如实体类、DAO 接口、DAO 实现类)。

(2)复杂性增加:
对于小型项目,使用 DAO 模式可能会显得过于复杂。

  1. DAO 模式的应用场景
  • 中大型项目,需要清晰的代码结构和良好的可维护性。
  • 需要支持多种数据库的项目。
  • 需要单元测试和模块化设计的项目。
  1. 测试主例程示例

这是一个没有完善的例程。把完善这个程序作为一个综合练习题吧!请自己再设计一些需求,自行搭建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();
        }
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到