上一篇说到大的文本对象,现实世界里面的数据大多是二进制的对象,因此要在数据库中保存这些数据就需要有相应的数据类型。其实二进制对象和文本对象类似,对于JDBC来说,插入和读取这些对象是很容易的。只是演示代码中的IO操作比较复杂。数据库中二进制对象用BLOB表示,每种数据库都有这种数据类型,只是表示的名称不同而已。在mysql中该类型用的名称就是BLOB。下面的代码说明了二进制对象的存储于文本对象是一样的:
 

static void create() throws SQLException, IOException { 
 
          Connection conn = null; 
 
          PreparedStatement ps = null; 
 
          ResultSet rs = null; 
 
          try { 
 
              conn = JdbcUtils.getConnection(); 
 
              String sql = "insert into blob_test(big_bit) values (?) "; 
 
              ps = conn.prepareStatement(sql); 
 
              File file = new File("img.jpg"); 
 
              InputStream in = new BufferedInputStream(new FileInputStream(file)); 
 
              ps.setBinaryStream(1, in, (int) file.length()); 
 
              int i = ps.executeUpdate(); 
 
              in.close(); 
 
  
 
              System.out.println("i=" + i); 
 
          } finally { 
 
              JdbcUtils.free(rs, ps, conn); 
 
          } 
 
      }


    除了改变了读入的文件输入流和PreparedStatement调用的方法,其它几乎没有改变。因为读入的文件时二进制的文件,所以就不能用字符流了,改用相应的字节流就可以了。PreparedStatement调用的是setBinaryStream,参数与前面的方法也一致,只需将其中的字符流改为字节流。同样的,读出操作也做相应更改就可以了:
 

rs = st.executeQuery("select big_bit  from blob_test"); 
 
              while (rs.next()) { 
 
                  Blob blob = rs.getBlob(1); 
 
                  InputStream in = blob.getBinaryStream(); 
 
                  File file = new File("IMG_0002_bak.jpg"); 
 
                  OutputStream out = new BufferedOutputStream( 
 
                          new FileOutputStream(file)); 
 
                  byte[] buff = new byte[1024]; 
 
                  for (int i = 0; (i = in.read(buff)) > 0;) { 
 
                      out.write(buff, 0, i); 
 
                  } 
 
                  out.close(); 
 
                  in.close();


    需要改动的代码如上所示,和文本对象一样,首先从结果集获取对应的Blob对象,然后调用Blob对象的getBinaryStream来获取相应的字节流,最后建立IO输出流,将获取的字节流通过输出流输出到文件上。到这里为止,JDBC中的数据类型应该就有了大概了解。在前面的例子中,结果处理都是直接打印显示一下查到的信息,正常情况下,一般不会这样做。在真正的J2EE设计中,往往采用三层架构来组织程序。分别是表示层 、业务逻辑层、数据访问层,三层之间用接口隔离,用Domain或者DTO来传递对象数据。而数据访问层是属于最底层的,JDBC只是数据访问层的一种最基本的实现方式。数据访问层是为业务逻辑层服务的。但是业务逻辑层操作的都是对象,而数据访问层操作的基本上时关系型数据库。数据访问层不能直接把关系数据库中的表、列、字段等内容直接返回给业务逻辑层。所以就需要进行类型转换。将关系型数据转换成对象返回给业务逻辑层。下面是演示用接口分离,用Domain对象来传递数据:

public class User { 
 
          private int id; 
 
          private String name; 
 
          private Date birthday; 
 
          private float money; 
 
          ... 
 
      }


    首先建立Domain对象,有四个属性与数据库中的user表对应,其中的省略号表示接下来是四个字段的getter和setter方法。有了Domain对象,就有了数据库和业务逻辑层的数据传递对象。接下来就是创建DAO接口,提供给业务逻辑层使用:

public interface UserDao { 
 
          public void addUser(User user); 
 
  
 
          public User getUser(int userId); 
 
  
 
          public User findUser(String loginName, String password); 
 
  
 
          public void update(User user); 
 
  
 
          public void delete(User user); 
 
  
 
      }


    有了DAO这个接口以后,就可以供业务逻辑层调用了。这样设计以后,DAO的具体实现可以随时改变而不用修改业务层代码。也就是说,业务层根本不知道数据访问层是怎么实现的。接下来是UserDao的JDBC实现方式,下面只是实现类一部分(包含一个实现方法):

public class UserDaoJdbcImpl implements UserDao { 
 
          public void addUser(User user) { 
 
              Connection conn = null; 
 
              PreparedStatement ps = null; 
 
              ResultSet rs = null; 
 
              try { 
 
                  conn = JdbcUtils.getConnection(); 
 
                  String sql = "insert into user(name,birthday, money) values (?,?,?) "; 
 
                  ps = conn.prepareStatement(sql); 
 
                  ps.setString(1, user.getName()); 
 
                  ps.setDate(2, new java.sql.Date(user.getBirthday().getTime())); 
 
                  ps.setFloat(3, user.getMoney()); 
 
                  ps.executeUpdate(); 
 
              } catch (SQLException e) { 
 
                  e.printStackTrace(); 
 
              } finally { 
 
                  JdbcUtils.free(rs, ps, conn); 
 
              } 
 
          } 
 
        ... 
 
    }


    在addUser方法中,基本全是模板代码,唯一区别是PreparedStatement的参数设置是从传入的User对象中取得的,而不是直接通过多个参数传入。上面的代码看上去很规范,但是在异常处理的地方是不正确的。因为,如果业务层代码调用addUser方法,在try语句块中出现异常,则程序直接打印了堆栈信息,然后业务层代码继续向下执行。这样就会导致业务层忽视了底层代码出现的异常,本来出错了,还在继续往下执行。但是又不能直接抛出异常,因为如果抛出SQLException,则将会导致UserDao接口发生改变,里面的addUser方法也要抛出SQLException异常。这样做的话,虽然用JDBC实现的数据访问层能够正常运行了,但是数据访问层也因此不能被替换成其它的实现了。因为其它的实现不一定会抛SQLException异常。所以这就破坏了三层结构的初衷,因此不能直接抛出SQLException异常。解决方法是自定义一个运行时异常,在catch语句块中抛出该异常,然后在业务逻辑代码中捕获该异常并进行处理。这样就保证了错误不会被隐藏,同时数据访问层的实现也能被随时替换掉。