Torque中的一些问题

Torque中的一些问题

说明:这里描述的问题来自: Torque 3.1.1, Village2.0 dev,文中用到的代码来自开源Apache项目. 文中虽例子,但主要是辅助说明错误原因和解决办法.以给使用Turbine+Torque+Velocity的人一点帮助.

 

 

 

Torqueapache数据库项目中一个子项目,它实现数据库的持久层,JDOApache实现.自从和Turbine项目分出来后,它形成了两部分:

生成器和运行时库.生成器用来生成数据库资源和访问数据库的类,使用这些类和在运行时库的支撑下,应用程序对数据库的各种操作得以用java对象的普通用法和操作习惯实现.

Torque除了用到Apache其他项目如Commons的成果外,还用到了一个开源项目:Village(http://share.whichever.com/index.php?SCREEN=village),它位于JDBC API,提供不用写sql语句就可以实现数据库的crud操作.使用它和数据库里的表以及里面的记录打交道,就象是在一个小镇,一些小屋和小屋里的人们交谈,非常祥和的感觉.它的设计也是非常简洁,占用的空间也不大,真的就象一个游者,可以很轻松的驴行天下.

Torque的生成器使用了Velocity项目,该项目是java的模板语言,在生成数据库资源时,它出了全力,我们可以通过修改{Torque.dir}/templates中各子文件夹里的vm模板来适合数据库及你项目中的需要.例如,对于PostgreSQL8,Torque生成的库资源中创建数据库的sql代码中没有指定代码页,这样PostgreSQL将使用默认的SQL_ASCII设置,如果想让生成的资源指定代码页可以修改{Torque.dir}/templates/sql/db-init/postgresql中的createdb.vm,本例指定代码页为UNICODE,如下:

#foreach ($databaseName in $databaseNames)

drop database $databaseName;

create database $databaseName

ENCODING = 'UNICODE';#增加该行即可

#end

Torque做为项目的持久层,其开发过程是很简单的,需要提供的总文件数是4:

在设计阶段使用的build.properties,project-schema.xml,id-table-schema.xml以及在开发阶段的运行时用到的Torque.properties.在构建阶段为每个表产生五个类文件:

<table-name>.java,<table-name>Peer.java,

Base<table-name>.java,Base<table-name>Peer.java,这俩文件不要修改,因为用ant重新构建时,这些文件会重新生成.把文件名称以Peer结尾的称为Peer,实现对象关系影射(ORM),不以Peer结尾的称为数据对象,应用和数据对象交互.不以base开头的类扩展了以base开头的类.业务逻辑应该加在子类上.<table-name>MapBuilder.java,这是用于特定表<table-name>的影射文件.Peer类的crud操作就是建立在Village之上的.

 

 

 

现在开始一个例子,项目名称是cms,以这个例子说明一些问题.

这是一个cms项目的模式文件cms-schema.xml:

<?xml version="1.0" encoding="gb2312" standalone="no" ?>

<!DOCTYPE database SYSTEM

 "http://jakarta.apache.org/turbine/dtd/database.dtd">

<database name="news" package="org.news.om">

       <table name="atts" idMethod="none" description="保存了新闻的附件信息,它和新闻存在引用关系.">

              <column name="pagedate" javaName="pagedate" primaryKey="true" required="true" type="VARCHAR"

                     size="8" javaNamingMethod="underscore" description="页创建日期,缩略形式"></column>

              <column name="pagecode" javaName="pagecode" primaryKey="true" required="true" type="VARCHAR"

                     size="5" javaNamingMethod="underscore" description="页代码"></column>

              <column name="filename" javaName="filename" primaryKey="false" required="true" type="VARCHAR"

                     size="30" javaNamingMethod="underscore" description="文件名称"></column>

              <column name="oldfilename" javaName="oldfilename" primaryKey="false" required="false" type="VARCHAR"

                     size="30" javaNamingMethod="underscore" description="旧文件名称"></column>

              <column name="attname" javaName="attname" primaryKey="false" required="true" type="VARCHAR"

                     size="50" javaNamingMethod="underscore" description="附件名称,由用户指定的名称"></column>

              <column name="img" javaName="img" primaryKey="false" required="false" type="BLOB"

                     javaNamingMethod="underscore" description="附件文件,保存大文件"></column>

              <column name="attdes" javaName="attdes" primaryKey="false" required="false" type="VARCHAR"

                     size="50" javaNamingMethod="underscore" description="对附件的描述"></column>

       </table>

</database>

 

 

 

我们只关注生成java文件,使用该文件Torque生成了5个类,下面是影射类AttsMapBuilder:

其代码片段为:

public void doBuild() throws TorqueException

    {

        dbMap = Torque.getDatabaseMap("news");

        dbMap.addTable("atts");

        TableMap tMap = dbMap.getTable("atts");

        tMap.setPrimaryKeyMethod("none");

              tMap.addPrimaryKey("atts.PAGEDATE", new String());

                    tMap.addPrimaryKey("atts.PAGECODE", new String());

                    tMap.addColumn("atts.FILENAME", new String());

                    tMap.addColumn("atts.OLDFILENAME", new String());

                    tMap.addColumn("atts.ATTNAME", new String());

                    tMap.addColumn("atts.IMG", new Object());

                    tMap.addColumn("atts.ATTDES", new String());

   }

  

问题一:删除数据时报告错误:You must specify KeyDef attributes for this TableDataSet in order to create a Record for update.

原因:因为TorquePeer使用了Village,KeyDef是其中一个类,该类定义为:

public class KeyDef

{

    private Vector mySelf = null;

 

 

 

    public KeyDef()

    {

        mySelf = new Vector();

        mySelf.addElement ("");       

    }

 

 

 

    public KeyDef addAttrib(String name)

    {

        mySelf.addElement (name);

        return this;

    }

 

 

 

    public boolean containsAttrib (String name)

    {

        return (mySelf.indexOf ((Object) name) == -1) ? false : true;

    }

 

 

 

    public String getAttrib (int pos)

    {

        if (pos == 0)

            pos = 1;

 

 

 

        try

        {

            return (String) mySelf.elementAt (pos);

        }

        catch (ArrayIndexOutOfBoundsException e)

        {

            return null;

        }

    }

    public int size()

    {

        return mySelf.size() - 1;

    }

}

该类的作用很简单,就是保存一个列名称.提供给其他类DataSetRecord使用,通过读其源码,发现Village很依赖表的primary,如果一个表没有

primary,那么你不能更新和删除记录.

解决办法就是为表创建primary.

问题二:当删除多行时,遭到操作失败.

原因:Village实现的比较奇怪,就是插入更新删除记录时,若影响行数大于1时会生成一个异常,下面的代码片段位于Record类中:

    private int saveWithDelete (Connection connection)

            throws DataSetException, SQLException

    {

        PreparedStatement stmt = null;

        try

        {

            stmt = connection.prepareStatement (getSaveString());

            int ps = 1;

            for (int i = 1; i <= dataset().keydef().size(); i++)

            {

                Value val = getValue (dataset().keydef().getAttrib(i));

 

 

 

                val.setPreparedStatementValue (stmt, ps++);

            }

 

 

 

            int ret = stmt.executeUpdate();

            setSaveType (Enums.ZOMBIE);

 

 

 

            if (ret > 1)

                throw new SQLException ("There were " + ret + " rows deleted with this records key value.");

 

 

 

            return ret;

        }

        catch (SQLException e1)

        {

            throw e1;

        }

        finally { try

            {

                if (stmt != null)

                    stmt.close();

            }

            catch (SQLException e2)

            {

                throw e2;

            }

        }

  }

  而这个异常在org.apache.torque.util.BasePeer类中是这样处理的:

    public static void doDelete(Criteria criteria) throws TorqueException

    {

        Connection con = null;

        try

        {

            con = Transaction.beginOptional(

                    criteria.getDbName(),

                    criteria.isUseTransaction());

            doDelete(criteria, con);

            Transaction.commit(con);

        }

        catch (TorqueException e)

        {

            Transaction.safeRollback(con);

            throw e;

        }

    }

  事务回滚导致删除操作失败.实际上不仅是对删除操作这样,而且对于其它操作插入更新也是这样的.

 解决办法就是重写Peer子类:因为Peer类是BasePeer的子类,doDelete(Criteria criteria)是静态方法,不能覆盖,所以我们要重新命名

 上面方法为doDelete1(Criteria criteria),在问题三具体给出代码.

 

问题三:有时候表中不存在唯一标识一条记录的字段组合(除了整行记录),想以某几个字段删除行.比如本例,

允许指定两个字段PAGEDATE,PAGECODE的值删除记录.

有两种办法:

1 为了能使字段PAGEDATE,PAGECODE的值相等的多行存在,手工删除表中的primary,保持影射文件不变,增删改都可工作.

2 修改本例的BaseAttsPeer,这样做要注意备份这个文件.

增加两个函数:doDelete1(Criteria criteria, Connection con),doDelete1(Criteria criteria),这俩函数替代BasePeer中的

doDelete(Criteria criteria, Connection con),doDelete(Criteria criteria)两个函数的功能,再把BaseAttsPeer类中定义的

doDelete所有重载版本的调用改到对doDelete1的调用,就可以了.

doDelete1代码片段为:

public static void doDelete1(Criteria criteria) throws TorqueException

         {

             Connection con = null;

             try

             {

                 con = Transaction.beginOptional(

                         criteria.getDbName(),true);

                         //criteria.isUseTransaction());

                 doDelete(criteria, con);

                 Transaction.commit(con);

             }

             catch (TorqueException e)

             {

                 Transaction.commit(con);

                 //Transaction.safeRollback(con);

                 throw e;

             }

         }

         /**

          * Method to perform deletes based on values and keys in a Criteria.

          *

          * @param criteria The criteria to use.

          * @param con A Connection.

          * @throws TorqueException Any exceptions caught during processing will be

          *         rethrown wrapped into a TorqueException.

          */

         public static void doDelete1(Criteria criteria, Connection con)

             throws TorqueException

         {

             DB db = Torque.getDB(criteria.getDbName());

             DatabaseMap dbMap = Torque.getDatabaseMap(criteria.getDbName());

 

 

 

             // Set up a list of required tables and add extra entries to

             // criteria if directed to delete all related records.

             // StringStack.add() only adds element if it is unique.

             HashSet tables = new HashSet();

             Iterator it = criteria.keySet().iterator();

             while (it.hasNext())

             {

                 String key = (String) it.next();

                 Criteria.Criterion c = criteria.getCriterion(key);

                 List tableNames = c.getAllTables();

                 for (int i = 0; i < tableNames.size(); i++)

                 {

                     String name = (String) tableNames.get(i);

                     String tableName2 = criteria.getTableForAlias(name);

                     if (tableName2 != null)

                     {

                         tables.add(new StringBuffer(

                                 name.length() + tableName2.length() + 1)

                                 .append(tableName2)

                                 .append(' ')

                                 .append(name)

                                 .toString());

                     }

                     else

                     {

                         tables.add(name);

                     }

                 }

 

 

 

                 if (criteria.isCascade())

                 {

                     // This steps thru all the columns in the database.

                     TableMap[] tableMaps = dbMap.getTables();

                     for (int i = 0; i < tableMaps.length; i++)

                     {

                         ColumnMap[] columnMaps = tableMaps[i].getColumns();

                         for (int j = 0; j < columnMaps.length; j++)

                         {

                             // Only delete rows where the foreign key is

                             // also a primary key.  Other rows need

                             // updating, but that is not implemented.

                             if (columnMaps[j].isForeignKey()

                                 && columnMaps[j].isPrimaryKey()

                                 && key.equals(columnMaps[j].getRelatedName()))

                             {

                                 tables.add(tableMaps[i].getName());

                                 criteria.add(columnMaps[j].getFullyQualifiedName(),

                                     criteria.getValue(key));

                             }

                         }

                     }

                 }

             }

             Iterator tabIt = tables.iterator();

             while (tabIt.hasNext())

             {

                 String tab = (String) tabIt.next();

                 KeyDef kd = new KeyDef();

                 HashSet whereClause = new HashSet();

 

 

 

                 ColumnMap[] columnMaps = dbMap.getTable(tab).getColumns();

                 for (int j = 0; j < columnMaps.length; j++)

                 {

                     ColumnMap colMap = columnMaps[j];

                     /*原来的在BasePeer类中操作KeyDef加入列字段的代码

                     if (colMap.isPrimaryKey())

                     {

                         kd.addAttrib(colMap.getColumnName());

                     }

                     */

                     String key = new StringBuffer(colMap.getTableName())

                             .append('.')

                             .append(colMap.getColumnName())

                             .toString();

                     if (criteria.containsKey(key))

                     {

                         if (criteria.getComparison(key).equals(Criteria.CUSTOM))

                         {

                             whereClause.add(criteria.getString(key));

                         }

                         else

                         {

                             //增加下列语句,是为了提供where句中的限定,以利于删除数据.

                             //这种修改就避免了表一定要有primarykey的限制.

                             kd.addAttrib(colMap.getColumnName());

                             whereClause.add(SqlExpression.build(

                                     colMap.getColumnName(),

                                     criteria.getValue(key),

                                     criteria.getComparison(key),

                                     criteria.isIgnoreCase(),

                                     db));

                         }

                     }

                 }

 

 

 

                 // Execute the statement.

                 TableDataSet tds = null;

                 try

                 {

                     tds = new TableDataSet(con, tab, kd);

                     String sqlSnippet = StringUtils.join(whereClause.iterator(), " AND ");

 

 

 

                     if (log.isDebugEnabled())

                     {

                         log.debug("BasePeer.doDelete: whereClause=" + sqlSnippet);

                     }

 

 

 

                     tds.where(sqlSnippet);

                     tds.fetchRecords();

                     if (tds.size() > 1 && criteria.isSingleRecord())

                     {

                         handleMultipleRecords(tds);

                     }

                     for (int j = 0; j < tds.size(); j++)

                     {

                         Record rec = tds.getRecord(j);

                         rec.markToBeDeleted();

                         rec.save();

                     }

                 }

                 catch (Exception e)

                 {

                     throwTorqueException(e);

                 }

                 finally

                 {

                     if (tds != null)

                     {

                         try

                         {

                             tds.close();

                         }

                         catch (Exception ignored)

                         {

                         }

                     }

                 }

             }

    }

 

 

 

    private static void throwTorqueException(Exception e) throws TorqueException {

      if (e instanceof TorqueException) {

        throw (TorqueException) e;

      }

      else {

        throw new TorqueException(e);

      }

    }

    这种修改解决了本问题,也彻底修正了上面遇到的2个问题.


原文链接: http://blog.csdn.net/keepeye/article/details/381681

你可能感兴趣的:(Torque中的一些问题)