Java动态拼接SQL--03--JdbcTemple

构建动态sql,其实说白了就是拼装sql语句,在这里我把传入的实体参数,属性有值的拼装进sql,为null的则忽略,要实现这个不用说,肯定要利用Java的反射功能,来看一个具有代表性的insert语句的构建:


 
 
[java] view plain copy
  1. /** 
  2.  * 构建insert语句 
  3.  *  
  4.  * @param entity 实体映射对象 
  5.  * @param nameHandler 名称转换处理器 
  6.  * @return 
  7.  */  
  8. public static SqlContext buildInsertSql(Object entity, NameHandler nameHandler) {  
  9.     Class<?> clazz = entity.getClass();  
  10.     String tableName = nameHandler.getTableName(clazz.getSimpleName());  
  11.     String primaryName = nameHandler.getPrimaryName(clazz.getSimpleName());  
  12.     StringBuilder sql = new StringBuilder("insert into ");  
  13.     List<Object> params = new ArrayList<Object>();  
  14.     sql.append(tableName);  
  15.     //获取属性信息  
  16.     BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(clazz);  
  17.     PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();  
  18.     sql.append("(");  
  19.     StringBuilder args = new StringBuilder();  
  20.     args.append("(");  
  21.     for (PropertyDescriptor pd : pds) {  
  22.         Object value = getReadMethodValue(pd.getReadMethod(), entity);  
  23.         if (value == null) {  
  24.             continue;  
  25.         }  
  26.         sql.append(nameHandler.getColumnName(pd.getName()));  
  27.         args.append("?");  
  28.         params.add(value);  
  29.         sql.append(",");  
  30.         args.append(",");  
  31.     }  
  32.     sql.deleteCharAt(sql.length() - 1);  
  33.     args.deleteCharAt(args.length() - 1);  
  34.     args.append(")");  
  35.     sql.append(")");  
  36.     sql.append(" values ");  
  37.     sql.append(args);  
  38.     return new SqlContext(sql, primaryName, params);  
  39. }  

众所周知,Java的反射是性能较低的,也有性能较好的第三方实现如cglib,这里并没有使用。在我的实测中两者差距不大。

但是注意这里并没有使用属性的操作方式,也就是没有使用jdk反射获取属性的getDeclaredFields()方法,而是使用了BeanInfo和PropertyDescriptor,因为后者的运行效率要远远高于前者。

在我的实测中,构建一个拥有12个属性的JavaBean的动态sql,十万次所耗时间为900毫秒左右,完全可以接受。当然,这里对JavaBean的信息进行了缓存,如果不缓存时间将多耗上几个数量级。

下面顺便贴上完整的代码:


 
 
[java] view plain copy
  1. /** 
  2.  * sql辅助为类 
  3.  *  
  4.  * User: liyd 
  5.  * Date: 2/13/14 
  6.  * Time: 10:03 AM 
  7.  */  
  8. public class SqlUtils {  
  9.   
  10.     /** 日志对象 */  
  11.     private static final Logger LOG = LoggerFactory.getLogger(SqlUtils.class);  
  12.   
  13.     /** 
  14.      * 构建insert语句 
  15.      * 
  16.      * @param entity 实体映射对象 
  17.      * @param nameHandler 名称转换处理器 
  18.      * @return 
  19.      */  
  20.     public static SqlContext buildInsertSql(Object entity, NameHandler nameHandler) {  
  21.         Class<?> clazz = entity.getClass();  
  22.         String tableName = nameHandler.getTableName(clazz.getSimpleName());  
  23.         String primaryName = nameHandler.getPrimaryName(clazz.getSimpleName());  
  24.         StringBuilder sql = new StringBuilder("insert into ");  
  25.         List<Object> params = new ArrayList<Object>();  
  26.         sql.append(tableName);  
  27.   
  28.         //获取属性信息  
  29.         BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(clazz);  
  30.         PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();  
  31.         sql.append("(");  
  32.         StringBuilder args = new StringBuilder();  
  33.         args.append("(");  
  34.         for (PropertyDescriptor pd : pds) {  
  35.             Object value = getReadMethodValue(pd.getReadMethod(), entity);  
  36.             if (value == null) {  
  37.                 continue;  
  38.             }  
  39.             sql.append(nameHandler.getColumnName(pd.getName()));  
  40.             args.append("?");  
  41.             params.add(value);  
  42.             sql.append(",");  
  43.             args.append(",");  
  44.         }  
  45.         sql.deleteCharAt(sql.length() - 1);  
  46.         args.deleteCharAt(args.length() - 1);  
  47.         args.append(")");  
  48.         sql.append(")");  
  49.         sql.append(" values ");  
  50.         sql.append(args);  
  51.         return new SqlContext(sql, primaryName, params);  
  52.     }  
  53.   
  54.     /** 
  55.      * 构建更新sql 
  56.      *  
  57.      * @param entity 
  58.      * @param nameHandler 
  59.      * @return 
  60.      */  
  61.     public static SqlContext buildUpdateSql(Object entity, NameHandler nameHandler) {  
  62.         Class<?> clazz = entity.getClass();  
  63.         StringBuilder sql = new StringBuilder();  
  64.         List<Object> params = new ArrayList<Object>();  
  65.         String tableName = nameHandler.getTableName(clazz.getSimpleName());  
  66.         String primaryName = nameHandler.getPrimaryName(clazz.getSimpleName());  
  67.         //获取属性信息  
  68.         BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(clazz);  
  69.         PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();  
  70.   
  71.         sql.append("update ");  
  72.         sql.append(tableName);  
  73.         sql.append(" set ");  
  74.         Object primaryValue = null;  
  75.         for (PropertyDescriptor pd : pds) {  
  76.             Object value = getReadMethodValue(pd.getReadMethod(), entity);  
  77.             if (value == null) {  
  78.                 continue;  
  79.             }  
  80.             String columnName = nameHandler.getColumnName(pd.getName());  
  81.             if (primaryName.equalsIgnoreCase(columnName)) {  
  82.                 primaryValue = value;  
  83.             }  
  84.             sql.append(columnName);  
  85.             sql.append(" = ");  
  86.             sql.append("?");  
  87.             params.add(value);  
  88.             sql.append(",");  
  89.         }  
  90.         sql.deleteCharAt(sql.length() - 1);  
  91.         sql.append(" where ");  
  92.         sql.append(primaryName);  
  93.         sql.append(" = ?");  
  94.         params.add(primaryValue);  
  95.         return new SqlContext(sql, primaryName, params);  
  96.     }  
  97.   
  98.     /** 
  99.      * 构建查询条件 
  100.      *  
  101.      * @param entity 
  102.      * @param nameHandler 
  103.      */  
  104.     public static SqlContext buildQueryCondition(Object entity, NameHandler nameHandler) {  
  105.         //获取属性信息  
  106.         BeanInfo beanInfo = ClassUtils.getSelfBeanInfo(entity.getClass());  
  107.         //        PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(entityClass);  
  108.         PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();  
  109.         StringBuilder condition = new StringBuilder();  
  110.         List<Object> params = new ArrayList<Object>();  
  111.         int count = 0;  
  112.         for (PropertyDescriptor pd : pds) {  
  113.             Object value = getReadMethodValue(pd.getReadMethod(), entity);  
  114.             if (value == null) {  
  115.                 continue;  
  116.             }  
  117.             if (count > 0) {  
  118.                 condition.append(" and ");  
  119.             }  
  120.             condition.append(nameHandler.getColumnName(pd.getName()));  
  121.             condition.append(" = ?");  
  122.             params.add(value);  
  123.             count++;  
  124.         }  
  125.         return new SqlContext(condition, null, params);  
  126.     }  
  127.   
  128.     /** 
  129.      * 获取属性值 
  130.      * 
  131.      * @param readMethod 
  132.      * @param entity 
  133.      * @return 
  134.      */  
  135.     private static Object getReadMethodValue(Method readMethod, Object entity) {  
  136.         if (readMethod == null) {  
  137.             return null;  
  138.         }  
  139.         try {  
  140.             if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {  
  141.                 readMethod.setAccessible(true);  
  142.             }  
  143.             return readMethod.invoke(entity);  
  144.         } catch (Exception e) {  
  145.             LOG.error("获取属性值失败", e);  
  146.             throw new MincoderException(e);  
  147.         }  
  148.     }  
  149. }  

获取BeanInfo时写了一个ClassUtils来实现,里面对Bean信息进行了缓存。因为项目使用spring,本来想使用spring提供的BeanUtils.getPropertyDescriptor()方法的,里面同样拥有缓存,但是该方法会把实体类父类的属性信息也获取出来,而PropertyDescriptor中又没法判断,这将直接导致拼装sql时字段的错误,因为你不知道哪些字段是操作当前表所需要的。没办法,查看jdk本身的Introspector类,发现里面有如下方法定义:


 
 
[java] view plain copy
  1. public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass) throws IntrospectionException  

即可以指定在哪个类停止获取属性,这正是我们需要的,可惜spring没有进行封装,只能自己实现了,参考了spring的实现,使用WeakHashMap来防止内存的溢出,及时清空Introspector本身的缓存:

[java]  view plain  copy
  1. /** 
  2.  * 类辅助 
  3.  * 
  4.  * User: liyd 
  5.  * Date: 2/12/14 
  6.  * Time: 10:08 PM 
  7.  */  
  8. public class ClassUtils {  
  9.   
  10.     /** 日志对象 */  
  11.     private static final Logger               LOG        = LoggerFactory  
  12.                                                              .getLogger(ClassUtils.class);  
  13.   
  14.     /** 
  15.      * Map keyed by class containing CachedIntrospectionResults. 
  16.      * Needs to be a WeakHashMap with WeakReferences as values to allow 
  17.      * for proper garbage collection in case of multiple class loaders. 
  18.      */  
  19.     private static final Map<Class, BeanInfo> classCache = Collections  
  20.                                                              .synchronizedMap(new WeakHashMap<Class, BeanInfo>());  
  21.   
  22.     /** 
  23.      * 获取类本身的BeanInfo,不包含父类属性 
  24.      *  
  25.      * @param clazz 
  26.      * @return 
  27.      */  
  28.     public static BeanInfo getSelfBeanInfo(Class<?> clazz) {  
  29.         try {  
  30.             BeanInfo beanInfo;  
  31.             if (classCache.get(clazz) == null) {  
  32.                 beanInfo = Introspector.getBeanInfo(clazz, clazz.getSuperclass());  
  33.                 classCache.put(clazz, beanInfo);  
  34.                 // Immediately remove class from Introspector cache, to allow for proper  
  35.                 // garbage collection on class loader shutdown - we cache it here anyway,  
  36.                 // in a GC-friendly manner. In contrast to CachedIntrospectionResults,  
  37.                 // Introspector does not use WeakReferences as values of its WeakHashMap!  
  38.                 Class classToFlush = clazz;  
  39.                 do {  
  40.                     Introspector.flushFromCaches(classToFlush);  
  41.                     classToFlush = classToFlush.getSuperclass();  
  42.                 } while (classToFlush != null);  
  43.             } else {  
  44.                 beanInfo = classCache.get(clazz);  
  45.             }  
  46.             return beanInfo;  
  47.         } catch (IntrospectionException e) {  
  48.             LOG.error("获取BeanInfo失败", e);  
  49.             throw new MincoderException(e);  
  50.         }  
  51.     }  
  52.   
  53.     /** 
  54.      * 初始化实例 
  55.      *  
  56.      * @param clazz 
  57.      * @return 
  58.      */  
  59.     public static Object newInstance(Class<?> clazz) {  
  60.         try {  
  61.             return clazz.newInstance();  
  62.         } catch (Exception e) {  
  63.             LOG.error("根据class创建实例失败", e);  
  64.             throw new MincoderException(e);  
  65.         }  
  66.     }  
  67. }  
  68.   
  69. 另外创建了对象SqlContext来保存构建后的sql和参数信息,定义如下:  
  70.   
  71. /** 
  72.  * 执行sql的上下文内容 
  73.  *  
  74.  * User: liyd 
  75.  * Date: 2/13/14 
  76.  * Time: 10:40 AM 
  77.  */  
  78. public class SqlContext {  
  79.   
  80.     /** 执行的sql */  
  81.     private StringBuilder sql;  
  82.   
  83.     /** 主键名称 */  
  84.     private String        primaryKey;  
  85.   
  86.     /** 参数,对应sql中的?号 */  
  87.     private List<Object>  params;  
  88.   
  89.     public SqlContext(StringBuilder sql, String primaryKey, List<Object> params) {  
  90.         this.sql = sql;  
  91.         this.primaryKey = primaryKey;  
  92.         this.params = params;  
  93.     }  
  94.   
  95.     //getter setter 略  
  96. }  
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值