Spring JdbcTemplate实现通用的泛型dao三:构建动态sql

http://www.dexcoder.com/selfly/article/431

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


来看一个具有代表性的insert语句的构建:

 
 
  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的信息进行了缓存,如果不缓存时间将多耗上几个数量级。

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

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

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

 
 
  1. public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass) throws IntrospectionException

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

 
 
  1. /**
  2. * 类辅助
  3. *
  4. * User: liyd
  5. * Date: 2/12/14
  6. * Time: 10:08 PM
  7. */
  8. public class ClassUtils {
  9. /** 日志对象 */
  10. private static final Logger LOG = LoggerFactory
  11. .getLogger(ClassUtils.class);
  12. /**
  13. * Map keyed by class containing CachedIntrospectionResults.
  14. * Needs to be a WeakHashMap with WeakReferences as values to allow
  15. * for proper garbage collection in case of multiple class loaders.
  16. */
  17. private static final Map<Class, BeanInfo> classCache = Collections
  18. .synchronizedMap(new WeakHashMap<Class, BeanInfo>());
  19. /**
  20. * 获取类本身的BeanInfo,不包含父类属性
  21. *
  22. * @param clazz
  23. * @return
  24. */
  25. public static BeanInfo getSelfBeanInfo(Class<?> clazz) {
  26. try {
  27. BeanInfo beanInfo;
  28. if (classCache.get(clazz) == null) {
  29. beanInfo = Introspector.getBeanInfo(clazz, clazz.getSuperclass());
  30. classCache.put(clazz, beanInfo);
  31. // Immediately remove class from Introspector cache, to allow for proper
  32. // garbage collection on class loader shutdown - we cache it here anyway,
  33. // in a GC-friendly manner. In contrast to CachedIntrospectionResults,
  34. // Introspector does not use WeakReferences as values of its WeakHashMap!
  35. Class classToFlush = clazz;
  36. do {
  37. Introspector.flushFromCaches(classToFlush);
  38. classToFlush = classToFlush.getSuperclass();
  39. } while (classToFlush != null);
  40. } else {
  41. beanInfo = classCache.get(clazz);
  42. }
  43. return beanInfo;
  44. } catch (IntrospectionException e) {
  45. LOG.error("获取BeanInfo失败", e);
  46. throw new MincoderException(e);
  47. }
  48. }
  49. /**
  50. * 初始化实例
  51. *
  52. * @param clazz
  53. * @return
  54. */
  55. public static Object newInstance(Class<?> clazz) {
  56. try {
  57. return clazz.newInstance();
  58. } catch (Exception e) {
  59. LOG.error("根据class创建实例失败", e);
  60. throw new MincoderException(e);
  61. }
  62. }
  63. }

另外创建了对象SqlContext来保存构建后的sql和参数信息,定义如下:

 
 
  1. /**
  2. * 执行sql的上下文内容
  3. *
  4. * User: liyd
  5. * Date: 2/13/14
  6. * Time: 10:40 AM
  7. */
  8. public class SqlContext {
  9. /** 执行的sql */
  10. private StringBuilder sql;
  11. /** 主键名称 */
  12. private String primaryKey;
  13. /** 参数,对应sql中的?号 */
  14. private List<Object> params;
  15. public SqlContext(StringBuilder sql, String primaryKey, List<Object> params) {
  16. this.sql = sql;
  17. this.primaryKey = primaryKey;
  18. this.params = params;
  19. }
  20. //getter setter 略
  21. }

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值