stone2083

ibatis--部分更新表记录字段的方法

使用ibatis,如果要更新表记录,一般常用的做法就是,查找出记录,然后修改部分字段,进行update操作.
以member表为例:
MemberDO member = memberDAO.findById(1);
member.setName(
"stone");
memberDAO.update(member);

这种是最常用的方法.不错,在很多应用场景下,这么干,完全没有问题.
但是(往往存在但是),如果member表中存在一个或者多个text(或者blob)字段.难道仅仅为了更新一个name字段,需要重新update那些本不需要更新的text/blob字段吗?

于是乎,人们又想出了一个办法,参数采用map,把需要更新的字段put到map中,
演示代码(省略ibatis的sqlmap文件):
Map<String,Object> map = new HashMap<String,Object>();
map.put(
"name","stone");
memberDAO.update(map);

没错,这种方法不错.需要更新哪些字段,只需要动态put到map中就可以.
但是,对于这种方法,需要调用更新的地方,需要手工维护数据库的字段名,如果在put的时候,一不小心拼错字段名,那么更新操作肯定和你预计的会有差别.
比如上面的代码:
Map<String,Object> map = new HashMap<String,Object>();
map.put(
"nama","stone");
memberDAO.update(map);
不小心把name拼成了nama,那么新的name字段就无法保存到数据库中.试想一下,任何需要更新字段的地方,都存在拼写错误的风险.

于是乎,人们又想到了参数类,比如就把MemberDO当成参数类:
MemberDO memberParam = new MemberDO();
memberParam.setName(
"stone");
memberDAO.update(memberParam);
sqlmap.xml如下:
update member
set gmt_modified = current_date
<dynamic>
<isNotNull property="loginId",prepend=",">
login_id = #loginId#
</isNotNull>
   
<isNotNull property="name",prepend=",">
name = #name#
</isNotNull>
   
</dynamic>
where id = #id# 
这方法貌似不错,不会存在字段名拼写错误的风险.并且需要更新哪些字段,动态set一下就可以.
但是,如果要把某个字段设置为null,那怎么办?那没辙咯...(sqlmap中约定,只有不为null的时候,才更新).

那...那...那怎么办呢?
貌似只有Map才能满足需求嘛...因为sqlmap中有个
"isPropertyAvailable"和"isNull"属性支持.只要配合这两个属性,就能区分需要更新为null,还是不更新保持原字段内容.
sqlmap文件演示:
<isPropertyAvailable property="loginId" prepend=",">
        
<isNotNull property="loginId">
          
<![CDATA[
            login_id = #loginId#
          
]]>
        
</isNotNull>
        
<isNull property="loginId">
          
<![CDATA[
            login_id = null
          
]]>
        
</isNull>
</isPropertyAvailable>
只要map不put loginId,那么更新的时候,就不会更新这个字段,如果map.put("loginId",null),那么就会把loginId更新为null.
看来只有map能胜任.

不是说,使用map,维护字段内容很麻烦嘛.但是好像又只能使用它?
于是乎,又想到了一种思路(也是本文要介绍的一个方法)
通过方法拦截,在设置参数类的时候,把设置的属性值put到map中.(cglib是很胜任这样的场合的)

首先,需要一个BaseDO.java DataObject的基类,仅仅用于维护一份Map对象.
BaseDO.java:
public class BaseDO implements Serializable {

    
private static final long serialVersionUID = -315506079592557582L;

    
private Map<String, Object> setterMap;

    
public synchronized void initSetterMap() {
    
if (setterMap == null) {
        setterMap 
= new HashMap<String, Object>();
    }
    }

    
public Map<String, Object> getSetterMap() {
    
return setterMap;
    }

}

采用Cglib,写一个对set方法的拦截器:
SetterInterceptor.java 用于对截获set操作,把set的对象put到map中
public class SetterInterceptor implements MethodInterceptor {

    
private static final String SET_METHOD = "set";

    @Override
    
public Object intercept(Object obj, Method method, Object[] args,
        MethodProxy proxy) 
throws Throwable {
    
// 拦截DataObject中所有的set方法,把set的属性放入到map中
    if (method.getName().startsWith(SET_METHOD)) {
        
if (obj instanceof BaseDO) {
        BaseDO baseDO 
= (BaseDO) obj;
        baseDO.initSetterMap();
        String attribute 
= StringUtils.substring(method.getName(),
            SET_METHOD.length());
        attribute 
= StringUtils.uncapitalize(attribute);
        
if (args != null && args.length == 1) {
            baseDO.getSetterMap().put(attribute, args[
0]);
        }
        }
    }
    
return proxy.invokeSuper(obj, args);
    }

}

写一个创建Setter的工厂类,用于创建带方法拦截的DataObject对象
public class SetterFactory {

    
private static final SetterInterceptor setterInterceptor = new SetterInterceptor();

    @SuppressWarnings(
"unchecked")
    
public static <extends BaseDO> T getSetterInstance(Class<T> clazz) {
    Enhancer enhancer 
= new Enhancer();
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(setterInterceptor);
    
return (T) enhancer.create();
    }

}

那么对于client调用,就非常简单了.
如:
public class Client {

    
private static final Log log = LogFactory.getLog(Client.class);

    
private static final String APP_CONFIG_FILE = "cn/zeroall/javalab/ibatis/app.xml";

    
public static void main(String[] args) {
    ApplicationContext ctx 
= new ClassPathXmlApplicationContext(
        APP_CONFIG_FILE);
    MemberDAO memberDAO 
= (MemberDAO) ctx.getBean("memberDAO");

    MemberDO setter 
= SetterFactory.getSetterInstance(MemberDO.class);
    setter.setId(
1);
    setter.setLoginId(
"stone1");
    setter.setName(
"stone1");
    memberDAO.updateById(setter);

    MemberDO member 
= memberDAO.findById(1);
    log.info(member.getLoginId());

    }
}

sqlmap文件如下:
<update id="update-by-id" parameterClass="java.util.Map">
    
<![CDATA[
      update member
      set gmt_modified = current_date
    
]]>
    
<dynamic>
      
<isPropertyAvailable property="loginId" prepend=",">
        
<isNotNull property="loginId">
          
<![CDATA[
            login_id = #loginId#
          
]]>
        
</isNotNull>
        
<isNull property="loginId">
          
<![CDATA[
            login_id = null
          
]]>
        
</isNull>
      
</isPropertyAvailable>
      
<isPropertyAvailable property="password" prepend=",">
        
<isNotNull property="password">
          
<![CDATA[
            password = #password#
          
]]>
        
</isNotNull>
        
<isNull property="password">
          
<![CDATA[
            password = null
          
]]>
        
</isNull>
      
</isPropertyAvailable>
      
<isPropertyAvailable property="name" prepend=",">
        
<isNotNull property="name">
          
<![CDATA[
            name = #name#
          
]]>
        
</isNotNull>
        
<isNull property="name">
          
<![CDATA[
            name = null
          
]]>
        
</isNull>
      
</isPropertyAvailable>
      
<isPropertyAvailable property="profile" prepend=",">
        
<isNotNull property="profile">
          
<![CDATA[
            profile = #profile#
          
]]>
        
</isNotNull>
        
<isNull property="profile">
          
<![CDATA[
            profile = null
          
]]>
        
</isNull>
      
</isPropertyAvailable>
    
</dynamic>
    
<![CDATA[
        where id = #id#
    
]]>
  
</update>


一旦采用了Setter对象,那么对于表记录的更新操作,仅仅需要一个sql,就能解决.比较方便.

附件中,把整个演示代码附上,有兴趣的朋友,可以了解下:
采用maven构建,workspace编码采用utf-8.数据库采用pgsql

demo附件

备注:
member表创建sql如下:
-- Table: member

-- DROP TABLE member;

CREATE TABLE member
(
  id serial 
NOT NULL,
  login_id 
character varying(16),
  "password" 
character varying(16),
  "name" 
character varying(32),
  profile 
text,
  gmt_created 
timestamp without time zone,
  gmt_modified 
timestamp without time zone,
  
CONSTRAINT member_pkey PRIMARY KEY (id)
)
WITH (OIDS=FALSE);
ALTER TABLE member OWNER TO javalab;


特别说明:
此方法原创作者为公司同事,本文只是盗用了他的创意.


posted on 2008-06-26 22:46 stone2083 阅读(7868) 评论(2)  编辑  收藏 所属分类: java

Feedback

# re: ibatis--部分更新表记录字段的方法[未登录] 2013-01-29 14:35 JAKE

不错,学习了下  回复  更多评论   

# re: ibatis--部分更新表记录字段的方法 2014-08-31 23:48 yanick

不错  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航: