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 params = new ArrayList();  
  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. }  
  40. 众所周知,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 params = new ArrayList();  
    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 params = new ArrayList();  
    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 params = new ArrayList();  
    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. }  
    150. 获取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 classCache = Collections  
      20.                                                              .synchronizedMap(new WeakHashMap());  
      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  params;  
      88.   
      89.     public SqlContext(StringBuilder sql, String primaryKey, List params) {  
      90.         this.sql = sql;  
      91.         this.primaryKey = primaryKey;  
      92.         this.params = params;  
      93.     }  
      94.   
      95.     //getter setter 略  
      96. }  
      97. 你可能感兴趣的:(Java)