ibatis--部分更新表记录字段的方法
使用ibatis,如果要更新表记录,一般常用的做法就是,查找出记录,然后修改部分字段,进行update操作.以member表为例:
MemberDO member
=
memberDAO.findById(
1
);
member.setName( " stone " );
memberDAO.update(member);
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);
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字段就无法保存到数据库中.试想一下,任何需要更新字段的地方,都存在拼写错误的风险.
map.put( " nama " , " stone " );
memberDAO.update(map);
于是乎,人们又想到了参数类,比如就把MemberDO当成参数类:
MemberDO memberParam
=
new
MemberDO();
memberParam.setName( " stone " );
memberDAO.update(memberParam);
sqlmap.xml如下:
memberParam.setName( " stone " );
memberDAO.update(memberParam);
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一下就可以.
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#
但是,如果要把某个字段设置为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.
< isNotNull property ="loginId" >
<![CDATA[
login_id = #loginId#
]]>
</ isNotNull >
< isNull property ="loginId" >
<![CDATA[
login_id = null
]]>
</ isNull >
</ isPropertyAvailable >
看来只有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;
}
}
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);
}
}
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 < T extends BaseDO > T getSetterInstance(Class < T > clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(setterInterceptor);
return (T) enhancer.create();
}
}
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();
}
}
那么对于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());
}
}
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 >
<![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;
-- 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;
特别说明:
此方法原创作者为公司同事,本文只是盗用了他的创意.