Handling Images and other File Data

For years there has been a debate about the relative merits of supporting persistent storage of images in database management systems versus file systems.

The database people think that all information should be stored and accessed using a DBMS.  The File system people think that the DBMS people are wrong -- or worse -- and that DBMS should be used for special purposes and that bulk data should be stored in a file system.

The DBMS people were clearly wrong for the first twenty or thirty years.  But as systems become larger and more powerful and as DBMS systems become more efficient and robust, the right position is not so clear.

From my perspective, particularly with respect to Web-based systems, I believe it still makes sense to store images, per se,  in the file system and store information about the images in the dbms.  The discussion that follows is based on that perspective.


Architecture

Uploading media and other file data is most likely to be done within the context of HTTP and HTML -- that is from an HTML form whose data is handled is handled in a Servlet, in a Struts Action, or in some other framework's controller.  The discussion below will assume the following architecture:

 


Steps

Designing and implementing the various components required or useful for uploading, storing, and accessing images and other file data is a fairly complex tasks with a numnber of different steps.  In this section, I will  list some of the basic steps and provide sample code to illustrate key technical features.  I will do so within the context of a system that is using Struts and a MySQL database, developed within an Eclipse IDE.

1. Define the HTML form element for uploading a file. The code shown is for a STRUTS JSP, using the html taglib:

	<html:file property="imageFile" size="25" />
		

2.  Create a Struts FormBean (ActionForm) property for the file:

	private FormFile imageFile = null;
		

3.  Create a DBMS table that includes an Integer key, most likely one auto-generated with consecutive values (e.g., 1, 2, 3, . . . ).

4.  Add an item to the DBMS for an entity that includes information about the image, such as its mime type.  The key, if auto-generated should be returned to the controller to be used for generating the path and file name for the image.  Note that the executeUpdate method must include a parameter that asks the DBMS to make the generated key available.

	int result = statement.executeUpdate( query, Statement.RETURN_GENERATED_KEYS );
	.
	.
	.
	ResultSet resultSet  = statement.getGeneratedKeys();
	if (resultSet.next()) {
	  productId = resultSet.getInt(1);
	} else {
	  throw new SQLException();
	}
	dataBean.setProductId(productId);
		

5.  After storing the information about the image in the DBMS, store the image, itself, in the file system (this will take several steps):

	//  write image to file
	long imageFileLength = 0;
	String fileName = formFile.getFileName();
	if ( (fileName != null) && ( ! fileName.equals("" )) )  {
	  imageFileLength = writeImageToFile( formFile, dataBeanNew, Constant.IMAGELARGE );				
	}
		

6.  Build the path and file name. In this example, strategy is to build the path for the image file in three parts:  a prefix which is the file system location of the application context, the hierarchical directory structure for the image, and the suffix which is the mime type for the image.  Whithin the application context, the root of the image directory hierarchy is a directory called image, specified by Constant.IMAGEFILEPREFIX.

	private long writeImageToFile(FormFile formFile, BeanProduct dataBean, int imageSize) {
	  try {

	    //  build path
	    Integer imageId = dataBean.getProductId();  // productId used as id for image

	    String prefix = "";
	    if ( imageSize == Constant.IMAGELARGE )
	      prefix = (((this.getServlet()).getServletContext()).getRealPath(Constant.IMAGEFILEPREFIX)) + "/";
	    else 
	      prefix = (((this.getServlet()).getServletContext()).getRealPath(Constant.IMAGEFILESMALLPREFIX)) + "/";
			
	    String suffix = "." + dataBean.getImageType();	
	
	    String path = Util.getImagePath(prefix, imageId, suffix);  //  see 7, below

	    //  get InputStream
	    InputStream inputStream = formFile.getInputStream();
	    // write file
	    return Util.fileWrite( inputStream, path );  //  see 8, below
		
	  } catch (FileNotFoundException e) {
	    e.printStackTrace();
	    return 0;
	  } catch (IOException e) {
	    e.printStackTrace();
	    return 0;
	  }
	}
		

7.  Build the core of the path -- the directory hierarchy and file name -- from the generated DBMS id.  Constant.IMAGEFILEMODULUS is the maximum number of images to be placed in a directory.  The full path to the image file is composed from this core path, along with the prefix and suffix, in the Util.getImagePath method, shown in 6, above.

	String path;
	int tempInt = imageId;
        String[] names = new String[4];
        
        for (int i=0;i<4;i++)  {
            names[i] = Integer.toString(tempInt % Constant.IMAGEFILEMODULUS);
            tempInt = tempInt / Constant.IMAGEFILEMODULUS;
        }
       
        path  =  names[3] + "/" + names[2] + "/" + names[1] + "/" + names[0];

		

8.  Actually write the image to the file:

	public static long fileWrite(InputStream inputStream, String filePathAndName) {
		
	  File outputFile;
		
	  try {
	    int lastSlash = filePathAndName.lastIndexOf("/");
	    String filePath = filePathAndName.substring(0, lastSlash);
	    File directories = new File( filePath );
	    directories.mkdirs();
        
	   try {
				
	      outputFile = new File ( filePathAndName );
	      FileOutputStream outputStream = new FileOutputStream( outputFile );
	      int bytesRead = 0;
	      byte[] buffer = new byte[8192];
				
              while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
              }
              outputStream.close();
              inputStream.close();
            		
	    } catch (FileNotFoundException e) {
	      System.out.println("fileWrite failed: FileNotFoundException");
	      return -1;      
	    } catch (IOException e) {
	      System.out.println("fileWrite failed: IOException");
	      return -1;        
	    }
        
	    long size = outputFile.length();
	    return size;
        
	  }  catch ( Exception e )  {	    
	     System.out.println("fileWrite failed: Exception");
	     return -1;        
	  }
    
	}