J2EE学习笔记
我们的失落……
posts - 13,comments - 1,trackbacks - 0
     摘要: 提起Java内部类(Inner Class)可能很多人不太熟悉,实际上类似的概念在C++里也有,那就是嵌套类(Nested Class),关于这两者的区别与联系,在下文中会有对比。内部类从表面上看,就是在类中又定义了一个类(下文会看到,内部类可以在很多地方定义),而实际上并没有那么简单,乍看上去内部类似乎有些多余,它的用处对于初学者来说可能并不是那么显著,但是随着对它的深入了解,你会发现Java的...  阅读全文
posted @ 2010-06-30 14:26 J2EE学习笔记 阅读(297) | 评论 (0)编辑 收藏

在JScript的众多运算符里,提供了三个逻辑运算符&&||!,噢?! 是高级语言都提供的。按我们对逻辑运算的正常认识,逻辑运算的结果因该是ture或者false。但是JScript的逻辑运算却不完全是这么定义的,这里只有!运算符总是返回true|false,而||和&&运算比较的好玩。

    JScript对于逻辑运算的true|false是这么定义的:

  • 所有对象都被认为是 true。
  • 字符串当且仅当为空(""或'')时才被认为是 false。
  • null 和未定义的均被认为是 false。
  • 数字当且仅当为 0 时才是 false。

    可是逻辑运算符||&&虽然遵循上面的定义规则,但是它们返回的值却很有意思。
    对于&&运算,按照上面的规则,表达式 if ( 'abc' && '123' && new Date() ) 是执行true分支,可是这个表达式如果写成:

var value = 'abc' && '123&& new Date();

 

    结果value=Fri Jan 21 00:01:17 UTC+0800 2005,原它从左到右检测,如果到了最后一个表达式也是为true的,就返回那个表达式。

    对于||运算同理,对于下面的表达式:

var value1 = 'abc' || '123|| null || false;
var value2 = null || '' || false || 'ok';

 

    结果value1='abc',value2='ok'。这是因为||运算会有"短路"特性,他也是从左向右检测,只不过它是一但发现有为true的值,就立即返回该表达式。
    这样的特性可以帮组我们写出精简的代码,可是同时也带来代码不便于阅读维护的问题。
    由于我手头暂时没有NS和moz什么的浏览器,不知道标准JavaScript是否也是这样支持的?如果您方便的话,请告如我运行后的结果

posted @ 2010-05-13 15:11 J2EE学习笔记 阅读(182) | 评论 (0)编辑 收藏

有时你可能需要对变量进行类型检查,或者判断变量是否已定义。有两种方法可以使用:typeof函数与constructor属性。

typeof函数的用法可能不用我多说,大家都知道怎么用。而constructor属性大家可能就陌生点。在《精通JavaScript》这本书中有提到construct的用法,但我用自己的几个浏览器(IE7.0 / Firefox1.9 / Opera9.50)测试的结果却和书上说的不一样。但是仍然是有办法通过constructor属性来检查变量类型的。
这里先补充一下,为什么明明有typeof函数可以很方便地用来检测类型,还要用constructor呢?
因为typeof会把所有的数组类型以及用户自定义类型判断为object,从而无法知道更确切的信息。而constructor却可以解决这个问题。

ok,明白了我们为什么要用constructor,现在让我带大家一步步认识一下typeof和constructor用法之间的差异吧~

首先我们运行一下下面这段代码:

var i;
alert(
typeof(i));
alert(i.constructor);


这3行代码告诉你什么情况下可以用constructor。
你可以看到第2行返回了字符串'undefined',而第三行则发生了错误,原因是i变量还没有类型定义,自然也没有constructor的存在。
从这一点上看,typeof可以检查到变量是否有定义,而construct只能检查已定义变量的类型

再运行一下下面这段代码:

var i = 2;
alert(
typeof(i));
alert(i.constructor);
alert(
typeof(i.constructor));


你会看到第2行返回了字符串'number’,第3行返回了一串类似函数定义的代码字符串(这就是跟《精通JavaScript》一书中介绍的不一样的地方)。
我们再用typeof检查一下constructor到底是个什么样类型的属性,第4行返回结果'function',也就是说,实际上constructor是一个函数,更确切地说是一个构造函数。这时你就可以知道,为什么constructor可以检查出各种类型了。

有经验的程序员看到这里应该知道要怎么利用constructor来检查变量类型了。方法有多种,这里提供一种比较容易理解的方法。

其实想法很简单,就是把construcor转化为字符串,通过寻找匹配字符串(function名)来确定是否指定类型。如下例子:

function user() {};
var i = new user();
alert((i.constructor
+'').match(/user/== null);


这仅仅是个简单的例子。如果返回true则变量i不是user类型,返回false则变量是user类型。
当然,这样检测是不够精确的,比如其实他是一个myuser类型的时候,同样会被认为是user类。所以你需要书写更精确的正则表达式去进行匹配。

可以这样简单改进你的正则表达式:

/function user\(\)/


替换上面代码段中的/user/。当然,如果你的构造函数原型是user(a),那么应该这样书写你的正则表达式:

/function user\(a\)/



到这里你应该知道怎样使用constructor类型去检查变量类型了吧?

ok,最后再提个醒,如果你要用基于constructor的方法去检查一些基本类型,如
Object / Array / Function / String / Number / Boolean
在你的正则表达式中,一定要将这些单词的首字母大写!!而如果该类型是自定义类型,则根据你定义的时候标识符的写法确定。

posted @ 2010-04-14 14:30 J2EE学习笔记 阅读(314) | 评论 (0)编辑 收藏
/**  
使用三种Callback接口作为参数的query方法的返回值不同:   
以ResultSetExtractor作为方法参数的query方法返回Object型结果,要使用查询结果,我们需要对其进行强制转型;   
以RowMapper接口作为方法参数的query方法直接返回List型的结果;   
以RowCallbackHandler作为方法参数的query方法,返回值为void;  
RowCallbackHandler和RowMapper才是我们最常用的选择   
 * 
@author Administrator  
 *   
 
*/
  
public class SpringTest {   
 
/**  
  * 返回结果是List里装Map,使用参数,使用回调 RowMapperResultSetExtractor用于处理单行记录,  
  * 它内部持有一个RowMapper实例的引用,当处理结果集的时候, 会将单行数据的处理委派给其所持有的RowMapper实例,而其余工作它负责  
  
*/
  
 
public void getListRowMapperResultSetExtractor() {   
  ApplicationContext context 
= new FileSystemXmlApplicationContext(   
    
"src/database_config.xml");   
  
// E:\demoworkspace\spring 为工程主目录   
  JdbcTemplate jt = new JdbcTemplate((DataSource) context   
    .getBean(
"oracleDataSourceTest")); // 测试用的方法   
  Object[] arg = new Object[] 10 };   
  List list 
= (ArrayList) jt.query("select * from region where rownum<?",   
    arg, 
new RowMapperResultSetExtractor(new RowMapper() {   
     
public Object mapRow(ResultSet rs, int index)   
       
throws SQLException {   
      Map u 
= new HashMap(); //可以是自己的JavaBean值对象(简单Java对象POJO)   
      u.put("region_id", rs.getString("region_id"));   
      u.put(
"region_name", rs.getString("region_name"));   
      
return u;   
     }
   
    }
));   
  Iterator it 
= list.iterator();   
  
while (it.hasNext()) {   
   Map map 
= (Map) it.next();   
   System.out.println(map.toString());   
  }
   
 }
   
  
  
 
/**返回结果是List里装Map,不使用参数,使用回调  
  使用RowMapper比直接使用ResultSetExtractor要方便的多,只负责处理单行结果就行,现在,我们只需要将单行的结果组装后返回就行,  
  剩下的工作,全部都是JdbcTemplate内部的事情了。 实际上,JdbcTemplae内部会使用一个ResultSetExtractor实现类来做其余的工作,  
  毕竟,该做的工作还得有人做不是?!    
  
*/
  
 
public void getListRowMapper() {   
  ApplicationContext context 
= new FileSystemXmlApplicationContext(   
    
"src/database_config.xml");   
  JdbcTemplate jt 
= new JdbcTemplate((DataSource) context   
    .getBean(
"oracleDataSourceTest"));   
  List list 
= jt.query(   
    
"select * from region where rownum<10"new RowMapper() {   
     
public Object mapRow(ResultSet rs, int index)   
       
throws SQLException {   
      Map u 
= new HashMap();   
      u.put(
"region_id", rs.getString("region_id"));   
      u.put(
"region_name", rs.getString("region_name"));   
      
return u;   
     }
   
    }
);   
  Iterator it 
= list.iterator();   
  
while (it.hasNext()) {   
   Map map 
= (Map) it.next();   
   System.out.println(map.toString());   
  }
   
 }
   
  
 
// 返回记录集   
 /**  
  RowCallbackHandler虽然与RowMapper同是处理单行数据,不过,除了要处理单行结果,它还得负责最终结果的组装和获取工作,  
  在这里我们是使用当前上下文声明的List取得最终查询结果, 不过,我们也可以单独声明一个RowCallbackHandler实现类,  
  在其中声明相应的集合类,这样,我们可以通过该RowCallbackHandler实现类取得最终查询结果   
  
*/
  
 
public void getListRowCallbackHandler() {   
  ApplicationContext context 
= new FileSystemXmlApplicationContext(   
    
"src/database_config.xml");   
  
  JdbcTemplate jt 
= new JdbcTemplate((DataSource) context   
    .getBean(
"oracleDataSourceTest"));   
  String sql 
= "select * from region  where region_id>?";   
  
final List<Map> list=new ArrayList<Map>(); //一定要用final定义   
  Object[] params = new Object[] 0 };   
  jt.query(sql, params, 
new RowCallbackHandler() {   
   
public void processRow(ResultSet rs) throws SQLException {   
    Map u 
= new HashMap();     
    u.put(
"region_id", rs.getString("region_id"));   
    u.put(
"region_name", rs.getString("region_name"));   
    list.add(u);   
   }
   
  }
);   
     
  Iterator it 
= list.iterator();   
  
while (it.hasNext()) {   
   Map map 
= (Map) it.next();   
   System.out.println(map.toString());   
  }
   
 }
posted @ 2010-03-10 10:27 J2EE学习笔记 阅读(563) | 评论 (0)编辑 收藏
     摘要: 1.springJdbcContext.xml <?xml version="1.0" encoding="UTF-8"?>    <beans xmlns="http://www.springframework.org/schema/beans"      &nb...  阅读全文
posted @ 2010-03-09 19:10 J2EE学习笔记 阅读(2011) | 评论 (0)编辑 收藏

很多朋友在深入的接触JAVA语言后就会发现这样两个词:反射(Reflection)和内省(Introspector),经常搞不清楚这到底是怎么回事,在什么场合下应用以及如何使用?今天把这二者放在一起介绍,因为它们二者是相辅相成的。

反射

相对而言,反射比内省更容易理解一点。用一句比较白的话来概括,反射就是让你可以通过名称来得到对象(类,属性,方法)的技术。例如我们可以通过类名来生成一个类的实例;知道了方法名,就可以调用这个方法;知道了属性名就可以访问这个属性的值。

还是写两个例子让大家更直观的了解反射的使用方法:

// 通过类名来构造一个类的实例
Class cls_str = Class.forName( "java.lang.String" );
// 上面这句很眼熟,因为使用过 JDBC 访问数据库的人都用过 J
Object str = cls_str.newInstance();
// 相当于 String str = new String(); 


// 通过方法名来调用一个方法
String methodName = "length" ;
Method m 
= cls_str.getMethod(methodName, null );
System.out.println( 
"length is " + m.invoke(str, null ));
// 相当于 System.out.println(str.length()); 


上面的两个例子是比较常用方法。看到上面的例子就有人要发问了:为什么要这么麻烦呢?本来一条语句就完成的事情干吗要整这么复杂?没错,在上面的例子中确实没有必要这么麻烦。不过你想像这样一个应用程序,它支持动态的功能扩展,也就是说程序不重新启动但是可以自动加载新的功能,这个功能使用一个具体类来表示。首先我们必须为这些功能定义一个接口类,然后我们要求所有扩展的功能类必须实现我指定的接口,这个规定了应用程序和可扩展功能之间的接口规则,但是怎么动态加载呢?我们必须让应用程序知道要扩展的功能类的类名,比如是test.Func1,当我们把这个类名(字符串)告诉应用程序后,它就可以使用我们第一个例子的方法来加载并启用新的功能。这就是类的反射,请问你有别的选择吗?

内省

内省是Java语言对Bean类属性、事件的一种缺省处理方法。例如类A中有属性name,那我们可以通过getName,setName来得到其值或者设置新的值。通过getName/setName来访问name属性,这就是默认的规则。Java中提供了一套API用来访问某个属性的getter/setter方法,通过这些API可以使你不需要了解这个规则(但你最好还是要搞清楚),这些API存放于包java.beans中。

一般的做法是通过类Introspector来获取某个对象的BeanInfo信息,然后通过BeanInfo来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的getter/setter方法,然后我们就可以通过反射机制来调用这些方法。下面我们来看一个例子,这个例子把某个对象的所有属性名称和值都打印出来:

/* 
 * Created on 2004-6-29
 
*/
 

package demo; 


import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor; 


/**
  * 内省演示例子
  * 
@author liudong
  
*/
 

public class IntrospectorDemo {
    String name;
    
public static void main(String[] args) throws Exception{
        IntrospectorDemo demo 
= new IntrospectorDemo();
        demo.setName( 
"Winter Lau" );         

        
// 如果不想把父类的属性也列出来的话,
        
// 那 getBeanInfo 的第二个参数填写父类的信息
        BeanInfo bi = Introspector.getBeanInfo(demo.getClass(), Object. class );
        PropertyDescriptor[] props 
= bi.getPropertyDescriptors();
        
for ( int i=0;i<props.length;i++){
            System.out.println(props[i].getName()
+ "=" +
                    props[i].getReadMethod().invoke(demo, 
null ));
        }
 

    }
     

    
public String getName() {
        
return name;
    }
 

    
public void setName(String name) {
        
this .name = name;
    }

}


Web开发框架Struts中的FormBean就是通过内省机制来将表单中的数据映射到类的属性上,因此要求FormBean的每个属性要有getter/setter方法。但也并不总是这样,什么意思呢?就是说对一个Bean类来讲,我可以没有属性,但是只要有getter/setter方法中的其中一个,那么Java的内省机制就会认为存在一个属性,比如类中有方法setMobile,那么就认为存在一个mobile的属性,这样可以方便我们把Bean类通过一个接口来定义而不用去关系具体实现,不用去关系Bean中数据的存储。比如我们可以把所有的getter/setter方法放到接口里定义,但是真正数据的存取则是在具体类中去实现,这样可提高系统的扩展性。

总结

将Java的反射以及内省应用到程序设计中去可以大大的提供程序的智能化和可扩展性。有很多项目都是采取这两种技术来实现其核心功能,例如我们前面提到的Struts,还有用于处理XML文件的Digester项目,其实应该说几乎所有的项目都或多或少的采用这两种技术。在实际应用过程中二者要相互结合方能发挥真正的智能化以及高度可扩展性。

posted @ 2010-02-04 13:42 J2EE学习笔记 阅读(366) | 评论 (1)编辑 收藏
     摘要: 原文出处:http://blog.chenlb.com/2008/11/join-or-countdownlatch-make-main-thread-wait-all-sub-thread.html 在编写多线程的工作中,有个常见的问题:主线程(main) 启动好几个子线程(task)来完成并发任务,主线程要等待所有的子线程完成之后才继续执行main的其它任务。 默认主线程退出时其它子线程不...  阅读全文
posted @ 2010-01-26 18:00 J2EE学习笔记 阅读(1198) | 评论 (0)编辑 收藏

这是jQuery里常用的2个方法。
他们2者功能是完全不同的,而初学者往往会被误导。


首先 我们看.find()方法:
现在有一个页面,里面HTML代码为:

<div class="css">
    
<class="rain">测试1</p>
</div>
<div class="rain">
    
<p>测试2</p>
</div>


如果我们使用find()方法:

var $find = $("div").find(".rain");
alert( $find.html() );


将会输出:

如果使用filter()方法:

var $filter = $("div").filter(".rain");
alert( $filter.html() );


将会输出:

也许你已经看出它们的区别了。
find()会在div元素内 寻找 class为rain 的元素。
而filter()则是筛选div的class为rain的元素。
一个是对它的子集操作,一个是对自身集合元素筛选。

另外find()其实还可以用选择器表示:

var $select = $("div .rain");
posted @ 2009-11-09 16:00 J2EE学习笔记 阅读(365) | 评论 (0)编辑 收藏

以前在Windows上ssh登录一直都是用putty,虽然它简单小巧,但毕竟缺少很多特性。今天试了一下SecureCRT,感觉用起来比Putty好多了,但SecureCRT默认的字体超难看,而且中文字体设置也比较麻烦一点,在这里记录一下以后可能还用得到。

  1. 在“会话选项”的“终端->仿真”里面选“Linux”,如果需要显示颜色的话需要把“ANSI颜色”选上
  2. 在“外观->字体”中选择喜欢的字体,但这里对字体是有要求的,只有等宽字体才行。如果要正常显示中文的话,所选择的字体还必须包含中文字符。
  3. 另外就是根据你要登录的主机的字符编码选择字符编码,一般是 “UTF-8″

简单的几步下来就设置好了,如果还有乱码的话就退出然后重新登陆一下。如果你想所有的连接都使用这个默认配置,可以在“全局选项”中设置“默认的会话选项”,这样以后新建的连接会自动应用上面的设置了。

PS:

以前用putty的时候,字体就直接用我在Linux最爱的Monaco,但在SecureCRT中用Monaco字体的话,中文会显示为乱码,这是因为Monaco字体中不包含中文字符,而SecureCRT也不会自动的选择系统默认的中文字体。

为了解决这个问题,我们只要去找一个包含中文的等宽字体来用,我从网上找了一个Consolas和雅黑的混合字体,虽然没有Monaco好看,但效果也还不错。这里有个地方需要注意一下,在选择这个字体的字体选择对话框中,字体的默认字符集是“西方”,需要改成CHINESE_GB2312。

如果你也想用这个字体的话,可以从这里下载。

posted @ 2009-09-14 14:14 J2EE学习笔记 阅读(6032) | 评论 (0)编辑 收藏

这些操作对经常使用hibernate的同学已经很熟悉了,我也经常用但一些细节并不了解,
最近遇到问题才开始有看了一下。

在读完robbin的这两个精华贴的时候,感觉清晰了很多,确实好文章。
http://www.javaeye.com/topic/2712
http://www.javaeye.com/topic/1604?page=1
还有这个精华贴
http://www.javaeye.com/topic/7484
也很不错。

里面总结的很好了,我结合以上三个帖子、自己的试验(版本hibernate-3.0.5)和Hibernate文档也总结了一点,加深理解。希望对刚开始学Hibernate的同学有所帮助

一、saveorUpdate与unsaved-value
到底是sava还是update
Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象还是临时对象。
1).主键Hibernate的id generator产生
<id name="id" type="java.lang.Long">
<column name="ID" precision="22" scale="0" />
<generator class="increment" />
</id>

Project project = new Project();
project.setId(XXX);
this.projectDao.saveOrUpdate(project);

1、默认unsaved-value="null"
主键是对象类型,hebernate判断project的主键是否位null,来判断project是否已被持久化
是的话,对project对象发送save(project),
若自己设置了主键则直接生成update的sql,发送update(project),即便数据库里没有那条记录。
主键是基本类型如int/long/double/
自己设置unsaved-null="0"。
所以这样的话save和update操作肯定不会报错。

2、unsaved-value="none",
由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对project对象发送update(project)

3、unsaved-value="any"
由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对project对象发送save(project),hibernate生成主键。

Hibernate文档中写到
saveOrUpdate()完成了如下工作:
如果对象已经在这个session中持久化过了,什么都不用做
如果对象没有标识值,调用save()来保存它
如果对象的标识值与unsaved-value中的条件匹配,调用save()来保存它
如果对象使用了版本(version或timestamp),那么除非设置unsaved-value="undefined",版本检查会发生在标识符检查之前.
如果这个session中有另外一个对象具有同样的标识符,抛出一个异常

2).主键由自己来赋值
<id name="id" type="java.lang.Long">
<column name="ID" precision="22" scale="0" />
<generator class="assigned" />
</id>

Project project = new Project();
project.setId(XXX);
this.projectDao.saveOrUpdate(project);

1、默认unsaved-value="null"
这时有所不同,hibernate会根据主键产生一个select,来判断此对象是否已被持久化
已被持久化则update,未被持久化则save。
2、unsaved-value="none",update对象,同上

3、unsaved-value="any" ,save对象,
如果自己自己设置的ID在数据库中已存在,则报错。

二、save与update操作
显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的
在同一Session,save没什么可说得
update对象时, 最直接的更改一个对象的方法就是load()它,保持Session打开,然后直接修改即可:
Session s =…
Project p = (Project) sess.load(Project.class, id) );
p.setName(“test”);
s.flush();
不用调用s.update(p);hibernate能察觉到它的变化,会自动更新。当然显示调用的话也不会错

Hibernate文档中写到
update()方法在下列情形下使用:
程序在前面的session中装载了对象
对象被传递到UI(界面)层
对该对象进行了一些修改
对象被传递回业务层
应用程序在第二个session中调用update()保存修改

三、delete操作
删除时直接自己构造一个project即可删除
this.projectDao.delete(preojct);

以前删除我是这样写的
public void deleteProject(String id) {
Project project = (Project) this.projectDao.get(Project.class, id);
if (project != null) {
this.projectDao.delete(project);
}
即这样也是可以的
Project project = new Project();
project.setId(id);
this.projectDao.delete(project).

如果有级联关系,需要把级联的子类也构造出来add进去,同样可以删除。

好了,罗嗦的够多了。

posted @ 2009-08-27 14:44 J2EE学习笔记 阅读(487) | 评论 (0)编辑 收藏
当你显式的使用session.save()或者session.update()操作一个对象的时候,实际上是用不到unsaved-value的。某些情况下(父子表关联保存),当你在程序中并没有显式的使用save或者update一个持久对象,那么Hibernate需要判断被操作的对象究竟是一个已经持久化过的持久对象,是一个尚未被持久化过的内存临时对象。例如:

Session session = ;
Transaction tx 
= ;

Parent parent 
= (Parent); session.load(Parent.class, id);;

Child child 
= new Child();;
child.setParent(parent);;
child.setName(
"sun");;

parent.addChild(child);;
s.update(parent);;

s.flush();;
tx.commit();;
s.close();;

在上例中,程序并没有显式的session.save(child); 那么Hibernate需要知道child究竟是一个临时对象,还是已经在数据库中有的持久对象。如果child是一个新创建的临时对象(本例中就是这种情况),那么Hibernate应该自动产生session.save(child)这样的操作,如果child是已经在数据库中有的持久对象,那么Hibernate应该自动产生session.update(child)这样的操作。

因此我们需要暗示一下Hibernate,究竟child对象应该对它自动save还是update。在上例中,显然我们应该暗示Hibernate对child自动save,而不是自动update。那么Hibernate如何判断究竟对child是save还是update呢?它会取一下child的主键属性 child.getId() ,这里假设id是 java.lang.Integer类型的。如果取到的Id值和hbm映射文件中指定的unsave-value相等,那么Hibernate认为child是新的内存临时对象,发送save,如果不相等,那么Hibernate认为child是已经持久过的对象,发送update。

unsaved-value="null" (默认情况,适用于大多数对象类型主键 Integer/Long/String/...)

Hibernate取一下child的Id,取出来的是null(在上例中肯定取出来的是null),和unsaved-value设定值相等,发送save(child)

Hibernate取一下child的id,取出来的不是null,那么和unsaved-value设定值不相等,发送update(child)

例如下面的情况:

Session session = ;
Transaction tx 
= ;

Parent parent 
= (Parent); session.load(Parent.class, id);;
Child child 
= (Child); session.load(Child.class, childId);;

child.setParent(parent);;
child.setName(
"sun");;

parent.addChild(child);;
s.update(parent);;

s.flush();;
tx.commit();;
s.close();;

child已经在数据库中有了,是一个持久化的对象,不是新创建的,因此我们希望Hibernate发送update(child),在该例中,Hibernate取一下child.getId(),和unsave-value指定的null比对一下,发现不相等,那么发送update(child)。

BTW: parent对象不需要操心,因为程序显式的对parent有load操作和update的操作,不需要Hibernate自己来判断究竟是save还是update了。我们要注意的只是child对象的操作。另外unsaved-value是定义在Child类的主键属性中的。

<class name="Child" table="child">
<id column="id" name="id" type="integer" unsaved-value="null">
  
<generator class="identity"/>
</id>

</class>

如果主键属性不是对象型,而是基本类型,如int/long/double/...,那么你需要指定一个数值型的unsaved-value,例如:

unsaved-value="0"

在此提醒大家,很多人以为对主键属性定义为int/long,比定义为Integer/Long运行效率来得高,认为基本类型不需要进行对象的封装和解构操作,因此喜欢把主键定义为int/long的。但实际上,Hibernate内部总是把主键转换为对象型进行操作的,就算你定义为int/long型的,Hibernate内部也要进行一次对象构造操作,返回给你的时候,还要进行解构操作,效率可能反而低也说不定。因此大家一定要扭转一个观点,在Hibernate中,主键属性定义为基本类型,并不能够比定义为对象型效率来的高,而且也多了很多麻烦,因此建议大家使用对象型的Integer/Long定义主键。

unsaved-value="none"和
unsaved-value="any"

主主要用在主键属性不是通过Hibernate生成,而是程序自己setId()的时候。

在这里多说一句,强烈建议使用Hibernate的id generator,或者你可以自己扩展Hibernate的id generator,特别注意不要使用有实际含义的字段当做主键来用!例如用户类User,很多人喜欢用用户登陆名称做为主键,这是一个很不好的习惯,当用户类和其他实体类有关联关系的时候,万一你需要修改用户登陆名称,一改就需要改好几张表中的数据。偶合性太高,而如果你使用无业务意义的id generator,那么修改用户名称,就只修改user表就行了。

由这个问题引申出来,如果你严格按照这个原则来设计数据库,那么你基本上是用不到手工来setId()的,你用Hibernate的id generator就OK了。因此你也不需要了解当

unsaved-value="none"和
unsaved-value="any"

究竟有什么含义了。如果你非要用assigned不可,那么继续解释一下:

unsaved-value="none" 的时候,由于不论主键属性为任何值,都不可能为none,因此Hibernate总是对child对象发送update(child)

unsaved-value="any" 的时候,由于不论主键属性为任何值,都肯定为any,因此Hibernate总是对child对象发送save(child)

大多数情况下,你可以避免使用assigned,只有当你使用复合主键的时候不得不手工setId(),这时候需要你自己考虑究竟怎么设置unsaved-value了,根据你自己的需要来定。

BTW: Gavin King强烈不建议使用composite-id,强烈建议使用UserType。

因此,如果你在系统设计的时候,遵循如下原则:

1、使用Hibernate的id generator来生成无业务意义的主键,不使用有业务含义的字段做主键,不使用assigned。

2、使用对象类型(String/Integer/Long/...)来做主键,而不使用基础类型(int/long/...)做主键

3、不使用composite-id来处理复合主键的情况,而使用UserType来处理该种情况。


那么你永远用的是unsaved-value="null" ,不可能用到any/none/..了。
posted @ 2009-08-17 22:41 J2EE学习笔记 阅读(938) | 评论 (0)编辑 收藏
问题的提出:
如果我们编译运行下面这个程序会看到什么?

public class Test {
    
public static void main(String args[]) {
        
        System.out.println(
0.05 + 0.01);
        System.out.println(
1.0 - 0.42);
        System.out.println(
4.015 * 100);
        System.out.println(
123.3 / 100);
        
    }

}

你没有看错!结果确实是

0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。现在终于理解为什么要有BCD码了。
这个问题相当严重,如果你有9.999999999999元,你的计算机是不会认为你可以购买10元的商品的。
在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。现在让我们看看如何解决这个问题。

四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能象这样(保留两位):
public double round(double value) {
        
return Math.round(value * 100/ 100.0;
    }

非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
java.text.DecimalFormat也不能解决这个问题:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
输出是4.02

BigDecimal
在《Effective Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。BigDecimal一共有4个够造方法,我们不关心用BigInteger来够造的那两个,那么还有两个,它们是:

BigDecimal(double val)
          Translates a double into a BigDecimal.
BigDecimal(String val)
          Translates the String repre sentation of a BigDecimal into a BigDecimal.

上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段:
Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1) is exactly equal to .1, but it is actually equal to .1000000000000000055511151231257827021181583404541015625. This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1, as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one.
原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。

解决方案
现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类Arith来简化操作。它提供以下静态方法,包括加减乘除和四舍五入:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
附录
源文件Arith.java:
import java.math.BigDecimal;

public class Arith {
    
//默认除法运算精度
    private static final int DEF_DIV_SCALE = 10;

    
//这个类不能实例化
    private Arith()
    
{
        ;
    }

    
/**
    * 提供精确的加法运算。
    * 
@param v1 被加数
    * 
@param v2 加数
    * 
@return 两个参数的和
    
*/

    
public static double add(double v1,double v2)
    
{
        BigDecimal b1 
= new BigDecimal(Double.toString(v1));
        BigDecimal b2 
= new BigDecimal(Double.toString(v2));
        
return b1.add(b2).doubleValue();
    }

    
/**
    * 提供精确的减法运算。
    * 
@param v1 被减数
    * 
@param v2 减数
    * 
@return 两个参数的差
    
*/

    
public static double sub(double v1,double v2){
        BigDecimal b1 
= new BigDecimal(Double.toString(v1));
        BigDecimal b2 
= new BigDecimal(Double.toString(v2));
        
return b1.subtract(b2).doubleValue();
    }

    
/**
    * 提供精确的乘法运算。
    * 
@param v1 被乘数
    * 
@param v2 乘数
    * 
@return 两个参数的积
    
*/

    
public static double mul(double v1,double v2)
    
{
        BigDecimal b1 
= new BigDecimal(Double.toString(v1));
        BigDecimal b2 
= new BigDecimal(Double.toString(v2));
        
return b1.multiply(b2).doubleValue();
    }

    
/**
    * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
    * 小数点以后10位,以后的数字四舍五入。
    * 
@param v1 被除数
    * 
@param v2 除数
    * 
@return 两个参数的商
    
*/

    
public static double div(double v1,double v2)
    
{
        
return div(v1,v2,DEF_DIV_SCALE);
    }

    
/**
    * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
    * 定精度,以后的数字四舍五入。
    * 
@param v1 被除数
    * 
@param v2 除数
    * 
@param scale 表示表示需要精确到小数点以后几位。
    * 
@return 两个参数的商
    
*/

    
public static double div(double v1,double v2,int scale)
    
{
        
if(scale<0)
        
{
            
throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }

        BigDecimal b1 
= new BigDecimal(Double.toString(v1));
        BigDecimal b2 
= new BigDecimal(Double.toString(v2));
        
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    
/**
    * 提供精确的小数位四舍五入处理。
    * 
@param v 需要四舍五入的数字
    * 
@param scale 小数点后保留几位
    * 
@return 四舍五入后的结果
    
*/

    
public static double round(double v,int scale)
    
{
        
if(scale<0)
        
{
            
throw new IllegalArgumentException("The scale must be a positive integer or zero");
        }

        BigDecimal b 
= new BigDecimal(Double.toString(v));
        BigDecimal one 
= new BigDecimal("1");
        
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
    }

}
posted @ 2009-05-07 11:33 J2EE学习笔记 阅读(1196) | 评论 (0)编辑 收藏