持久层的组成
这一节的名字应该换成“基于Hibernate的持久层的组成”更合适一点,可是它太长了。既然Hibernate是用来开发持久层,那么我先介绍一下这个持久层中的各个元素。
1. POJO:Plain Old Java Object,你可以把它看作是简单的JavaBean。一般说来,一张数据库表对应一个POJO,也就是对象/关系的一一映射。
2. DAO:对于每一个POJO,一般都有一个DAO与之对应,承担所有关于该POJO的访问控制。实际上也就是控制了对数据库中一张表的访问控制。
3. *.hbm.xml文件:这个文件定义了POJO和数据库中的表是如何映射的,比如POJO中的字段对应数据库表中的哪个字段等等。一般每个映射都用单独的文件来描述,也就是有一个POJO就有一个*.hbm.xml文件。
4. *.cfg.xml文件:这个文件定义了Hibernate的基本信息,比如数据库驱动,用户名,密码等等连接信息,也包括了所有要用的*.hbm.xml文件,在初始化的时候,Hibernate会读取这个文件来找相应的映射文件完成对象/关系。
我们还是以上文的例子来详细描述一下这里提到的各个元素的内容。
1. Student.java:
代码片段4:
-
- public class Student implements java.io.Serializable
- {
- private String id;
- private String name;
- private Set courseSelections = new HashSet(0);
-
- public Student()
- {
- }
-
- public String getId()
- {
- return this.id;
- }
-
- public void setId(String id)
- {
- this.id = id;
- }
-
- public String getName()
- {
- return this.name;
- }
-
- public void setName(String name)
- {
- this.name = name;
- }
-
- public Set getCourseSelections()
- {
- return this.courseSelections;
- }
-
- public void setCourseSelections(Set courseSelections)
- {
- this.courseSelections = courseSelections;
- }
- }
这个类就是一个POJO,你可以很明显的看出来它就是一个JavaBean。我想解释它的courseSelection字段。很显然,在数据库表student中,没有这个字段。这里的这个字段是因为一个外键引用,course_selection的student_id是一个外键,引用了student表中的id字段。那么在Student类中courseSelection来记录这样的外键关系,也就是说,当我们获取了Student对象以后,就可以直接获取他的选课记录,这样就为上层的调用提供了很大的方便。这里有点模糊没关系,我在介绍映射定义文件(*.hbm.xml)的时候还会提到这个问题。
2. StudentDAO.java
代码片段5:
-
- public class StudentDAO
- {
- Session session;
- public StudentDAO()
- {
- Configuration cfg = new Configuration();
- cfg.configure("/hibernate.cfg.xml");
- SessionFactory sessionFactory = cfg.buildSessionFactory();
- session = sessionFactory.openSession();
- }
-
- public void save(Student transientInstance)
- {
- session.save(transientInstance);
- }
-
- public void delete(Student persistentInstance)
- {
- session.delete(persistentInstance);
- }
-
- public Student findById(java.lang.String id)
- {
- List list = session.createCriteria(Student.class).add(
- Expression.eq("id", id)).list();
- if (list.size() > 0)
- {
- return (Student)list.get(0);
- }
- return null;
- }
- }
这里的构造函数是用来启动Hibernate,并获取session。打开一个session就相当于打开了一个数据库连接,然后我们就可以对这个session进行操作,完成数据库操作,完全不用写SQL语句。我这里Hibernate的启动方式写的很不规范,系统应该只需要完成一次Hibernate启动就可以在不同的DAO中使用,我把它写在构造函数里面纯粹是为了简化演示代码。
你可以看到save和delete方法都很简单直接对对象操作,而findById就有些麻烦,因为这里有一个查询过程在里面。Hibernate里面查询可以用Criteria这个类来完成,我们也常用Hibernate独有的HQL(Hibernate Query Language)来完成查询。当然Hibernate也是支持原生SQL的。关于查询的详细信息请参考其他文章或书籍,我只是演示一个流程,介绍一些概念。
3. Student.hbm.xml
代码片段6:
-
- <hibernate-mapping>
- <class name="Student" table="STUDENT">
- <id name="id" type="string">
- <column name="ID" length="10" />
- <generator class="assigned" />
- </id>
- <property name="name" type="string">
- <column name="NAME" not-null="true" />
- </property>
- <set name="courseSelections" inverse="true">
- <key>
- <column name="STUDENT_ID" length="10"
- not-null="true" />
- </key>
- <one-to-many class="CourseSelection" />
- </set>
- </class>
- </hibernate-mapping>
这个文件定义了Student类和Student表是如何映射的。class元素定义了Sudent类和STUDENT表映射,然后就定义了各个属性是如何映射的。如果一个属性是数据库的key,那么会用id标签来定义,column定义了当前类的属性和数据库中的哪个字段对应,generator是id特有的。一般来说id是自增的,由于我的数据库是用的Oracle,它没有自增字段,要实现自增必须用Sequence,这超出了本文的范围,所以我就用assigned来简化示例代码。assigned表示id是用户给定的。
有一个比较特别的标签是set,它对应着数据库中的外键关系,上文我提到的通过Student对象可以获得所有相关的选课记录就是通过这里的定义实现的。name属性对应了Student类中的字段名,key表示哪个字段是外键,one-to-many表示Student和CourseSelection是一对多关系,这和事实相符。类似的还有many-to-one,many-to-many,不过这些都不常用,我不介绍了。Hibernate根据这个映射定义文件,在实例化一个POJO(比如Student)的时候,会自动的把定义过映射的属性用数据库中的数据填充,set也包括在内。
4. hibernate.cfg.xml
代码片段7:
-
- <hibernate-configuration>
- <session-factory>
- <property name="connection.username">test</property>
- <property name="connection.url">
- jdbc:oracle:thin:@10.85.33.199:1521:glee</property>
- <property name="dialect">
- org.hibernate.dialect.Oracle9Dialect</property>
- <property name="connection.password">test</property>
- <property name="connection.driver_class">
- oracle.jdbc.OracleDriver</property>
- <mapping resource="Student.hbm.xml"></mapping>
- <mapping resource="CourseSelection.hbm.xml"></mapping>
- <mapping resource="Course.hbm.xml"></mapping>
- </session-factory>
- </hibernate-configuration>
这个文件我不解释了,自己看吧。结合上文StudentDAO的例子,我想你应该能看明白。
看了这么多,或许你会有点头皮发麻,POJO,DAO,配置文件...好像要写的东西还是很多。值得庆幸的是现在Hibernate已经发展的比较成熟了,有很多工具来帮助我们完成这些工作,比如MiddleGen,Hibernate Synchronizer等等。我使用的开发工具是Eclipse+MyEclipse,我所要做的只是把数据库表建好,然后MyEclipse提供的工具会自动根据数据库表生成POJO,DAO,*.hbm.xml,甚至hibernate.cfg.xml都是自动完成的(前提是MyEclipse知道你的数据库连接信息)。我并不打算介绍如何用IDE来开发Hibernate,你可以参考IDE的帮助文档。
到这里为止,使用Hibernate进行开发的基本组成元素我都介绍好了,强烈建议你马上实践一遍,即使有些不理解,也先依葫芦画瓢一个。对了,别忘了把Hibernate的包down下来放到classpath里面。
三、Session与SessionFactory
Session可以说是Hibernate的核心,Hibernate对外暴露的接口就是Session。所以我这里讲一下有关Session的常用函数和特性。
在讲Session之前,我想先提一下SessionFactory,这个东西不复杂,只要配置好就行了。顾名思义,SessionFactory就是用来创建Session的。SessionFactory是线程安全的,也就是说对于同一个数据库的所有操作共享一个SessionFactory就行了。回头看代码片段5,我们可以看到SessionFactory的常用配置方式。
代码片段8:
-
- Configuration cfg = new Configuration();
- cfg.configure("/hibernate.cfg.xml");
- SessionFactory sessionFactory = cfg.buildSessionFactory();
我们通过Configuration来读取配置文件,然后就可以创建SessionFactory,这段代码在 所有系统中都大同小异,一般就是xml配置文件的名字不一样,所以也没什么好说的。
当我们有了SessionFactory以后就可以获取Session了。调用SessionFactory.openSession()就会返回一个Session实例,然后我们操作这个Session来访问数据库。值得一提的是Session并不是线程安全的,也就是每一个线程都必须有自己的Session。所以我们一般通过以下方法来获取和关闭Session:
代码片段9:
-
- public static Session currentSession() throws HibernateException
- {
- Session session = (Session) threadLocal.get();
- if (session == null || !session.isOpen())
- {
- if (sessionFactory == null)
- {
- try
- {
- cfg.configure(CONFIG_FILE_LOCATION);
- sessionFactory = cfg.buildSessionFactory();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- session = (sessionFactory != null) ?
- sessionFactory.openSession(): null;
- threadLocal.set(session);
- }
- return session;
- }
-
- public static void closeSession() throws HibernateException
- {
- Session session = (Session) threadLocal.get();
- threadLocal.set(null);
-
- if (session != null)
- {
- session.close();
- }
- }
可以看到,我们通过threadLocal来保存每个线程的session,这样就保证了各个线程之 间的互不干扰,也保证了系统只有一个SessionFactory实例(对于大多数应用来说已经 足够了)。如果你使用MyEclipse进行开发的话,它会自动生成一个 HibernateSessionFactory.java,其中就包含了以上代码。
好了,现在我们已经获得了Session,下面我来介绍以下Session的常用函数,这些函数都有很多重载函数,我只介绍以下大概是干嘛的,不一一解释,详细信息你可以查看Hibernate的API。
1.Session.get(),获取某个类的实例,一般都是通过id来获取比如
Session.get(Student.class, "0361095");
这句话的意思就是获取id(primary key)为“0361095”的Student对象。这里要注 意的是第二个参数必须是Object,也就是说,如果是long类型的1,那么必须转换 成new Long(1)再传入。
2.Session.load(),用法和意义都和get一样,不过它们还是有点区别,我稍后解释。
3.Session.save(),将某个实例保存到数据库中去(往往在数据库中形成一条新的记录)。
4.Session.update(),更新某个实例,这个实例必须和数据库中有对应,否则会报错。
5.Session.delete(),删除某个实例,也就是删除这个实例对应的数据表中的数据。
6.Session.saveOrUpdate(),保存或者更新某个实例,调用这个方法你就不用去关心到 底是save还是update了,它会自己判断,然后调用相应的函数。其实save和update 涉及到实体对象生命周期中的三种状态,这个比较重要,我在后面会单独讲的。
对于get和load的区别,很难讲清楚,这里涉及到Hibernate的缓存机制,是一个非常 复杂的话题,我不打算深入讨论这个内容。简单的来讲,Session实现了Hibernate的一 级缓存,SessionFactory实现了Hibernate的二级缓存。load方法会先查找一级缓存再查 找二级缓存,最后再去数据库中找,而get只会查找一级缓存,然后就去数据库中找了。 这只是是get和load的一个区别,另外的区别如果你有兴趣的话自己去google吧。关于Hibernate的缓存机制,如果你只是一般用用Hibernate的话没有必要深入研究,就当它不存在好了,get和load到底用哪个也不用非常讲究,如果你用工具生成DAO的话, 生成的代码用什么就用什么吧。
四、关键概念的理解
在使用Hibernate进行开发的时候有几个概念在我看来是必须理解的,否则在开发的时候会遇到很多问题而摸不着头脑。
1.Lazy loading,懒加载(延迟加载)
这个技术在很多地方被应用,比如Eclipse的插件管理也是用的延迟加载。在Hibernate中,所谓延迟加载就是返回一个POJO但是某些数据(往往是实体类型或者Set类型)并没有被真正的被填充,直到POJO的某个字段真正被引用的时候才从数据库中读取相应的数据来填充POJO中的字段。这样就能有效的避免很多不必要的数据库操作,因为POJO的有些数据我们并不需要,而且数据库操作是很费时间的。在Hibernate2中,默认是非延迟加载的,而在Hibernate3中,默认就是延迟加载了。
如果使用了延迟加载,那么在读取数据的时候有一个问题必须注意,那就是在数据真正被加载之前,Session不能被关闭。你可以回头看一下代码片段5,我在构造函数里面open了session以后就没有关闭这个session,所以我在使用的时候没有什么问题,但这样总占着数据库连接也不好,用好了应该及时关闭给别人用。我上文给的例子中没有关闭Session的代码,要加的话给DAO加一个方法调用Session.close(),然后在代码片段1中的return之前调用这个方法就行了。
Hibernate的延迟加载机制远不是这么简单,但是普通的应用没有必要去深究这些东西,了解这么多就够了。
2. Object lifecycle,对象生命周期
在Hibernate中,对象分为三种状态,Transient(自由状态)、Persistent(持久状态),Detached(游离状态),下面我分别解释一下。
1、自由状态:所谓自由状态就是说这个对象是自由的,与Hibernate无关,比 如:
Student student = new Student();
student.setId("0361095");
这里的student就是一个普通的对象与hibernate无关,称为自由状态。
2、持久状态:所谓持久状态就是指对象和数据库中的数据(持久状态的数据) 有关联,也就是说对象被Hibernate所管理了,比如:
session.save(student);
这样student对象就从自由状态变为持久状态了。持久状态的对象在Session 与数据库中的数据进行同步时(比如commit)会把数据更新到数据库。而 其他状态的则不会。我觉得可以这样来理解持久状态,可以看成Hibernate 也拥有一份对象的引用,那么如果你对持久状态对象的属性进行更改的话, Hibernate看到的对象的状态也更改了,而Hibernate所看到的对象和数据 库中的数据是等价的。也正是这样,Hibernate才实现了Object/Relation 的映射。类似的,load和get方法也一样会获取Persistent状态的对象。
3、游离状态:每次调用Session.close以后,所有跟这个session有关的处于
Persistant的对象就变成了游离状态。也许你要问Detached和Transient有什么区别。其实从我给的例子来说看不出什么区别,因为我这里ID是给定的,而真正开发的时候ID往往是自增的,那么Transient的对象是没有ID 的,当save了以后就有了,显而易见Detached的对象也是有ID,只不过这个对象已经和Hibernate脱离了关系。但是游离状态的对象仍然和数据库 中的记录有一定联系,至少游离状态的对象知道数据库中有条记录的ID为xxx。从这一点上来讲,游离状态是可以自己创造出来的,只要你知道数据库中的主键信息。
在使用Hibernate开发的时候要分清楚这三种状态,否则很容易出错。比如不能去save一个游离状态的对象,不能去update一个自由状态的对象等等。
五、结束语
我要讲的就这么多,这篇文章不是详细介绍Hibernate,我只是总结了自己学习和使用Hibernate的一些感受和经验,希望能给没有用过Hibernate的开发者一个上手的指引。如果你看完了这篇文章仍然满头雾水,无从下手的话,我只能向你表示歉意,浪费你的时间了。不管怎样,我强烈推荐一本书《深入浅出Hibernate》,这本书既能作为学习的教程,也能作为日后的查询用书,相当实用。
基本类型有以下四种:
int长度数据类型有:byte(8bits)、short(16bits)、int(32bits)、long(64bits)、
float长度数据类型有:单精度(32bits float)、双精度(64bits double)
boolean类型变量的取值有:ture、false
char数据类型有:unicode字符,16位
对应的类类型:
Integer、Float、Boolean、Character、Double、Short、Byte、Long
转换原则:
从低精度向高精度转换
byte 、short、int、long、float、double、char
注:两个char型运算时,自动转换为int型;当char与别的类型运算时,也会先自动转换为int型的,再做其它类型的自动转换
基本类型向类类型转换
正向转换:
通过类包装器来new出一个新的类类型的变量
Integer a= new Integer(2);
反向转换:
通过类包装器来转换
int b=a.intValue();
类类型向字符串转换
正向转换:
因为每个类都是object类的子类,而所有的object类都有一个toString()函数,所以通过toString()函数来转换即可
反向转换:
通过类包装器new出一个新的类类型的变量
eg1: int i=Integer.valueOf(“123”).intValue()
说明:上例是将一个字符串转化成一个Integer对象,然后再调用这个对象的intValue()方法返回其对应的int数值。
eg2: float f=Float.valueOf(“123”).floatValue()
说明:上例是将一个字符串转化成一个Float对象,然后再调用这个对象的floatValue()方法返回其对应的float数值。
eg3: boolean b=Boolean.valueOf(“123”).booleanValue()
说明:上例是将一个字符串转化成一个Boolean对象,然后再调用这个对象的booleanValue()方法返回其对应的boolean数值。
eg4:Double d=Double.valueOf(“123”).doubleValue()
说明:上例是将一个字符串转化成一个Double对象,然后再调用这个对象的doubleValue()方法返回其对应的double数值。
eg5: long l=Long.valueOf(“123”).longValue()
说明:上例是将一个字符串转化成一个Long对象,然后再调用这个对象的longValue()方法返回其对应的long数值。
eg6: char=Character.valueOf(“123”).charValue()
说明:上例是将一个字符串转化成一个Character对象,然后再调用这个对象的charValue()方法返回其对应的char数值。
基本类型向字符串的转换
正向转换:
如:int a=12;
String b;
b=a+””;
反向转换:
通过类包装器
eg1:
int i=Integer.parseInt(“123”)
说明:此方法只能适用于字符串转化成整型变量
eg2: float f=Float.valueOf(“123”).floatValue()
说明:上例是将一个字符串转化成一个Float对象,然后再调用这个对象的floatValue()方法返回其对应的float数值。
eg3: boolean b=Boolean.valueOf(“123”).booleanValue()
说明:上例是将一个字符串转化成一个Boolean对象,然后再调用这个对象的booleanValue()方法返回其对应的boolean数值。
eg4:Double d=Double.valueOf(“123”).doubleValue()
说明:上例是将一个字符串转化成一个Double对象,然后再调用这个对象的doubleValue()方法返回其对应的double数值。
eg5: long l=Long.valueOf(“123”).longValue()
说明:上例是将一个字符串转化成一个Long对象,然后再调用这个对象的longValue()方法返回其对应的long数值。
eg6: char=Character.valueOf(“123”).charValue()
说明:上例是将一个字符串转化成一个Character对象,然后再调用这个对象的charValue()方法返回其对应的char数值。
java
数据库基本操作
1、java数据库操作基本流程
2、几个常用的重要技巧:
·可滚动、更新的记录集
·批量更新
·事务处理
java数据库操作基本流程:取得数据库连接 - 执行sql语句 - 处理执行结果 - 释放数据库连接
1、取得数据库连接
1)用DriverManager取数据库连接
例子
String className,url,uid,pwd;
className = "oracle.jdbc.driver.OracleDriver";
url = "jdbc:oracle:thin:@127.0.0.1:1521:orasvr;
uid = "system";
pwd = "manager";
Class.forName(className);
Connection cn = DriverManager.getConnection(url,uid,pwd);
2)用jndi(java的命名和目录服务)方式
例子
String jndi = "jdbc/db";
Context ctx = (Context) new InitialContext().lookup("java:comp/env");
DataSource ds = (DataSource) ctx.lookup(jndi);
Connection cn = ds.getConnection();
多用于jsp中
2、执行sql语句
1)用Statement来执行sql语句
String sql;
Statement sm = cn.createStatement();
sm.executeQuery(sql); // 执行数据查询语句(select)
sm.executeUpdate(sql); // 执行数据更新语句(delete、update、insert、drop等)statement.close();
2)用PreparedStatement来执行sql语句
String sql;
sql = "insert into user (id,name) values (?,?)";
PreparedStatement ps = cn.prepareStatement(sql);
ps.setInt(1,xxx);
ps.setString(2,xxx);
...
ResultSet rs = ps.executeQuery(); // 查询
int c = ps.executeUpdate(); // 更新
3、处理执行结果
查询语句,返回记录集ResultSet
更新语句,返回数字,表示该更新影响的记录数
ResultSet的方法
1、next(),将游标往后移动一行,如果成功返回true;否则返回false
2、getInt("id")或getSting("name"),返回当前游标下某个字段的值
4、释放连接
cn.close();
一般,先关闭ResultSet,然后关闭Statement(或者PreparedStatement);最后关闭Connection
可滚动、更新的记录集
1、创建可滚动、更新的Statement
Statement sm = cn.createStatement(ResultSet.TYPE_SCROLL_ENSITIVE,ResultSet.CONCUR_READ_ONLY);
该Statement取得的ResultSet就是可滚动的
2、创建PreparedStatement时指定参数
PreparedStatemet ps = cn.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet.absolute(9000);
·批量更新
1、Statement
Statement sm = cn.createStatement();
sm.addBatch(sql1);
sm.addBatch(sql2);
...
sm.executeBatch()
一个Statement对象,可以执行多个sql语句以后,批量更新。这多个语句可以是delete、update、insert等或兼有
2、PreparedStatement
PreparedStatement ps = cn.preparedStatement(sql);
{
ps.setXXX(1,xxx);
...
ps.addBatch();
}
ps.executeBatch();
一个PreparedStatement,可以把一个sql语句,变换参数多次执行,一次更新。
·事务的处理
1、关闭Connection的自动提交
cn.setAutoCommit(false);
2、执行一系列sql语句
要点:执行每一个新的sql语句前,上一次执行sql语句的Statement(或者PreparedStatemet)必须先close
Statement sm ;
sm = cn.createStatement(insert into user...);
sm.executeUpdate();
sm.close();
sm = cn.createStatement("insert into corp...);
sm.executeUpdate();
sm.close();
3、提交
cn.commit();
4、如果发生异常,那么回滚
cn.rollback();
Apache已经发布了Struts2.0的正式版,即2.0.6GA版本。这个版本已经可以在项目中正式使用了。当然大家一定很关心,从webwork2.2迁移到struts2.0麻烦不麻烦,请看Struts2.0的FAQ:
引用
Essentially, Struts 2.0 is the technical equivalent of WebWork 2.3. Aside from the package and property renaming, it isn't much different than, say, migrating from WebWork 2.1 to 2.2.
Struts2.0其实就是webwork2.3而已,从webwork2.2迁移到struts2.0不会比从webwork2.1到2.2更麻烦。
webwork2.2和struts2.0差异对比:
http://struts.apache.org/2.x/docs/key-changes-from-webwork-2.html
迁移步骤:
http://struts.apache.org/2.x/docs/webwork-2-migration-strategies.html
总结:
大致来说,struts2.0就是把package和配置文件的名字改了改而已,别的没有做什么改动,所以现在用struts2.0和用webwork2.2没有多大区别。当然这迁移一迁就是将近两年,还是有点进步的:
1、搭配struts2.0的xwork版本必须使用xwork2.0.1,而xwork2.0.1集成了可选的Google Guice IoC容器
2、Struts2.0弄了一个plugin机制,来适配各种扩展机制
3、全面引入annotation语法,验证,拦截都可以用annotation了。
所以用webwork的同志们,大胆的迁移到struts2.0来吧。
缓存实现的层面有很多:
1、对象缓存
由ORM框架提供,透明性访问,细颗粒度缓存数据库查询结果,无需业务代码显式编程。当软件结构按照ORM框架的要求进行针对性设计,使用对象缓存将会极大降低web系统对于数据库的访问请求。因为类似Hibernate这样的ORM,良好的设计数据库结构和利用对象缓存,在大负载网站,能够提供极高的性能。因为使用对象缓存也无需显式编程,所以适用范围也最广泛。
2、查询缓存
对数据库查询结果行集进行缓存,适用于一些耗时,但是时效性要求比较低的场景。iBATIS就只能使用查询缓存,而无对象缓存。查询缓存和对象缓存适用的场景不一样,是互为补充的。
3、片断缓存
针对动态页面的局部片断内容进行缓存,适用于一些个性化但不经常更新的页面(例如博客)。OSCache提供了相当简陋的片断缓存,而RoR则提供了相当好的片断缓存机制。
4、Action缓存
针对URL访问返回的页面结果进行缓存,适用于粗粒度的页面缓存,例如新闻发布。OScache提供了相当简陋的Action缓存(通过web.xml中的配置),而RoR提供了相当好的Action缓存。
缓存不能一概而论,以上每种缓存分别适用于各自的场景,缓存不同的层面。当然你可以在应用程序当中把4种缓存一起用上。
import dori.jasper.engine.*;
import dori.jasper.engine.util.*;
import java.sql.*;
import java.util.*;
import dori.jasper.engine.JasperReport;
import java.util.Date;
import java.text.SimpleDateFormat;
import dori.jasper.view.*;
import org.apache.commons.logging.LogFactory;
import com.lowagie.text.DocumentException;
import com.lowagie.text.*;
import dori.jasper.engine.design.*;
import org.apache.commons.digester.Digester;
import org.apache.commons.beanutils.BeanUtils;
public class myreport
{
public void reportName(String strBbmc)
{
JRResultSetDataSource jrds = null;
JasperPrint jasperPrint = null;
Map reportParams = new HashMap();
ResultSet rs = null;
Connection con = null;
Statement stmt = null;
String sql = "select * from bbmc";
try
{
//动态获取
JasperDesign jasperDesign = JasperManager.loadXmlDesign("./bbmc.jrxml");
JasperReport jasperreport = JasperManager.compileReport(jasperDesign);
//建立连接
ConnectionpoolManager mgr = new ConnectionpoolManager();
mgr.setMonitorThread(520);
mgr.addAlias("jdbcConnecpool","com.microsoft.jdbc.sqlserver.SQLServerDriver","jdbc:microsoft:sqlserver://192.168.45.233:1433;DatabaseName = ysgl ","sa","sa",10,300,520,30,false);
con = DriverManager.getConnection(ConnectionPoolManager.URL_PREFIX+ConnectionPoolManager.getPoolName(),null,null);
stmt = con.creatStatement();
rs = stmt.executeQuery(sql);
jrds = new JRResultSetDataSource(rs);
jasperPrint = JasperFillManager.fillReport(jasperReport,reportParams,jrds);
//调出JasperViewer进行打印预览
JasperViewer.viewReport(jasperPrint);
}
catch(ClassNotFoundException ee)
{
ee.printStackTrace();
}
catch(SQLException ex)
{
ex.pritStackTrace;
}
catch(JRException e)
{
e.getMessage();
}
}
}
实现论坛树型结构的算法很多,这里是一个不用递归实现树型结构的算法
1.演示表的结构:
表名:mybbslist
字段 数据类型 说明
BBSID 自动编号
RootID Int 根帖ID,本身为根帖则RootID = BBSID
FID Int 父帖ID,上一层帖子的ID,如是根帖则FID = 0
DEPTH Int 根帖Level=0,其他依据回复的深度递增
BBSSubject Char 主题 |
2.创建表(可根据此表的结构在ACCESS中创建表):
create table mybbslist (
forumID int(20) not null,
bbsID int auto_increment primary key,
rootid int(20) not null,
fid int(20) not null,
depth int(20) not null,
userID int(20) not null,
bbsUser varchar(24) not null,
bbsSubject varchar(100) not null,
bbsContent text,
bbsTime varchar(30),
bbsRead int(20),
bbsReply int(20),
INDEX forumID (forumID)) |
3.演示论坛树型结构的JSP程序,注意此程序只对一个根贴进行了演示(数据库用ACCESS)
<%@ page contentType="text/html;charset=gb2312" %>
<%@ page import="java.io.*" %>
<%@ page import="java.sql.*" %>
<%
String driverName = "sun.jdbc.odbc.JdbcOdbcDriver";
String connURL= "jdbc:odbc:cwb";
Connection conn = null;
Statement stmt = null;
int intRowCount;
out.print("显示论坛树形结构");
out.print("<br><br>");
try {
Class.forName(driverName);
conn = DriverManager.getConnection(connURL);
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
String sql="select * from mybbslist order by rootid desc,depth,fid,bbsid";
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()){
rs.last();
intRowCount=rs.getRow();
out.print("论坛树中有");
out.print(intRowCount);
out.print("个叶子节点");
rs.first();
int j=0;
int Depth = 0;
out.print("<ul>");
while(j<intRowCount){
int rsDepth=rs.getInt("depth");
if (rsDepth>Depth){
out.print("<ul>");
}
out.print("<li>");
String bbssubject=rs.getString("bbsSubject");
out.print(bbssubject);
out.print("</li>");
Depth=rsDepth;
j=j+1;
rs.next();
}
for(int i=0;i<Depth+1;i=i+1) {
out.print("</ul>");
}
}else{
out.print("数据库中无记录");
}
}catch (SQLException E) {
out.println("SQLException: " + E.getMessage());
out.println("SQLState: " + E.getSQLState());
out.println("VendorError: " + E.getErrorCode());
}
%>
<% //关闭mysql连接
try {
if(conn!=null) conn.close();
} catch (Exception ex) {
System.err.println("closeConn: " + ex.getMessage());
}
%>
|
最近在SUN的网站上看到一则关于J2ME GUI的文章:《
The Java ME GUI APIs at a Glance 》。文章对
MIDP 1.0, 2.0, 3.0的图形界面进行了阐述和比较。最吸引我的是Advanced Graphics and User Interface (AGUI),看这界面是不是很COOL,不过要CDC1.1才能运行,希望以后手机能发展到可以支持这样的图形界面。
软件质量保证和软件控制都是得罪人的工作,并不是太好的做。经常是费力不讨好,最后人得罪了不少,力气费了不少,成效不大。而很多质量保证人员也不得不离开所在的单位。很多著名的公司为了搞好质量工作,采用国外先进的管理方法进行质量保证,但也往往遇到水土不服的问题,因为这个问题下岗的高级管理人员也不是一个两个了。为什么在国外很好的质量保证手段在中国就水土不服?为什么质量保证人员总是打一枪换一个地方(很多质量保证人员在一个地方工作的时间往往是2年左右)。如果我们一味强调客观因素,而不是从我们自身找问题,是没有办法真正找到解决这个的方法的。下边就我说说我自己的一些感受。
1 质量保证工作,重流程,轻实施是我们的一个问题。
现在的质量保证人员一般手里都不乏这样或那样的证书,拿我们软件质量保证人员来说,最基本的是ISO9000的证书,然后是CMM/CMMI,似乎没有这些证书就不适合搞质量保证工作。如果让他们规范单位开发流程,他们在几天之内就可以给你拿出一大堆流程文件来,而且可以保证这些问题是符合质量的要求。但一旦到了实施阶段,就会发现这些流程不能适应公司的具体要求,不是太繁琐,就是开发人员搞不明白,和技术人员矛盾不断涌现,如果你的对手只是普通的技术人员那算你走运,你还可以挺几年,但如果是和公司几个核心的开发人员产生矛盾,那你准备卷铺盖走人吧。一个公司没有质量保证人员不会死,但核心技术人员如果走了,公司就会面临倒闭的问题,再傻的老板到这个时候都会挥泪斩马谡的。所以,想真正实施好公司的质量保证一定要重流程,更重实施。
2 质量保证工作,重知识,轻经验。
我这里说的知识是指质量保证知识,经验是指实际工作经验,拿我们做SQA的来说就是你的开发经验,我遇到了很多SQA只有1-2年的开发经验,或者干脆就没有开发经验。想一下,没有调研经验的人去给一帮参加过7,8个大项目调研的人将如何调研,一个没有编写过设计文档的人,在给一群架构师讲解如何设计软件,一个没有代码编程经验的人给一帮代码量超过20万行的程序员讲代码规范,你说是不是一个可笑的事情,开发人员都是很实在的人(甚至有时候可以说比较傻的人),他们长期的工作经验使他们只信服那些实力比他们强的人,如果你有充足的实际工作经验(软件开发经验),说的问题到位,能够将质量保证理论和实际开发结合,可以帮助他们解决实际问题,他们很快就会接受你,否则他们会对你不屑一顾。而你的质量保证根本无法实施。
3 质量保证人员不要抢功劳。
一个公司就象一个盘子,盘子里的蛋糕就这么大。你进来了需要从别人的蛋糕里切一块,别人自然会有反弹,作为质量保证人员在这个时候最好的方法就是退后一步,将功劳让给开发人员。质量保证工作会涉及到公司的各个部门,各项工作,会影响公司几乎所有的工作人员,如果在这个时候,你想把所有的功劳都抢到自己手里,会树敌无数,一旦这种情况出现,一个是你的工作无法推行(基本没有人支持你),另外一个就是你不得不离开这个公司了。但如果你是聪明人,你帮助开发人员不断的改善他们的工作,他们可以很好的完成自己的开发工作,加班时间大大减少,产品质量不断提高,不断获得客户和领导的表扬,而且加了工资,你想在这种情况下,他们怎么会不配合你的工作?所以说,好的质量保证人员永远是站在成功者背后的那个人,他默默地做着自己的工作,为别人做绿叶。一个良好的心态才能真正搞好质量保证工作。在这里再多说一句,任何人的功劳都会得到回报。当公司的整体质量提升了,公司的效益提高了,你觉得老板会不明白这是谁的功劳吗?
4 质量保证工作,不能一开始就全面开花,而要由点到线,由线到面。
上边说了质量保证工作会涉及到单位工作的各个方面,在你刚进入到公司的时候,立刻会发现很多问题,但如何着手,需要一个谋划,一般来说比较容易见效果的,投入不大的方面是你首先要考虑的(有时候还不是公司最主要的质量问题)。如果你能在短时间让别人看到你的工作效果,见到成果,认可你的实力,才可能和他们达成一定的协作关系,为以后的质量保证工作铺平道路。另外需要说的,质量保证需要不断的投入人力和物力,而这些东西在你刚进入公司的时候往往是不具备的,分清事情的轻重缓急,难易程度,逐步实施质量保证。可以保证你的工作的顺利实施。
5 做质量保证工作,最重要的是做人的工作,这里分两个问题来说明,一个是你要有自己可信赖的人员。
打铁先要自身硬,做质量保证工作,不但你最好有技术背景,精通软件开发,遵守公司的规章制度,你还要有一支可培养,可信赖的质量保证队伍,一个人的精力和能力毕竟是有限的,而一旦你形成了一个良好的质量保证队伍就可以保证你的工作越做越有成效。另外一个就是善于借力打力。上边说过,绝大多数开发人员都渴望成功,他们缺少的只是经验,将你的经验和知识和他们分享,让他们成为的朋友,成为工作的伙伴,成为你的编外质量人员,这对那些质量保证人员编制比较少的质量保证部门格外重要。
6 质量保证工作,遇到问题,先解决问题,找出原因,进行改进,而不要一味地追查责任。
质量保证人员的责任是改进质量工作,提高整个公司的工作效率,而不是老板,去追查这是谁的责任。当一个问题发生的时候,所有的人员都在往后躲。怕承担责任,作为质量人员如果在这个时候,首先去追查责任,那你就大错特错了,首先我们要解决问题,看有什么补救的方法,先想办法将事情办好,然后仔细分析问题产生的原因,找到如何避免这个问题再次发生,至于责任在哪个责任人,自有具体的管理人员负责,这不是我们的责任,说简单一点,我们的责任就是协助一切工作人员做好他们的工作,而不是给人员裹乱。
一、 前言
本文作者在企业从事SQA工作,同时兼任SEPG的工作进行基于CMM3的过程改进,在实践过程中,对SQA的工作有了较多的想法和认识。本文是个人看法,请大家指教,如果要和本人联系,请发Email到:heqingemail@163.net。
二、SQA的理论探索
2.1、过程的;认识
我们都知道一个项目的主要内容是:成本、进度、质量;良好的项目管理就是综合三方面的因素,平衡三方面的目标,最终依照目标完成任务。项目的这三个方面是相互制约和影响的,有时对这三方面的平衡策略甚至成为一个企业级的要求,决定了企业的行为,我们知道IBM的软件是以质量为最重要目标的,而微软的“足够好的软件”策略更是耳熟能详,这些质量目标其实立足于企业的战略目标。所以用于进行质量保证的SQA工作也应当立足于企业的战略目标,从这个角度思考SQA,形成对SQA的理论认识。
软件界已经达成共识的:影响软件项目进度、成本、质量的因素主要是“人、过程、技术”。
首先要明确的是这三个因素中,人是第一位的。
现在许多实施CMM的人员沉溺于CMM的理论过于强调“过程”,这是很危险的倾向。这个思想倾向在国外受到了猛烈抨击,从某种意义上各种敏捷过程方法的提出就是对强调过程的一种反思。
“XP”中的一个思想“人比过程更重要” 是值得我们思考的。我个人的意见在进行过程改进中坚持“以人为本”,强调过程和人的和谐。
根据现代软件工程对众多失败项目的调查,发现管理是项目失败的主要原因。这个事实的重要性在于说明了“要保证项目不失败,我们应当更加关注管理”,注意这个事实没有说明另外一个问题“良好的管理可以保证项目的成功”。现在很多人基于一种粗糙的逻辑,从一个事实反推到的这个结论,在逻辑上是错误的,这种错误形成了更加错误的做法,这点在SQA的理解上是体现较深。
如果我们考证一下历史的沿革,应当更加容易理解CMM的本质。CMM首先是作为一个“评估标准”出现的,主要评估的是美国国防部供应商保证质量的能力。CMM关注的软件生产有如下特点:
质量重要
规模较大
这是CMM产生的原因。它引入了“全面质量管理”的思想,尤其侧重了“全面质量管理”中的“过程方法”,并且引入了“统计过程控制”的方法。可以说这两个思想是CMM背后的基础。
上面这些内容形成了我对软件过程地位、价值的基本理解;在这个基础上我们可以引申讨论SQA。
2.2、生产线的隐喻
如果将一个软件生产类比于一个工厂的生产。那么生产线就是过程,产品按照生产线的规定过程进行生产。SQA的职责就是保证过程的执行,也就是保证生产线的正常执行。
抽象出管理体系模型的如下,这个模型说明了一个过程体系至少应当包含“决策、执行、反馈”三个重要方面。QA的职责就是确保过程的有效执行,监督项目按照过程进行项目活动;它不负责监管产品的质量,不负责向管理层提供项目的情况,不负责代表管理层进行管理,只是代表管理层来保证过程的执行。
2.3、SQA和其他工作的组合
在很多企业中,将SQA的工作和QC、SEPG、组织级的项目管理者的工作混合在一起了,有时甚至更加注重其他方面的工作而没有做好SQA的本职工作。
根据hjhza 的意见“中国现在基本有三种QA(按照工作重点不同来分):一是过程改进型,一是配置管理型,一是测试型”。我个人认为是因为SQA工作和其他不同工作组合在一起形成的。
下面根据本人经验对它们之间的关系进行一个说明。
2.4、QA和QC
两者基本职责
QC:检验产品的质量,保证产品符合客户的需求;是产品质量检查者;
QA:审计过程的质量,保证过程被正确执行;是过程质量审计者;
注意区别检查和审计的不同
检查:就是我们常说的找茬,是挑毛病的;
审计:来确认项目按照要求进行的证据;仔细看看CMM中各个KPA中SQA的检查采用的术语大量用到了“证实”,审计的内容主要是过程的;对照CMM看一下项目经理和高级管理者的审查内容,他们更加关注具体内容。
对照上面的管理体系模型,QC进行质量控制,向管理层反馈质量信息;QA则确保QC按照过程进行质量控制活动,按照过程将检查结果向管理层汇报。这就是QA和QC工作的关系。在这样的分工原则下,QA只要检查项目按照过程进行了某项活动没有,产出了某个产品没有;而QC来检查产品是否符合质量要求。如果企业原来具有QC人员并且QA人员配备不足,可以先确定由QC兼任QA工作。但是只能是暂时的,独立的QA人员应当具备,因为QC工作也是要遵循过程要求的,也是要被审计过程的,这种混合情况,难以保证QC工作的过程质量。
2.5、QA和SEPG
两者基本职责
SEPG:制定过程,实施过程改进;
QA: 确保过程被正确执行
SEPG应当提供过程上的指导,帮助项目组制定项目过程,帮助项目组进行策划;从而帮助项目组有效的工作,有效的执行过程。如果项目和QA对过程的理解发生争持,SEPG作为最终仲裁者。为了进行有效过程改进,SEPG必须分析项目的数据。
QA本也要进行过程规范,那么所有QA中最有经验、最有能力的QA可以参加SEPG,但是要注意这两者的区别。
如果企业的SEPG人员具有较为深厚的开发背景,可以兼任SQA工作,这样利于过程的不断改进;但是由于立法、执法集于一身也容易造成SQA过于强势,影响项目的独立性。管理过程比较成熟的企业,因为企业的文化和管理机制已经健全,SQA职责范围的工作较少,往往只是针对具体项目制定明确重点的SQA计划,这样SQA的审计工作会大大减少,从而可以同时审计较多项目。
另一方面,由于分工的细致化,管理体系的复杂化,往往需要专职的SEPG人员,这些人员要求了解企业的所有管理过程和运作情况,在这个基础上才能统筹全局的进行过程改进,这时了解全局的SQA人员就是专职SEPG的主要人选;这些SQA人员将逐渐的转化为SEPG人员,并且更加了解管理知识,而SQA工作渐渐成为他们的兼职工作。
这种情况在许多CMM5企业比较多见,往往有时看不见SQA人员在项目组出现或者很少出现,这种SEPG和SQA的融合特别有利于组织的过程改进工作。SEPG确定过程改进内容,SQA计划重点反映这些改进内容,从保证有效的改进,特别有利于达到CMM5的要求。从这个角度,国外的SQA人员为什么高薪就不难理解了,也决定了当前中国SQA人员比较被轻视的原因;因为管理过程还不完善,我们的SQA人员还没有产生这么大的价值嘛!
2.6、QA和组织级的监督管理
有的企业为了更好的监督管理项目,建立了一个角色,我取名为“组织级的监督管理者”,他们的职责是对所有项目进行统一的跟踪、监督、适当的管理,来保证管理层对所有项目的可视性、可管理性。
为了有效管理项目,“组织级的监督管理者”必须分析项目的数据。
他们的职责对照上图的模型,就是执行“反馈”职能。
QA本身不进行反馈工作,最多对过程执行情况的信息进行反馈。
SQA职责最好不要和“组织级的项目管理者”的职责混合在一起,否则容易出现SAQ困境:一方面SQA不能准确定位自己的工作,另一方面过程执行者对SQA人员抱有较大戒心。
如果建立了较好的管理过程,那么就会增强项目的可视性,从而保证企业对所有项目的较好管理;而QA来确保这个管理过程的运行。
三、SQA的工作内容和工作方法
3.1、 计划
针对具体项目制定SQA计划,确保项目组正确执行过程。制定SQA计划应当注意如下几点:
有重点:依据企业目标以及项目情况确定审计的重点
明确审计内容:明确审计哪些活动,那些产品
明确审计方式:确定怎样进行审计
明确审计结果报告的规则:审计的结果报告给谁
3.2、审计/证实
依据SQA计划进行SQA审计工作,按照规则发布审计结果报告。
注意审计一定要有项目组人员陪同,不能搞突然袭击。双方要开诚布公,坦诚相对。
审计的内容:是否按照过程要求执行了相应活动,是否按照过程要求产生了相应产品。
3.3、问题跟踪
对审计中发现的问题,要求项目组改进,并跟进直到解决。
四、SQA的素质
过程为中心:应当站在过程的角度来考虑问题,只要保证了过程,QA就尽到了责任。
服务精神:为项目组服务,帮助项目组确保正确执行过程
了解过程:深刻了解企业的工程,并具有一定的过程管理理论知识
了解开发:对开发工作的基本情况了解,能够理解项目的活动
沟通技巧:善于沟通,能够营造良好的气氛,避免审计活动成为一种找茬活动。