使用ibatis,如果要更新表記錄,一般常用的做法就是,查找出記錄,然后修改部分字段,進(jìn)行update操作.
以member表為例:
MemberDO member = memberDAO.findById(1);
member.setName("stone");
memberDAO.update(member);
這種是最常用的方法.不錯(cuò),在很多應(yīng)用場(chǎng)景下,這么干,完全沒有問題.
但是(往往存在但是),如果member表中存在一個(gè)或者多個(gè)text(或者blob)字段.難道僅僅為了更新一個(gè)name字段,需要重新update那些本不需要更新的text/blob字段嗎?
于是乎,人們又想出了一個(gè)辦法,參數(shù)采用map,把需要更新的字段put到map中,
演示代碼(省略ibatis的sqlmap文件):
Map<String,Object> map = new HashMap<String,Object>();
map.put("name","stone");
memberDAO.update(map);
沒錯(cuò),這種方法不錯(cuò).需要更新哪些字段,只需要?jiǎng)討B(tài)put到map中就可以.
但是,對(duì)于這種方法,需要調(diào)用更新的地方,需要手工維護(hù)數(shù)據(jù)庫的字段名,如果在put的時(shí)候,一不小心拼錯(cuò)字段名,那么更新操作肯定和你預(yù)計(jì)的會(huì)有差別.
比如上面的代碼:
Map<String,Object> map = new HashMap<String,Object>();
map.put("nama","stone");
memberDAO.update(map);
不小心把name拼成了nama,那么新的name字段就無法保存到數(shù)據(jù)庫中.試想一下,任何需要更新字段的地方,都存在拼寫錯(cuò)誤的風(fēng)險(xiǎn).
于是乎,人們又想到了參數(shù)類,比如就把MemberDO當(dāng)成參數(shù)類:
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#
這方法貌似不錯(cuò),不會(huì)存在字段名拼寫錯(cuò)誤的風(fēng)險(xiǎn).并且需要更新哪些字段,動(dòng)態(tài)set一下就可以.
但是,如果要把某個(gè)字段設(shè)置為null,那怎么辦?那沒轍咯...(sqlmap中約定,只有不為null的時(shí)候,才更新).
那...那...那怎么辦呢?
貌似只有Map才能滿足需求嘛...因?yàn)閟qlmap中有個(gè)
"isPropertyAvailable"和"isNull"屬性支持.只要配合這兩個(gè)屬性,就能區(qū)分需要更新為null,還是不更新保持原字段內(nèi)容.
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,那么更新的時(shí)候,就不會(huì)更新這個(gè)字段,如果map.put("loginId",null),那么就會(huì)把loginId更新為null.
看來只有map能勝任.
不是說,使用map,維護(hù)字段內(nèi)容很麻煩嘛.但是好像又只能使用它?
于是乎,又想到了一種思路(也是本文要介紹的一個(gè)方法)
通過方法攔截,在設(shè)置參數(shù)類的時(shí)候,把設(shè)置的屬性值put到map中.(cglib是很勝任這樣的場(chǎng)合的)
首先,需要一個(gè)BaseDO.java DataObject的基類,僅僅用于維護(hù)一份Map對(duì)象.
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,寫一個(gè)對(duì)set方法的攔截器:
SetterInterceptor.java 用于對(duì)截獲set操作,把set的對(duì)象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);
}
}
寫一個(gè)創(chuàng)建Setter的工廠類,用于創(chuàng)建帶方法攔截的DataObject對(duì)象
public class SetterFactory {
private static final SetterInterceptor setterInterceptor = new SetterInterceptor();
@SuppressWarnings("unchecked")
public static <T extends BaseDO> T getSetterInstance(Class<T> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(setterInterceptor);
return (T) enhancer.create();
}
}
那么對(duì)于client調(diào)用,就非常簡(jiǎn)單了.
如:
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對(duì)象,那么對(duì)于表記錄的更新操作,僅僅需要一個(gè)sql,就能解決.比較方便.
附件中,把整個(gè)演示代碼附上,有興趣的朋友,可以了解下:
采用maven構(gòu)建,workspace編碼采用utf-8.數(shù)據(jù)庫采用pgsql
demo附件
備注:
member表創(chuàng)建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;
特別說明:
此方法原創(chuàng)作者為公司同事,本文只是盜用了他的創(chuàng)意.