从ET2开发中领悟到的(续)
1、 设计表时,如果属于同种类型的字符串保存,可以使用分隔符来分隔保存数据,可以避免存储多条记录或多个字段;操作上只需在存储之前加上分隔符,读取时分割一下就可以了。
2、 对于标志之类的字段,如果可以使用0、1表示,那么可以设计在一个字段之类,使用每一位来表示不同的含义,可以利用的函数BITAND(位与运算)来判断标志位. 在存储过程中需要创建一个函数BITOR(位或运算)来设置标志位
create or replace function BITOR(x in number, y in number) return number is
Result number;
begin
Result:=(x + y) - BITAND(x, y);
return(Result);
end BITOR;
3、 对于数字或日期,可以使用trunc方法来截取指定长度的内容,该方法只截取,不做舍入。要做舍入使用round方法;日期天数加减直接使用+、-号就可一了,月份加减使用add_months函数
4、 在实际使用中,经常会有带in的子查询,如where id in (1,2,3)这样的情况,但是如果很多这样的语句在数据库中出现,将引起数据库的大量硬解析与共享池SQL碎片。所以,在实际应用中,可以采用其他方法,将这些in list给绑定起来。
如果需要绑定in list,首先,需要创建两个类型(type):
针对数据类型的
CREATE OR REPLACE TYPE NUMTABLETYPE as table of number;
针对字符串类型的(每个list的单元大小不要超过1000字节)
create or replace type vartabletype as table of varchar2(1000);
然后创建两个相关的函数
数字列表函数
代码:
create or replace function str2numList( p_string in varchar2 ) return numTableType
as
v_str long default p_string || ',';
v_n number;
v_data numTableType := numTableType();
begin
loop
v_n := to_number(instr( v_str, ',' ));
exit when (nvl(v_n,0) = 0);
v_data.extend;
v_data( v_data.count ) := ltrim(rtrim(substr(v_str,1,v_n-1)));
v_str := substr( v_str, v_n+1 );
end loop;
return v_data;
end;
create or replace function str2varList( p_string in varchar2 ) return VarTableType
as
v_str long default p_string || ',';
v_n number;
v_data VarTableType := VarTableType();
begin
loop
v_n :=instr( v_str, ',' );
exit when (nvl(v_n,0) = 0);
v_data.extend;
v_data( v_data.count ) := ltrim(rtrim(substr(v_str,1,v_n-1)));
v_str := substr( v_str, v_n+1 );
end loop;
return v_data;
end;
创建之后,我们就可以采用如下的方式来使用in list的绑定了。如可以采用如下的三种方案
代码:
SELECT /*+ ordered use_nl(a,u) */ id, user_id, BITAND(promoted_type,4) busauth
from table(STR2NUMLIST(:bind0)) a,
bmw_users u
where u.user_id = a.column_value
SELECT /*+ leading(a) */ id, user_id, BITAND(promoted_type,4) busauth
from bmw_users u where user_id in
(select * from table(STR2NUMLIST(:bind0)) a);
SELECT /*+ index(bmw_users UK_BMW_USERS_USERID) */ id, user_id
from bmw_users where user_id in
(SELECT * FROM THE (SELECT CAST(STR2NUMLIST(:bind0) AS NUMTABLETYPE) FROM dual) WHERE rownum<1000)
在如上的方案中,以上语句中的hint提示,是为了稳定执行计划,防止Oracle对in list的错误估计而导致走hash连接。一般建议采用第一种方法,比较简单可靠并且可以指定稳定的计划。但是要求数据库的版本比较高,在老版本中(8i),可能只能采用第三种方法。总的来说,1、2两种方法比3要少6个逻辑读左右
5、 程序中关键地方一定要打印日志,对日志进行分级打印,异常日志统一在业务层最高层进行处理,其他底层异常包装后直接向业务层抛出。日志信息一定要有助于日后错误查找,不要打只打印无用的”系统出错啦”等,而其他有用的信息都没有。
6、 如果是相关的一组常量,则可以考虑设计枚举来进行操作,例如:订单状态,并且将状态相关的操作都封装在枚举中进行操作。颜色等也可以进行类似的封装:
/**
* @author huanggang
* @since 2007-11-13下午05:21:31
*
*/
public enum Color {
Red(1) {// 红色
public Color nextColor() {
return Blue;
}
},
Blue(2) {// 蓝色
public Color nextColor() {
return Black;
}
},
Black(3) {// 黑色
public Color nextColor() {
return Red;
}
};
/**
* 保存实际的值,这个类型可以是Object等其他类型
*/
private int value;
private Color(int value) {
this.value = value;
}
/**
* 得到当前值
*
* @return
*/
public int getValue() {
return this.value;
}
/**
* 当前颜色的下一个颜色
* @return
*/
public abstract Color nextColor();
/**
* 是否是红色,其他颜色都可以这样定义
* @return
*/
public boolean isRed() {
return isRed(this.value);
}
public boolean isRed(int value){
return Red.value==value;
}
}
7、 鉴于网络上对于ibatis的缓存介绍不够详细,特别是对于session的概念没有明确指出,都是人云亦云sqlMapClient相当于一个session,本人阅读了ibatis源代码,得出如下结论:
Ibatis一个sqlMapClient实例对应一个SqlMapExecutorDelegate实例,如果sqlMap被多线程执行,那么会共享这两个实例,ibatis对于每个线程都会创建一个SqlMapSessionImpl实例来表示session这个概念,每个线程的数据库操作方法都在本session内执行,SqlMapExecutorDelegate保存了一个ThreadLocal来保存最大同时能够运行的session数目,也就是配置文件中的maxSessions="800"。一个SqlMapSessionImpl对应ThreadLocal中的一个session.当执行查询操作时,如果有缓存,ibatis会首先根据参数、statement的id、缓存的id等hash一个CacheKey出来,如果该缓存是是可读写缓存,那么将本session也hash进去,然后以这个对象为key查找缓存,由于有了session的特征,这样对于每一个session中查询出的数据的修改都不会影响另一个session的数据。如果该缓存是只读缓存,则不将session的特征hash进去,意思就是说,该sqlmap实例中的所有session都可以共享这一个查询出来的数据,对数据的修改也会造成对另一个session读出数据的影响。
所以,按照这个道理,cache的配置中如果readonly为true,则该sqlMap的所有session共享该实例,如果修改其中一个实例,那么会对其他session造成影响。如果readonly为false,由于该实例只是对该session有效,所以,修改也只会对本session有效。Serialize参数为true并且readonly为false时,将会对缓存数据进行序列化保存,读取时再反序列化。如果多个session访问,则Serialize会返回不反序列化之后的对象(相当于复制的对象,也可以这样理解,处理这一点和readonly为true时不一样,其他处理方式都一样);当readonly为true时,不论是否设置了Serialize,都会忽略该参数的设置,处理的结果和值设置readonly效果一样。
下面是两个关于该属性设置的关键代码,一读就明白了。(红色部分)
cacheModel.java 关键部分
/**
* Get an object out of the cache.
* A side effect of this method is that is may clear the cache if it has not been
* cleared in the flushInterval.
*
* @param key The key of the object to be returned
* @return The cached object (or null)
*/
public Object getObject(CacheKey key) {
synchronized (this) {
if (flushInterval != NO_FLUSH_INTERVAL
&& System.currentTimeMillis() - lastFlush > flushInterval) {
flush();
}
}
Object value = null;
synchronized (getLock(key)) {
value = controller.getObject(this, key);
}
if (serialize && !readOnly && (value != NULL_OBJECT && value != null)) {
try {
ByteArrayInputStream bis = new ByteArrayInputStream((byte[]) value);
ObjectInputStream ois = new ObjectInputStream(bis);
value = ois.readObject();
ois.close();
} catch (Exception e) {
throw new NestedRuntimeException("Error caching serializable object. Be sure you're not attempting to use " +
"a serialized cache for an object that may be taking advantage of lazy loading. Cause: " + e, e);
}
}
synchronized (STATS_LOCK) {
requests++;
if (value != null) {
hits++;
}
}
return value == NULL_OBJECT ? null : value;
}
/**
* Add an object to the cache
*
* @param key The key of the object to be cached
* @param value The object to be cached
*/
public void putObject(CacheKey key, Object value) {
if (null == value) value = NULL_OBJECT;
if (serialize && !readOnly && value != NULL_OBJECT) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
oos.close();
value = bos.toByteArray();
} catch (IOException e) {
throw new NestedRuntimeException("Error caching serializable object. Cause: " + e, e);
}
}
synchronized (getLock(key)) {
controller.putObject(this, key, value);
}
}
CachingStatement.java 关键部分
public CacheKey getCacheKey(RequestScope request, Object parameterObject) {
CacheKey key = statement.getCacheKey(request, parameterObject);
if (!cacheModel.isReadOnly() && !cacheModel.isSerialize()) {
key.update(request.getSession());
}
return key;
}