传智播客 JDBC基础之工厂模式的应用与事务分析

来源:互联网 发布:excel数据图表制作 编辑:程序博客网 时间:2024/05/09 20:21

 

    前面讲解了UserDao接口的JDBC实现,列举了其中一个方法的实现。其它方法与addUser方法代码模式上基本相差不大,就sql语句和设置参数时有少许改变。完全实现UserDao接口后,就可以模拟一下业务层调用数据访问层的过程。模拟过程中,用的父类(UserDao)的引用指向子类(UserDao的实现类)的对象。虽然可以替换掉实现类,但是还是需要在代码中修改,然后重新编译。运用工厂模式,可以把实现类写在配置文件中,这样就不用修改代码,直接修改配置文件就能起到换数据访问层的作用。
    先编写一个key和value值对的属性文件,关键字是userDaoClass,值是类的名字,如下:
      userDaoClass=cn.itcast.jdbc.dao.impl.UserDaoJdbcImpl
    然后通过Properties类来读取配置文件中的键值对,从而构造出相应的UserDao的实现类,即数据访问层。
      public class DaoFactory {
          private static UserDao userDao = null;
          private static DaoFactory instance = new DaoFactory();   
          private DaoFactory() {
              try {
                  Properties prop = new Properties();
                  InputStream inStream = DaoFactory.class.getClassLoader()
                          .getResourceAsStream("daoconfig.properties");
                  prop.load(inStream);
                  String userDaoClass = prop.getProperty("userDaoClass");
                  Class clazz = Class.forName(userDaoClass);
                  userDao = (UserDao) clazz.newInstance();
              } catch (Throwable e) {
                  throw new ExceptionInInitializerError(e);
              }
          }    
          public static DaoFactory getInstance()  {
              return instance;
          }    
          public UserDao getUserDao() {
              return userDao;
          }
      }
    例子中采用单例模式,在构造函数中初始化UserDao。在载入配置文件的过程中,有两种方式可供选择,一是通过FileInputStream文件输入流,利用相对路径或者绝对路径来载入文件。二是通过载入当前类的类加载器来载入。这种方式的好处是,将属性文件放在源文件夹下,部署的时候该文件也会被复制到相应的class目录下,被相应的类加载器加载。载入文件后,通过Properties的load方法使该Properties对象完全初始化,这样就能继续通过getProperty方法获取相应关键字对应的值。最后通过反射构造出UserDao的实例对象。在上面的代码中,如果将最开始的两个静态成员变量交换位置,则将会出现初始化错误。因为先执行new DaoFactory()时,UserDao初始化为正常的值,然后再次执行UserDao=null,刚刚获取的实例对象就又被覆盖成null了,这将使getInstance和getUserDao返回的都是null值。因此,有时候变量的顺序(初始化顺序)也能导致异常。这样的错误往往很难查找。
    学习了上面比较实用的JDBC的例子,下面来学习JDBC的另外一个比较重要的方面——事务。事务在企业级应用中经常遇到,比如在银行的两个账户的转账过程,就必须保证一个账户加钱,一个账户减钱,不能存在只对一个账户进行操作,而另外一个却没有做。事务有以下四个特性:
      1.原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。
      2.一致性(consistency):在事务处理执行前后,数据库是一致的(数据库数据完整性约束)。
      3.隔离性(isolcation):一个事务处理对另一个事务处理的影响。
      4.持续性(durability):事务处理的效果能够被永久保存下来。
    了解一些概念后,用一个演示程序模拟了事务。用到的还是user这张表,要求在张三(id=1)的money减10块,李四(id=2)的money加10块,但是如果李四的money大于400后,则抛出异常,程序要求张三、李四的money同时操作。所以,当李四的money小于400时,程序执行结果是正常的,一个减一个加,但是当李四的money大于400时,张三的money减了,但是李四的却没有加。所以要保证这些操作的原子性,加入事务控制语句就能解决问题,下面是示例代码:
      static void test() throws SQLException {
          Connection conn = null;
          Statement st = null;
          ResultSet rs = null;
          try {
              conn = JdbcUtils.getConnection();
              conn.setAutoCommit(false);
              st = conn.createStatement();
              String sql = "update user set money=money-10 where id=1";
              st.executeUpdate(sql);
              sql = "select money from user where id=2";
              rs = st.executeQuery(sql);
              float money = 0.0f;
              if (rs.next()) {
                  money = rs.getFloat("money");
              }
              if (money > 400)
                  throw new RuntimeException("已经超过最大值!");
              sql = "update user set money=money+10 where id=2";
              st.executeUpdate(sql);
              conn.commit();
          } catch (SQLException e) {
              if (conn != null)
                  conn.rollback();
              throw e;
          } finally {
              JdbcUtils.free(rs, st, conn);
          }
      }
    开启事务的语句为conn.setAutoCommit(false),即把事务自动提交设置成false。当一切正常时,在try的末尾有commit语句,数据库修改生效。当try语句块中抛出异常时,在catch语句块中通过rollback语句回滚事务到开启事务的地方。因此,从事务开启到commit语句之间的SQL操作保证了原子性。上面的例子是完全回滚,如果当只想撤销事务中的部分操作时可使用SavePoint。使用下面语句可以在当前语句处建立一个存储点:
      SavePoint sp = connection.setSavepoint();
    当需要将数据库回滚到某个存储点并保存回滚点之前的所有SQL操作,则使用下面两个语句:
      connection.rollerbak(sp);
      connection.commit();
    最后提及的是JTA,当需要跨越多个数据源的事务时,就应该使用JTA容器实现事务。这个虽然复杂,但是企业级应用也经常用到。比如EJB、分布式事务处理等等。

原创粉丝点击