项目中使用SpringJdbcTemplate来操作数据库,简单方便实用(根据项目需求选择技术),目前用到Oracle10g数据库,由于其操作大文本使用Clob类型,故而研究了下jdbctemplate对clob和Blob的操作。
jdbctemplate对clob和blob的操作使用起来也很简单,网友提供很多实例和代码。例如:
http://hi.baidu.com/sileader/item/0b3335f512378fb731c19999
主要是利用jdbctemplate提供的两个类来操作:LobCreator和LobHandler,具体使用方法可参考该链接。
但是如果某个Bean属性字段太多的话,代码写起来将会很麻烦。是否可以提供一种通用的方法,让每个业务方法操作数据库Clob和Blob时候也可以像操作其他基本数据类型一样,一句代码就完成。例如插入和修改Bean:
public <T> void saveOrUpdate(String sql, T bean) {
namedParameterJdbcTemplate.update(sql, new BeanPropertySqlParameterSource(bean));
}
经过一些尝试和摸索,我使用反射和注解,写了一个通用类,可以操作查询、修改、添加,满足了项目中的需求。
1、定义了一个注解类,用于bean中属性上,主要是提供属性的数据库字段名以及其数据库类型。
/**
* @description
* @author aokunsang
* @date 2013-7-4
*/
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PmcColumn {
/**
* 数据库中字段类型
* @return
*/
int type();
/**
* 数据库中字段名
* @return
*/
String columnName();
/**
* 查询列表时候是否忽略
* @return
*/
boolean ignore() default false;
}
2、定义一个反射的工具类,反射操作属性字段的setter和getter方法注入和获取属性值、
/**
* @description 对bean的反射操作
* @author aokunsang
* @date 2013-7-3
*/
public class BeanUtils {
/**
* @param obj 操作的对象
* @param att 操作的属性
* */
public static Object getter(Object obj, String att) {
try {
Method method = obj.getClass().getMethod("get" + StringUtils.capitalize(att));
return method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 注入数据
* @param obj 类的实例
* @param att 属性名
* @param value 注入数据内容
* @param type 返回的数据类型
*
*/
public static void setter(Object obj, String att, Object value, Class<?> type) {
try {
Method method = obj.getClass().getMethod("set" + StringUtils.capitalize(att), type);
method.invoke(obj, value);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取某个属性字段的ignore信息
* @param field
* @return
*/
public static boolean getPmcColumnIgnore(Field field){
Annotation annotation = field.getAnnotation(PmcColumn.class);
boolean ignore = false;
if(annotation!=null){
ignore = ((PmcColumn)annotation).ignore();
}
return ignore;
}
/**
* 获取某个属性的Type信息
* @param field
* @return
*/
public static int getPmcColumnType(Field field){
Annotation annotation = field.getAnnotation(PmcColumn.class);
int type = Types.VARCHAR;
if(annotation!=null){
type = ((PmcColumn)annotation).type();
}
return type;
}
/**
* 获取某个属性的数据库中字段名
* @param field
* @return
*/
public static String getPmcColumnName(Field field){
Annotation annotation = field.getAnnotation(PmcColumn.class);
String columnName = "";
if(annotation!=null){
columnName = ((PmcColumn)annotation).columnName();
}
return columnName;
}
}
3、定义一个对sql语句的转换类,sql语句进行转变,变成需要的字符串。
如:insert into t1 values(:a,:b,:c) ----> insert into t1(A,B,C) values(?,?,?);
或者update t1 set A=:a,B=:b where C=:c ----> update t1 set A=?,B=? where C=?
/**
* 分割插入、修改的sql语句
* @param sql
* @param columnMaps <属性名,数据库中字段名>
* @return
*/
public static Map<String,List<String>> spQuerysql(String sql,Map<String,String> columnMaps){
if(StringUtils.isEmpty(sql)) return null;
String _sql = sql + " ";
String key = _sql.replaceAll(":(.+?),\\s*", "?,").replaceAll(":(.+?)[)]\\s*", "?)").replaceAll(":(.+?)\\s+", "? ");
Map<String,List<String>> result = new HashMap<String, List<String>>();
List<String> fieldList = new LinkedList<String>();
Pattern pattern = Pattern.compile(":(.+?)[)|,|\\s*]");
Matcher matcher = pattern.matcher(_sql);
StringBuffer insertString = new StringBuffer();
while(matcher.find()){
String value = matcher.group(1);
fieldList.add(value);
insertString.append(columnMaps.get(value)+ ",");
}
if(fieldList.isEmpty() || key.equals(_sql)) return null;
StringBuffer key_sb = new StringBuffer(key);
if(!key.trim().matches("insert\\s*into\\s*[\\w|_]+?\\(.*?\\)\\s*values.*") && !key.trim().contains("update")){ //判断insert语句是否有数据库字段,比如:insert into t1(ID,NAME)
key_sb.insert(key_sb.indexOf("values")-1, "("+insertString.substring(0,insertString.length()-1)+")");
}
result.put(key_sb.toString(), fieldList);
return result;
}
/**
* 通过查询语句分离出相应信息
* @param sql
* @param clazz
* @return
*/
public static Field[] spSelectSql(String sql,Class<?> clazz){
if(StringUtils.isEmpty(sql) || sql.indexOf("*")!=-1) return null;
Map<String,Field> fieldMap = new HashMap<String,Field>();
List<Field> list = new ArrayList<Field>();
Field[] fields = clazz.getDeclaredFields(); //获取所有属性
for(Field field : fields){
fieldMap.put(BeanUtils.getPmcColumnName(field), field);
}
String[] columnNameArry = sql.substring(sql.toLowerCase().indexOf("select") + 6,sql.toLowerCase().indexOf("from")).replaceAll("\\s", "").split(",");
for(String columnName : columnNameArry){
if(columnName.indexOf(".") != -1){
columnName = columnName.substring(columnName.indexOf(".")+1).trim();
}else{
columnName = columnName.trim();
}
Field field = fieldMap.get(columnName);
if(field==null) throw new PmcRuntimeException(String.format("你提供的SQL语句中的数据库字段[%s],没有在Bean中找到有效的属性值对应。", columnName));
list.add(field);
}
return list!=null ? list.toArray(new Field[]{}) : null;
}
4、定义一个对clob和blob的通用类[[关键类],也可以说是已经存在的Dao类的扩展类。
/**
* @description 增强版数据库操作类[添加对clob和blob的部分操作]
* @author aokunsang
* @date 2013-7-4
*/
public class StGenericDao extends GenericDao {
/**
* 查询单条记录
* @param <T>
* @param sql
* @param clazz
* @param args
* @return
*/
public <T> T _find(String sql,final Class<T> clazz,Object... args){
printSqlInfo(sql,args!=null ? Arrays.toString(args) : "");
final Field[] _fields = Util.spSelectSql(sql, clazz);
try {
return jdbcTemplate.queryForObject(sql,new RowMapper<T>(){
@Override
public T mapRow(ResultSet rs, int rownum) throws SQLException {
T bean = null;
try {
bean = clazz.newInstance();
Field[] fields = _fields!=null ? _fields : clazz.getDeclaredFields(); //获取所有属性
for(Field field : fields){
if(field.getAnnotation(PmcColumn.class)==null) continue;
switch(BeanUtils.getPmcColumnType(field)) {
case Types.CLOB:{
BeanUtils.setter(bean, field.getName(), lobHandler.getClobAsString(rs, BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.BLOB:{
BeanUtils.setter(bean, field.getName(), lobHandler.getBlobAsBytes(rs, BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.TIMESTAMP:{
BeanUtils.setter(bean, field.getName(),rs.getTimestamp(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.DATE:{
BeanUtils.setter(bean, field.getName(),rs.getDate(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.INTEGER:{
BeanUtils.setter(bean, field.getName(),rs.getInt(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.VARCHAR:{
BeanUtils.setter(bean, field.getName(),rs.getString(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.BIGINT:{
BeanUtils.setter(bean, field.getName(),rs.getLong(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.FLOAT:{
BeanUtils.setter(bean, field.getName(),rs.getFloat(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
},args);
} catch (EmptyResultDataAccessException e) {
return null;
} catch (DataAccessException e) {
throw new DaoRuntimeException("----------数据库错误saveOrUpdate()------", e);
}
}
/**
* 查询列表
* @param <T>
* @param sql
* @param clazz
* @param args
* @return
*/
public <T> List<T> _query(String sql,final Class<T> clazz,Object...args){
printSqlInfo(sql,args!=null ? Arrays.toString(args) : ""); //打印分页语句
final Field[] _fields = Util.spSelectSql(sql, clazz);
return jdbcTemplate.query(sql, args, new RowMapper<T>(){
@Override
public T mapRow(ResultSet rs, int rownum) throws SQLException {
T bean = null;
try {
bean = clazz.newInstance();
Field[] fields = _fields!=null ? _fields : clazz.getDeclaredFields(); //获取所有属性
for(Field field : fields){
if(field.getAnnotation(PmcColumn.class)==null) continue;
if(BeanUtils.getPmcColumnIgnore(field)) continue; //是否忽视
switch(BeanUtils.getPmcColumnType(field)) {
case Types.CLOB:{
BeanUtils.setter(bean, field.getName(), lobHandler.getClobAsString(rs, BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.BLOB:{
BeanUtils.setter(bean, field.getName(), lobHandler.getBlobAsBytes(rs, BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.TIMESTAMP:{
BeanUtils.setter(bean, field.getName(),rs.getTimestamp(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.DATE:{
BeanUtils.setter(bean, field.getName(),rs.getDate(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.INTEGER:{
BeanUtils.setter(bean, field.getName(),rs.getInt(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.VARCHAR:{
BeanUtils.setter(bean, field.getName(),rs.getString(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.BIGINT:{
BeanUtils.setter(bean, field.getName(),rs.getLong(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
case Types.FLOAT:{
BeanUtils.setter(bean, field.getName(),rs.getFloat(BeanUtils.getPmcColumnName(field)), field.getType());
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
});
}
/**
* 添加或修改某条记录
* @param <T>
* @param sql
* @param bean
*/
public <T> void _saveOrUpdate(String sql,final T bean){
final Map<String,Integer> columnTypes = new HashMap<String,Integer>();
Map<String,String> columnMaps = new HashMap<String,String>();
Field[] fields = bean.getClass().getDeclaredFields(); //获取所有属性
for(Field field : fields){
columnTypes.put(field.getName(), BeanUtils.getPmcColumnType(field));
columnMaps.put(field.getName(), BeanUtils.getPmcColumnName(field));
}
final Map<String,List<String>> resultMap = Util.spQuerysql(sql,columnMaps);
if(resultMap==null) throw new PmcRuntimeException(String.format("你提供的SQL语句有问题,请详细检查再次尝试运行。SQL:[s%]",sql));
Object[] validSql = resultMap.keySet().toArray(); //改变后的Sql语句
printSqlInfo(sql+"<<----->>"+validSql[0].toString(),""); //打印改变前后的sql语句
final List<String> fieldList = resultMap.get(validSql[0]); //对应插入的bean字段
jdbcTemplate.execute(validSql[0].toString(),new AbstractLobCreatingPreparedStatementCallback(this.lobHandler) {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreater)
throws SQLException, DataAccessException {
for(int i=0,length=fieldList.size();i<length;i++){
String filedValue = fieldList.get(i);
Integer columnType = columnTypes.get(filedValue);
if(columnType==null) throw new PmcRuntimeException(String.format("你提供的SQL语句有中存在不能识别的Bean属性字段,请详细检查再次尝试运行"));
switch (columnType) {
case Types.CLOB:{
lobCreater.setClobAsString(ps, i+1, BeanUtils.getter(bean, filedValue)==null ? null : BeanUtils.getter(bean, filedValue).toString());
break;
}
case Types.BLOB:{
lobCreater.setBlobAsBytes(ps, i+1, BeanUtils.getter(bean, filedValue)==null ? null : (byte[])BeanUtils.getter(bean, filedValue));
break;
}
case Types.TIMESTAMP:{
ps.setTimestamp(i+1, BeanUtils.getter(bean, filedValue)==null ? null : (Timestamp)BeanUtils.getter(bean, filedValue));
break;
}
case Types.DATE:{
ps.setDate(i+1, BeanUtils.getter(bean, filedValue)==null ? null : (Date)BeanUtils.getter(bean, filedValue));
break;
}
case Types.INTEGER:{
ps.setObject(i+1, BeanUtils.getter(bean, filedValue)==null ? null : (Integer)BeanUtils.getter(bean, filedValue), Types.INTEGER);
break;
}
case Types.VARCHAR:{
ps.setString(i+1, BeanUtils.getter(bean, filedValue)==null ? null : BeanUtils.getter(bean, filedValue).toString());
break;
}
case Types.BIGINT:{
ps.setObject(i+1, BeanUtils.getter(bean, filedValue)==null ? null : (Long)BeanUtils.getter(bean, filedValue), Types.BIGINT);
break;
}
case Types.FLOAT:{
ps.setObject(i+1, BeanUtils.getter(bean, filedValue)==null ? null : (Float)BeanUtils.getter(bean, filedValue), Types.FLOAT);
break;
}
}
}
}
});
}
}
如何使用以上方法,需要注意以下2个方面:
其一:如果某个Bean类中包括Clob和Blob字段,那么该Bean的字段类型需要加入PmcColumn注解。如:
/**
* @description
* @author aokunsang
* @date 2013-7-4
*/
public class TDemo implements Serializable{
@PmcColumn(columnName="ID",type=Types.BIGINT)
private Long id;
@PmcColumn(columnName="NAME",type=Types.VARCHAR)
private String name;
@PmcColumn(columnName="DEMO_PIC",type=Types.BLOB,ignore=true)
private byte[] pic; //BLOB类型,属性字段类型为byte[]
@PmcColumn(columnName="CONTENT",type=Types.CLOB,ignore=true)
private String content; //CLOB类型,属性字段类型为String
setter and getter...
}
其二:Service业务方法中注入StGenericdao类,使用其方法即可;当然如果你操作的Bean类没有Clob和Blob类型字段就无需写PmcColumn注解,注入IGenericDao接口也可以。
/**
* @description
* @author aokunsang
* @date 2013-7-4
*/
@Service
public class DemoServiceImpl implements IDemoService{
@Resource(name="genericDao")
private StGenericDao genericDao;
public void saveDemo(TDemo demo){
genericDao._saveOrUpdate("insert into tdemo values(:id,:name,:pic,:content)",demo);
}
}