您好, 在实际工作中我们的表一般都是下划线隔开多个单词,而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的时候的策略。