Closed hexian closed 4 years ago
您好, 在实际工作中我们的表一般都是下划线隔开多个单词,而hibernate的投影或者自定义的ResultTransformer一般使用Transformers.aliasToBean(Clazz), 根据数据库列的别名和返回类型的名称相同才能正确赋值, 我觉得可以在您的 com.blinkfox.fenix.jpa.FenixJpaQuery.doCreateQuery(Object[]) 这个方法里面的 this.querySql = this.sqlInfo.getSql(); 对这个sql进行再次处理( 这里 ), 使用durid或者jsqlparse等库对sql进行改写,自动增加下划线转别名的功能, 或者支持传入一个策略类, 可以自定义列明到Java对象字段的转换, 这样就实现了类似MyBatis中的 resultMap 的功能,下面是我参考durid的一个写法:
import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern;
import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectItem; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants;
public abstract class SqlUtils {
public static enum GenAliasSrategy { NONE("直接以列名作为别名"), LOWER_COLUMN_NAME("以列的小写作为别名"), UPPER_COLUMN_NAME("以列的小写作为别名"), CAMEL2UNDERLINE("驼峰转下划线"), UNDERLINE2CAMEL("下划线转驼峰"); private String srategyRemark; private GenAliasSrategy(String srategyRemark) { this.srategyRemark = srategyRemark; } public String getSrategyRemark() { return srategyRemark; } } /** * <pre> * 将传入的sql语句进行处理, 如果这个sql语句的列没有别名, 则根据传入的策略自动加上别名, 如果列本身有别名则不处理有别名的列(依赖 druid 来进行解析, 需要引入依赖包), * 因为是静态解析sql,所以 * 这个需要写程序来展开实际的列, 注: 应该也可以用与查询列的jpql * * 例如: (策略为下划线转驼峰) * 传入的sql: * SELECT d.dept_name deptNameVo, e.emp_id, e.gender FROM t_employee e left join department d ON e.emp_id=d.dept_id+200 * * 处理和sql: * SELECT d.dept_name AS deptNameVo, e.emp_id AS empId, e.gender AS gender FROM t_employee e LEFT JOIN department d ON e.emp_id=d.dept_id+200 * * </pre> * @return 返回改写后的sql语句 */ public static String autoPopulatesColumnAliases(String sql, String dbType, GenAliasSrategy strategy) { if(strategy == null) { strategy = GenAliasSrategy.UNDERLINE2CAMEL; } List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType); // 只能处理一个sql语句 SQLStatement sqlStatement = stmtList.get(0); // 设置访问者 ExportTableAliasVisitor visitor = new ExportTableAliasVisitor(); sqlStatement.accept(visitor); // 获取所有的表(可以改写表名,仅对NativeSql有效) Map<String, SQLTableSource> tableaAliasMap = visitor.getTableaAliasMap(); // 获取所有的列 Map<String, SQLSelectItem> columnItems = visitor.getColumnItems(); Iterator<Entry<String, SQLSelectItem>> iterator = columnItems.entrySet().iterator(); while(iterator.hasNext()) { Entry<String, SQLSelectItem> entry = iterator.next(); // 数据库列名称 String name = entry.getKey(); SQLSelectItem item = entry.getValue(); String alias = item.getAlias(); // 如果没有别名则自动添加别名 if(alias==null || "".equals(alias.trim())) { switch(strategy) { case NONE : { item.setAlias(name); break; } case LOWER_COLUMN_NAME : { item.setAlias(name.toLowerCase()); break; } case UPPER_COLUMN_NAME : { item.setAlias(name.toUpperCase()); break; } case CAMEL2UNDERLINE : { String underLine = camel2UnderLine(name); item.setAlias(underLine); break; } case UNDERLINE2CAMEL : { String camel = underLine2Camel(name); item.setAlias(camel); break; } } } } // 获取处理后的sql语句 String newSql = sqlStatement.toString(); return newSql; } /** * 下划线转驼峰 */ public static String underLine2Camel(String underline){ Pattern pattern = Pattern.compile("[_]\\w"); String camel = underline.toLowerCase(); Matcher matcher = pattern.matcher(camel); while(matcher.find()){ String w = matcher.group().trim(); camel = camel.replace(w, w.toUpperCase().replace("_", "")); } return camel; } /** * 驼峰转下划线 */ public static String camel2UnderLine(String camel) { // 转成小写 String underline = camel.replaceAll("\\B([A-Z])", "_$1").toLowerCase(); return underline; } /** * 仅内部使用 * */ private static class ExportTableAliasVisitor extends SQLASTVisitorAdapter /* MySqlASTVisitorAdapter */ { // 保存数据库的表的别名与表信息的映射 private Map<String, SQLTableSource> tableaAliasMap = new LinkedHashMap<String, SQLTableSource>(); // 保存列的别名与列信息的映射 private Map<String, SQLSelectItem> columnItems = new LinkedHashMap<String, SQLSelectItem>(); @Override public boolean visit(SQLExprTableSource x) { String alias = x.getAlias(); tableaAliasMap.put(alias, x); return true; } @Override public boolean visit(SQLSelectItem item) { SQLExpr expr = item.getExpr(); if(expr instanceof SQLPropertyExpr) { SQLPropertyExpr propExpr = (SQLPropertyExpr) expr; String name = propExpr.getName(); columnItems.put(name, item); } return true; } public Map<String, SQLTableSource> getTableaAliasMap() { return tableaAliasMap; } public Map<String, SQLSelectItem> getColumnItems() { return columnItems; } } public static void main(String[] args) { // 测试自动增加别名性能与功能 String select = "SELECT " + " d.dept_name deptNameVo, " // 有别名的不会自己转换 + " e.emp_id, " // emp_id ==> empId + " e.gender " // gender ==> gender + " FROM t_employee e " + " left join department d " + " ON e.emp_id=d.dept_id+200 "; long start = System.currentTimeMillis(); String newSql = autoPopulatesColumnAliases(select, JdbcConstants.MYSQL, GenAliasSrategy.UNDERLINE2CAMEL); System.out.println(newSql); long end = System.currentTimeMillis(); System.out.println("自动增加别名耗时: "+(end-start)+" 毫秒" ); }
}
我写了另一个半成品,就是使用jpa的@Column注解, 但是name是作为数据库的列名称(最好还是自定义一个注解比较好), 然后写了一个 ResultTransformer
返回的vo定义: import javax.persistence.Column;
import com.ducha.repositories.convert.impl.DateDataConvert; import com.ducha.repositories.dao.annotation.ResultConvertAndTransformer; import com.ducha.repositories.dao.annotation.TypeConvert;
// 标记该类中可能出现需要类型转换的字段 @ResultTransformer public class UserVo {
@Column(name = "id") private Integer id; @Column(name = "username") private String username; @Column(name = "password") private String password; @Column(name = "birthDay") @TypeConvert(converter=DateDataConvert.class, parameter = {"yyyy/MM/dd HH:mm:ss"}) // @TypeConvert 自定义注解 private String birthDay; @Column(name = "entryDate") @TypeConvert(converter=DateDataConvert.class, parameter = {"yyyy-MM-dd HH:mm:ss"}) private String entryDate; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getBirthDay() { return birthDay; } public void setBirthDay(String birthDay) { this.birthDay = birthDay; } public String getEntryDate() { return entryDate; } public void setEntryDate(String entryDate) { this.entryDate = entryDate; }
ResultTransformer定义: import java.lang.reflect.Field; import java.util.LinkedHashMap; import java.util.List; import java.util.Map;
import javax.persistence.Column;
import org.hibernate.transform.ResultTransformer;
public class AnnotationBeanResultTransforme implements ResultTransformer {
private static final long serialVersionUID = 1638820969102403186L; private final Class<?> resultClass; private Map<String, Field> columnFieldMapping = new LinkedHashMap<String, Field>(); public AnnotationBeanResultTransforme(Class<?> resultClass) { this.resultClass = resultClass; try { init(); } catch (Exception e) { e.printStackTrace(); } } @Override public Object transformTuple(Object[] tuple, String[] aliases) { Object result = null; try { result = resultClass.newInstance(); for ( int i = 0; i < aliases.length; i++ ) { String alias= aliases[i]; Field field = columnFieldMapping.get(alias); if(field!=null) { Object columnValue = tuple[i]; field.set(result, columnValue); } } } catch (Exception e) { e.printStackTrace(); } return result; } // 首先进入 transformTuple(Object[], String[]) 进行转换, 然后再进入 transformList(List), 此时集合已经是实体对象了, 直接返回即可 @SuppressWarnings("rawtypes") @Override public List transformList(List list) { return list; } private void init() throws Exception { Class<?> clazz = resultClass; while (clazz != null) { // 获取所有字段,public和protected和private,但是不包括父类字段 Field[] declaredFields = clazz.getDeclaredFields(); for(int idx=0; idx<declaredFields.length; idx++) { Field f = declaredFields[idx]; Column column = f.getAnnotation(Column.class); if(column!=null) { // 数据库列名称 String name = column.name(); if("".equals(name)) { // 如果没有值则取字段的值 name = f.getName(); } if(!columnFieldMapping.containsKey(name)) { f.setAccessible(true); columnFieldMapping.put(name, f); } } } // 访问父类 clazz = clazz.getSuperclass(); } }
这样在 @QueryFenix 新增一个属性或者新增一个新的注解来告诉框架改写sql的时候的策略。
比较独立可行的方案应该是传入一个转换器,在注解或者 XML 中配置下就行,但是感觉这个功能有些鸡肋。而且不同的公司规范,表设计存在较大差异。
您好, 在实际工作中我们的表一般都是下划线隔开多个单词,而hibernate的投影或者自定义的ResultTransformer一般使用Transformers.aliasToBean(Clazz), 根据数据库列的别名和返回类型的名称相同才能正确赋值, 我觉得可以在您的 com.blinkfox.fenix.jpa.FenixJpaQuery.doCreateQuery(Object[]) 这个方法里面的 this.querySql = this.sqlInfo.getSql(); 对这个sql进行再次处理( 这里 ), 使用durid或者jsqlparse等库对sql进行改写,自动增加下划线转别名的功能, 或者支持传入一个策略类, 可以自定义列明到Java对象字段的转换, 这样就实现了类似MyBatis中的 resultMap 的功能,下面是我参考durid的一个写法:
import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern;
import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLExpr; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLSelectItem; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.visitor.SQLASTVisitorAdapter; import com.alibaba.druid.util.JdbcConstants;
public abstract class SqlUtils {
}
我写了另一个半成品,就是使用jpa的@Column注解, 但是name是作为数据库的列名称(最好还是自定义一个注解比较好), 然后写了一个 ResultTransformer
返回的vo定义: import javax.persistence.Column;
import com.ducha.repositories.convert.impl.DateDataConvert; import com.ducha.repositories.dao.annotation.ResultConvertAndTransformer; import com.ducha.repositories.dao.annotation.TypeConvert;
// 标记该类中可能出现需要类型转换的字段 @ResultTransformer public class UserVo {
}
ResultTransformer定义: import java.lang.reflect.Field; import java.util.LinkedHashMap; import java.util.List; import java.util.Map;
import javax.persistence.Column;
import org.hibernate.transform.ResultTransformer;
public class AnnotationBeanResultTransforme implements ResultTransformer {
}
这样在 @QueryFenix 新增一个属性或者新增一个新的注解来告诉框架改写sql的时候的策略。