副标题#e#
需求:
(1)提取ResultSet中值到一个类中挺贫苦的,需要new一个变量,利用对应成员的Set要领赋值,能不能按照类的Class,直接提取出ResultSet中的数据,最后返回实例化的类?
(2)可以用PreparedStatement预编译带变量的sql语句,在execute之前需要将变量值填充进去,一个一个配置太贫苦了,能不能提供一个类变量,利用类成员变量的值自动填充PreparedStatement?
这样的成果很多开源的框架可以实现,可是不想因为这么一点点的需求去进修那么复杂的一套框架,于是本身实现了一下,总结下本身的实现思路。
实现这套框架下面两个问题是必需思量到的:
数据库表的字段名称大概和类的成员名称纷歧样。举个例子数据库表中常常有这样的定名modify_time,在类中很少会利用下划线,一般倾向于modifyTime这样的定名方法。
预编译的sql语句中变量的顺序和类中成员变量的顺序大概会差异。
怎么将类成员的值对应到预编译好的PreparedStatement中对应的变量上。
办理问题1和问题2的要领是为类成员提供别名,利用java中的Annotation(标注)机制,标注接口如下:
1 @Target(ElementType.FIELD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface Column {
4 String value();
5 }
标注的例子:
public class Work extends WorkAbstraction{ @Column(value = "coop_type") private Integer coopType; private String subtitle; private String coworker; private String tutor; @Column(value = "create_time") private String createTime; @Column(value = "modify_time") private String modifyTime; private String content; }
在数据库中有一个work表,有一个字段名是modify_time,对应实体类Work中相应字段被定名为modifyTime,利用@Column(value = "modify_time")为属性modifyTime提供别名modify_time,对付没有别名的属性,默认其和ResultSet中的列名称沟通。
仅仅提供别名是不足的,从ResultSet中可以获取到查询功效的列名称字段,此时假如想要将ResultSet中的数据填充到某个class中,还需要一个数据布局:
HashMap<Class<?>,HashMap<String,Field>> mappingPools;
HashMap的主键是数据类的Class,HashMap的Value照旧一个Map,HashMap<String,Field>,该Map的主键是类中成员变量的别名,value是java反射范例Field,操作java反射机制就可以很容易通过Field向数据类填凑数据。
接下来的问题就是已知class,怎么理会出上面的HashMap布局,代码如下:
public class BeanMappingPool { private static Lock lock = new ReentrantLock(); private static HashMap<Class<?>,HashMap<String,Field>> mappingPools; static{ mappingPools = new HashMap<Class<?>,HashMap<String,Field>>(); }; public static HashMap<String,Field> GetFieldsMap(Class<?> objClass){ if(mappingPools.get(objClass) != null) return mappingPools.get(objClass); lock.lock(); if(mappingPools.get(objClass) != null){ lock.unlock(); return mappingPools.get(objClass); } HashMap<String,Field> pools = new HashMap<String,Field>(); for(;objClass != Object.class; objClass = objClass.getSuperclass()) for(Field f: objClass.getDeclaredFields()){ f.setAccessible(true); Column col = f.getAnnotation(Column.class); if(col != null) pools.put(col.value(), f); else pools.put(f.getName(), f); } mappingPools.put(objClass, pools); lock.unlock(); return pools; } }
#p#副标题#e#
GetFieldsMap函数从上面的Work中理会出别名和Field的对应干系如下:
{ coop_type= Integer Work.coopType, work_group= String WorkAbstraction.workGroup, content= String Work.content, id= Long WorkAbstraction.id, author= Long WorkAbstraction.author, title= String WorkAbstraction.title, school= String WorkAbstraction.school, tutor= String Work.tutor, name= String WorkAbstraction.name, subtitle= String Work.subtitle, create_time= String Work.createTime, grade= String WorkAbstraction.grade, coworker= String Work.coworker, audit_status= Integer WorkAbstraction.auditStatus, modify_time= String Work.modifyTime, activity_type= Integer WorkAbstraction.activityType }
左边是类成员的别名,右边是类成员的Field反射范例。
阅读GetFieldsMap函数需要留意要理会的类大概有担任条理,理会时需要迭代向上理会。
将ResultSet中的数据填充到数据类中的例子如下第32行到58行。
public List<Object> query(String preparedSql, Class<?> objCls,Object[] params) { PreparedStatement pstmt = null; ResultSet reSet = null; try { pstmt = conn.prepareStatement(preparedSql); } catch (SQLException e) { e.printStackTrace(); return null; } if(params != null){ try { for (int i = 0; i < params.length; ++i) { if (params[i] != null) pstmt.setObject(i + 1, params[i]); else { Class cls = params[i].getClass(); if (cls.getSimpleName().compareToIgnoreCase("Integer") == 0) pstmt.setNull(i + 1, Types.INTEGER); else if (cls.getSimpleName().compareToIgnoreCase("Long") == 0) pstmt.setNull(i + 1, Types.BIGINT); else if (cls.getSimpleName().compareToIgnoreCase("String") == 0) pstmt.setNull(i + 1, Types.VARCHAR); else pstmt.setNull(i + 1, Types.OTHER); } } } catch (Exception e) { logger.error("[Session.query] " + e.getMessage(),e); } } List<Object> res = new ArrayList<Object>(); try { reSet = pstmt.executeQuery(); List<String> colNames = DBUtil.GetColumnNameSet(reSet); HashMap<String, Field> fieldsMap = BeanMappingPool .GetFieldsMap(objCls); while (reSet.next()) { Object obj = objCls.newInstance(); for (String colName : colNames) { Field field = fieldsMap.get(colName); if (field != null){ Object value = null; Class cls = field.getType(); if (cls.getSimpleName().compareToIgnoreCase("Integer") == 0) value = reSet.getInt(colName); else if (cls.getSimpleName().compareToIgnoreCase("Long") == 0) value = reSet.getLong(colName); else if (cls.getSimpleName().compareToIgnoreCase("String") == 0) value = reSet.getString(colName); else value = reSet.getObject(colName); field.set(obj, value); } } res.add(obj); } } catch (Exception e) { // TODO Auto-generated catch block logger.error("[Session.query] " + e.getMessage(),e); } DBUtil.free(null, pstmt, reSet); return res; }
这里讲清楚了,接下来讲讲怎么将类成员的值填充到预编译好的PreparedStatement中。
#p#分页标题#e#
问题3是怎么将类成员的值对应到预编译好的PreparedStatement中对应的变量上,这里回收的要领是简朴界说了下sql的方言,例子如下:
UPDATE collective_user SET name=?name, sex=?sex, birthdate=?birthdate, professional_title=?professional_title, subject=?subject, forte=?forte, unit=?unit, city=?city, county=?county, zip_code=?zip_code, contacts=?contacts, address=?address, comment=?comment, modify_time=?modify_time WHERE id=?id;
new PreparedStatement工具时,一般环境下带变量的sql语句直接雷同name=?就可以了,可是为了办理问题3,需要变革成雷同name=?name的形式,问号后头是类成员变量的别名,也就是数据库表中字段的名称。
利用数据类填充PreparedStatement还需要一个数据布局的辅佐:
HashMap<String,Pair<String,List<String>>> sqlMappingPools;
HashMap的key是方言版本的sql,value是一个数据对Pair,Pair的left是理会方言版本的sql获得的类型的sql,right是预编译好的PreparedStatement中变量名称列表,此列表中的变量名称是凭据sql中变量的呈现顺序排好序的。
上面的update语句理会完成后获得的名目Map如下:
< UPDATE collective_user SET name=?,sex=?,birthdate=?,professional_title=?,subject=?, forte=?,unit=?,city=?,county=?,zip_code=?,contacts=?,address=?, comment=?,modify_time=? WHERE id=?; [name, sex, birthdate, professional_title, subject, forte, unit, city, county, zip_code, contacts, address, comment, modify_time, id] >
可以看出上面是类型化的update语句,下面是update语句中对应的变量。
大概会有一个疑问,为什么问号后头的名称和等号前面的一样还需要出格在问号后头标出来?
有些环境下固然问号呈现了,可是并不知道问号对应的数据库表中的字段的名称,好比下面的sql语句:
insertinto a_tab values(?,?);
上面的sql方言办理了这个问题。
#p#分页标题#e#
理会sql方言的代码如下:
public class PreparedSqlMappingPool { private static HashMap<String,Pair<String,List<String>>> sqlMappingPools; private static Lock lock = new ReentrantLock(); static{ sqlMappingPools = new HashMap<String,Pair<String,List<String>>>(); }; public static Pair<String,List<String>> GetSqlMap(String sql){ if(sqlMappingPools.get(sql) != null) return sqlMappingPools.get(sql); lock.lock(); if(sqlMappingPools.get(sql) != null){ lock.unlock(); return sqlMappingPools.get(sql); } List<String> params = new ArrayList<String>(); StringBuilder strb = new StringBuilder(); boolean append = true; String param = ""; for(char c: sql.toCharArray()){ if(c == '?'){ strb.append("?"); append = false; param = ""; } else if(!append && c == '`') continue; else if(!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') && !append){ strb.append(c); append = true; params.add(param); param = ""; } else if(append){ strb.append(c); } else param += c; } if(!append && param != "") params.add(param); Pair<String,List<String>> pair = Pair.of(strb.toString(), params); sqlMappingPools.put(sql, pair); lock.unlock(); return pair; } }
将类成员变量填充到PreparedStatement中的代码如下:
public boolean save(Object obj, String preparedSql) { Pair<String, List<String>> sqlMap = PreparedSqlMappingPool .GetSqlMap(preparedSql); HashMap<String, Field> fields = BeanMappingPool.GetFieldsMap(obj .getClass()); PreparedStatement pstmt = null; boolean res = true; try { pstmt = conn.prepareStatement(sqlMap.left); } catch (SQLException e) { logger.error("[Session.save] " + e.getMessage(),e); return false; } int index = 1; for (String param : sqlMap.right) { Field f = fields.get(param); if (f == null) continue; Object ob = null; try { ob = f.get(obj); } catch (IllegalArgumentException e) { logger.error("[Session.save] " + e.getMessage(),e); } catch (IllegalAccessException e) { e.printStackTrace(); } try { if (ob != null) { pstmt.setObject(index, ob); } else { Class cls = f.getType(); if (cls.getSimpleName().compareToIgnoreCase("Integer") == 0) pstmt.setNull(index, Types.INTEGER); else if (cls.getSimpleName().compareToIgnoreCase("Long") == 0) pstmt.setNull(index, Types.BIGINT); else if (cls.getSimpleName().compareToIgnoreCase("String") == 0) pstmt.setNull(index, Types.VARCHAR); else pstmt.setNull(index, Types.OTHER); } } catch (SQLException e) { logger.error("[Session.save] " + e.getMessage(),e); } ++index; } try { pstmt.execute(); } catch (SQLException e) { res = false; logger.error("[Session.save] " + e.getMessage(),e); } DBUtil.free(null, pstmt, null); return res; }
领略上述代码需要留意的是java反射范例Field不仅可以用于填充类实例,也可以用于从类实例中取出成员数据。