利用方法拦截器优化Ibatis更新策略 —— 基于POJO、CGLIB、SPRING AOP
如何让CRUD来得更优雅些?前几天整理代码时,发现一位已离职的同事写的一段代码很有意思,学习研究之后整理出一片文档,供大家参考。
最后,祝开卷有益。
我们程序员在使用Ibatis开发过程中,往往会遇到多种不同条件下更新记录的情况,考虑以下场景,CMSTask是CMS系统中一个用于表示一次页面分发操作的POJO,它包含以下字段:
CmsTask task = TaskDAO.queryCmsTaskByPageId(pageId);task.setGmtPulish(currentTime + interval);TaskDAO.updateTask(task)
而你的sqlmap-mapping-CmsTask.xml很可能是这样:
<update id = "UPDATE_CMS_TASK_BY_ID" parameterMap=“CmsTask”> <![CDATA[ UPDATE cms_task SET gmt_modified = #gmtModified#, PAGE_ID = #pageId#, STATUS = #status#, GMT_PUBLISH = #gmtPublish# WHERE ID = #id#]]></update>
CmsTask task = new CmsTask();task.setGmtPulish(currentTime + interval);TaskDAO.updateTask(task)
<update id = "UPDATE_CMS_TASK_BY_ID" parameterMap=“CmsTask”> <![CDATA[ UPDATE cms_task SET ]]> <dynamic prepend = ""> <isNotNull property = "pageId" prepend = ",">PAGE_ID = #pageId#</isNotNull> <isNotNull property = "status" prepend = ",">STATUS = #status#</isNotNull> <isNotNull property = "gmtPublish" prepend = ",">GMT_PUBLISH = #gmtPublish#</isNotNull> <isNotNull property = "gmtModified" prepend = ",">GMT_MODIFIED = #gmtModified#</isNotNull> </dynamic> <![CDATA[ WHERE ID = #id# ]]></update>
UPDATE cms_task SET GMT_PUBLISH = #gmtPublish# where ID = #id#
)。你或许认为可以用<isNull property = " gmtPublish "> GMT_PUBLISH = null</isNull>来克服这个问题,但是Ibatis如何知道gmtPublish是初始化后为null的,还是task.setGmtPublish(null)的结果呢?如果是前者的情况,不是又陷入了重复刷新的泥潭了吗?m.put(“id”, id);m.put(“gmtPublish”, gmtPublish);TaskDAO.updateTask(m);
<update id = "UPDATE_CMS_TASK_BY_ID" parameterMap=“CmsTask”>
<update id = "UPDATE_CMS_TASK_BY_ID" parameterClass=“java.util.Map”>
。public abstract class AbstractBaseDO{ protected AbstractBaseDO(){}Map settedMap; public Map getSettedMap(){ return settedMap; } public boolean setterInited(){ return settedMap != null; }}public class CmsTask extends AbstractBaseDO
public class DOSetterFactory {private static DOChangeInterceptor interceptor = new DOChangeInterceptor();private DOSetterFactory(){}public static<T extends AbstractBaseDO> T newDOSetter(Class<T> modelClass){Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(modelClass); enhancer.setCallback(interceptor); return (T)enhancer.create();}}public class DOChangeInterceptor implements MethodInterceptor {private static final String SET = "set";public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable{String name = method.getName();//当set方法被调用时,将被设置的属性和需要更新的值自动存储在HashMap中if(obj instanceof AbstractBaseDO && name.startsWith(SET)){AbstractBaseDO model = (AbstractBaseDO)obj;if(model.getSettedMap() == null){model.settedMap = (new HashMap());}//将setGmtPublic转化为gmtPublicString filedNameTMP = method.getName().substring(3);char[] charlist = filedNameTMP.toCharArray(); charlist[0] = Character.toLowerCase(charlist[0]); String filedName = String.valueOf(charlist); if ((args != null) && (args.length == 1)) { model.getSettedMap().put(filedName,args[0]); }}return proxy.invokeSuper(obj, args);}}<update id = "UPDATE_CMS_TASK_BY_ID"> <![CDATA[ UPDATE cms_task SET ]]> <dynamic prepend = ""> <isPropertyAvailable property = "pageId" prepend = ","> <isNotNull property = "pageId">PAGE_ID = #pageId#</isNotNull> <isNull property = "pageId">PAGE_ID = null</isNull> </isPropertyAvailable> <isPropertyAvailable property = "status" prepend = ","> <isNotNull property = "status">STATUS = #status#</isNotNull> <isNull property = "status">STATUS = null</isNull> </isPropertyAvailable> <isPropertyAvailable property = "gmtPublish" prepend = ","> <isNotNull property = "gmtPublish">GMT_PUBLISH = #gmtPublish#</isNotNull> <isNull property = "gmtPublish">GMT_PUBLISH = null</isNull> </isPropertyAvailable><isPropertyAvailable property = "gmtModified" prepend = ","> <isNotNull property = "gmtPublish">GMT_MODIFIED = #gmtModified#</isNotNull> <isNull property = "gmtPublish">GMT_MODIFID= null</isNull> </isPropertyAvailable> </dynamic> <![CDATA[ WHERE ID = #id# ]]> </update>
CmsTask task = DOSetterFactory.newDOSetter(CmsTask.class);
CmsTask task = new CmsTask();
@Retention (RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})public @interface AnyJoinPointAnnotation {}@Aspectpublic class IbatisPartialUpdateAspect { @Pointcut("@within(AnyJoinPointAnnotation)") public void pointcutName(){}; @Before("pointcutName()") public Object performanceUpdate(ProceedingJoinPoint joinpoint) throws Throwable { System.out.println("method called"); Object obj = joinpoint.getTarget(); if(obj instanceof AbstractBaseDO && joinpoint.getSignature().getName().startsWith("set")) { System.out.println("set called"); AbstractBaseDO model = ((AbstractBaseDO)obj); if(model.getSettedMap() == null){ model.settedMap = (new HashMap()); } ((AbstractBaseDO)obj).getSettedMap().put(joinpoint.getSignature().getName().substring(3).toLowerCase(), joinpoint.getArgs()[0]); } return joinpoint.proceed(); }}