感谢JFinal的Db.paginate方法,此实现参考之。
我们在JPA环境下,某些查询的SQL可能是动态生成的,查询或者分页我们就需要手动使用EntityManager来查询,参数处理和结果集就需要手动,此工具类就是干这些脏活累活的。废话不多说,直接上代码。
import cn.palmte.gpas.bean.Page;
import org.hibernate.Session;
import org.hibernate.transform.Transformers;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* @author xiongshiyan at 2018/5/9
* @see cn.zytx.common.db.QueryHelper
* @see Record
* SQL语句用{@see QueryHelper}封装。结果集用JavaBean或者{@see Record}来封装。JavaBean需要保证别名就是属性。
* 可以使用{@see QueryHelper}完全处理参数,也可以不处理,支持?和:的方式
*/
public class Pagination {
private EntityManager entityManager;
public Pagination(EntityManager entityManager){
this.entityManager = entityManager;
}
public Pagination(){}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
/**
* 返回查询的一个Record,没有则为null
*/
public Record findFirst(String sql , Object... params){
return findFirst(sql , Record.class , params);
}
public Record findFirst(String sql , Map<String , Object> searchMap){
return findFirst(sql , Record.class , searchMap);
}
/**
* 返回查询的一个实体,没有则为null
*/
public <T> T findFirst(String sql , Class<T> clazz , Object... params){
List<T> ts = find(sql, clazz, params);
return (ts == null || ts.size() == 0) ? null : ts.get(0);
}
public <T> T findFirst(String sql , Class<T> clazz ,Map<String , Object> searchMap){
List<T> ts = find(sql, clazz, searchMap);
return (ts == null || ts.size() == 0) ? null : ts.get(0);
}
public List<Record> find(String sql , Object... params){
return find(sql, Record.class , params);
}
public List<Record> find(String sql , Map<String , Object> searchMap){
return find(sql, Record.class , searchMap);
}
public List<Record> find(String sql){
return find(sql, Record.class , (Map<String , Object>)null);
}
/**
* 查询列表
* @param sql native sql语句,可以包含?
* @param clazz 返回的类型,可以是JavaBean,可以是Record
* @param params 参数列表
* @param <T> 泛型
* @return 查询列表结果
*/
public <T> List<T> find(String sql , Class<T> clazz , Object... params){
Session session = entityManager.unwrap(Session.class);
org.hibernate.Query query = session.createSQLQuery(sql);
//0-Based
for (int i = 0; i < params.length; i++) {
query.setParameter(i , params[i]);
}
List list = getList(query, clazz);
return list;
}
/**
* 查询列表
* @param sql native sql语句,可以包含 :具名参数
* @param clazz 返回的类型,可以是JavaBean,可以是Record
* @param searchMap 具名参数列表
* @param <T> 泛型
* @return 查询列表结果
*/
public <T> List<T> find(String sql , Class<T> clazz , Map<String , Object> searchMap){
Session session = entityManager.unwrap(Session.class);
org.hibernate.Query query = session.createSQLQuery(sql);
if(null != searchMap) {
searchMap.forEach(query::setParameter);
}
List list = getList(query, clazz);
return list;
}
/**----------------------------------------------record-positioned-parameter---------------------------------------------------*/
public Page<Record> paginate( String nativeSQL,int pageNumber, int pageSize, Object... params){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( null, nativeSQL, nativeCountSQL, Record.class,pageNumber, pageSize, params);
}
public Page<Record> paginate( String nativeSQL, Boolean isGroupBySql, int pageNumber, int pageSize, Object... params){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, Record.class, pageNumber, pageSize,params);
}
public Page<Record> paginate( String nativeSQL, String nativeCountSQL, int pageNumber, int pageSize, Object... params){
return paginate( null, nativeSQL, nativeCountSQL, Record.class, pageNumber, pageSize,params);
}
public Page<Record> paginate( Boolean isGroupBySql, String nativeSQL ,String nativeCountSQL ,int pageNumber, int pageSize, Object... params){
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, Record.class,pageNumber, pageSize, params);
}
/**----------------------------------------------record-maped-parameter---------------------------------------------------*/
public Page<Record> paginate( String nativeSQL,int pageNumber, int pageSize, Map<String , Object> searchMap){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( null, nativeSQL, nativeCountSQL, Record.class,pageNumber, pageSize, searchMap);
}
public Page<Record> paginate( String nativeSQL, Boolean isGroupBySql, int pageNumber, int pageSize, Map<String , Object> searchMap){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, Record.class, pageNumber, pageSize,searchMap);
}
public Page<Record> paginate(String nativeSQL, String nativeCountSQL, int pageNumber, int pageSize, Map<String , Object> searchMap){
return paginate( null, nativeSQL, nativeCountSQL, Record.class, pageNumber, pageSize,searchMap);
}
public Page<Record> paginate( Boolean isGroupBySql, String nativeSQL ,String nativeCountSQL ,int pageNumber, int pageSize, Map<String , Object> searchMap){
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, Record.class,pageNumber, pageSize, searchMap);
}
/**----------------------------------------------JavaBean-positioned-parameter---------------------------------------------------*/
public <T> Page<T> paginate( Boolean isGroupBySql, String nativeSQL , Class<T> clazz, int pageNumber, int pageSize,Object... params){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, clazz, pageNumber, pageSize,params);
}
public <T> Page<T> paginate( String nativeSQL ,String nativeCountSQL, Class<T> clazz,int pageNumber, int pageSize, Object... params){
return paginate( null, nativeSQL, nativeCountSQL, clazz, pageNumber, pageSize, params);
}
public <T> Page<T> paginate(String nativeSQL , Class<T> clazz ,int pageNumber, int pageSize, Object... params){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( null, nativeSQL, nativeCountSQL ,clazz ,pageNumber, pageSize, params);
}
/**----------------------------------------------JavaBean-maped-parameter---------------------------------------------------*/
public <T> Page<T> paginate( String nativeSQL , Class<T> clazz ,int pageNumber, int pageSize, Map<String , Object> searchMap){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( null, nativeSQL, nativeCountSQL ,clazz ,pageNumber, pageSize, searchMap);
}
public <T> Page<T> paginate( Boolean isGroupBySql, String nativeSQL , Class<T> clazz,int pageNumber, int pageSize, Map<String , Object> searchMap){
String nativeCountSQL = getCountSQL(nativeSQL);
return paginate( isGroupBySql, nativeSQL, nativeCountSQL, clazz, pageNumber, pageSize,searchMap);
}
public <T> Page<T> paginate( String nativeSQL ,String nativeCountSQL, Class<T> clazz,int pageNumber, int pageSize, Map<String , Object> searchMap){
return paginate( null, nativeSQL, nativeCountSQL, clazz, pageNumber, pageSize, searchMap);
}
/**
*
* @param pageNumber pageNumber
* @param pageSize pageSize
* @param isGroupBySql 是否包含Group by语句,影响总行数
* @param nativeSQL 原生SQL语句 {@see QueryHelper}
* @param nativeCountSQL 原生求总行数的SQL语句 {@see QueryHelper}
* @param clazz JavaBean风格的DTO或者Record,需要用别名跟JavaBean对应
* @param <T> 返回JavaBean风格的DTO或者Record
* @param params 按照顺序给条件
*/
public <T> Page<T> paginate( Boolean isGroupBySql, String nativeSQL, String nativeCountSQL , Class<T> clazz , int pageNumber, int pageSize, Object... params){
if (pageNumber < 1 || pageSize < 1) {
throw new IllegalArgumentException("pageNumber and pageSize must more than 0");
}
Query countQuery = entityManager.createNativeQuery(nativeCountSQL);
//坑死人,1-Based
for (int i = 1; i <= params.length; i++) {
countQuery.setParameter(i , params[i-1]);
}
List countQueryResultList = countQuery.getResultList();
int size = countQueryResultList.size();
if (isGroupBySql == null) {
isGroupBySql = size > 1;
}
long totalRow;
if (isGroupBySql) {
totalRow = size;
} else {
totalRow = (size > 0) ? ((Number)countQueryResultList.get(0)).longValue() : 0;
}
if (totalRow == 0) {
return new Page<>(new ArrayList<>(0), pageNumber, pageSize, 0, 0);
}
int totalPage = (int) (totalRow / pageSize);
if (totalRow % pageSize != 0) {
totalPage++;
}
if (pageNumber > totalPage) {
return new Page<>(new ArrayList<>(0), pageNumber, pageSize, totalPage, (int)totalRow);
}
Session session = entityManager.unwrap(Session.class);
int offset = pageSize * (pageNumber - 1);
org.hibernate.Query query = session.createSQLQuery(nativeSQL).setFirstResult(offset).setMaxResults(pageSize);
//坑死人,0-Based
for (int i = 0; i < params.length; i++) {
query.setParameter(i , params[i]);
}
final List list = getList(query, clazz);
return new Page<T>(list, pageNumber, pageSize, totalPage, (int)totalRow);
}
/**
*
* @param pageNumber pageNumber
* @param pageSize pageSize
* @param isGroupBySql 是否包含Group by语句,影响总行数
* @param nativeSQL 原生SQL语句 {@see QueryHelper}
* @param nativeCountSQL 原生求总行数的SQL语句 {@see QueryHelper}
* @param clazz JavaBean风格的DTO或者Record,需要用别名跟JavaBean对应
* @param <T> 返回JavaBean风格的DTO或者Record
* @param searchMap k-v条件
*/
public <T> Page<T> paginate( Boolean isGroupBySql, String nativeSQL,String nativeCountSQL , Class<T> clazz ,int pageNumber, int pageSize, Map<String , Object> searchMap){
if (pageNumber < 1 || pageSize < 1) {
throw new IllegalArgumentException("pageNumber and pageSize must more than 0");
}
Query countQuery = entityManager.createNativeQuery(nativeCountSQL);
if(null != searchMap) {
searchMap.forEach(countQuery::setParameter);
}
List countQueryResultList = countQuery.getResultList();
int size = countQueryResultList.size();
if (isGroupBySql == null) {
isGroupBySql = size > 1;
}
long totalRow;
if (isGroupBySql) {
totalRow = size;
} else {
totalRow = (size > 0) ? ((Number)countQueryResultList.get(0)).longValue() : 0;
}
if (totalRow == 0) {
return new Page<>(new ArrayList<>(0), pageNumber, pageSize, 0, 0);
}
int totalPage = (int) (totalRow / pageSize);
if (totalRow % pageSize != 0) {
totalPage++;
}
if (pageNumber > totalPage) {
return new Page<>(new ArrayList<>(0), pageNumber, pageSize, totalPage, (int)totalRow);
}
Session session = entityManager.unwrap(Session.class);
int offset = pageSize * (pageNumber - 1);
org.hibernate.Query query = session.createSQLQuery(nativeSQL).setFirstResult(offset).setMaxResults(pageSize);
if(null != searchMap) {
searchMap.forEach(query::setParameter);
}
final List list = getList(query, clazz);
return new Page<T>(list, pageNumber, pageSize, totalPage, (int)totalRow);
}
private <T> List getList(org.hibernate.Query query, Class<T> clazz) {
final List list;
//Object[].class
if(Object[].class == clazz){
return query.list();
}
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List mapList = query.list();
list = new ArrayList(mapList.size());
mapList.forEach(map->{
Map<String , Object> tmp = (Map<String , Object>) map;
//Record.class
if(Record.class == clazz){
list.add(new Record(tmp));
//Map及子类
}else if(Map.class.isAssignableFrom(clazz)){
list.add(tmp);
//JavaBean风格
}else {
list.add(Map2Bean.convert(tmp , clazz));
}
});
return list;
}
/*private <T> List getList(org.hibernate.Query query, Class<T> clazz) {
final List list;
if(Record.class == clazz){
//返回Record
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
List mapList = query.list();
list = new ArrayList(mapList.size());
mapList.forEach(map->{
Map<String , Object> tmp = (Map<String , Object>) map;
list.add(new Record(tmp));
});
}else {
//返回JavaBean
//只能返回简单的Javabean,不具备级联特性
query.setResultTransformer(Transformers.aliasToBean(clazz));
list = query.list();
}
return list;
}*/
private String getCountSQL(String sql){
String countSQL = "SELECT COUNT(*) AS totalRow " + sql.substring(sql.toUpperCase().indexOf("FROM"));
return replaceOrderBy(countSQL);
}
protected static class Holder {
private static final Pattern ORDER_BY_PATTERN = Pattern.compile(
"order\\s+by\\s+[^,\\s]+(\\s+asc|\\s+desc)?(\\s*,\\s*[^,\\s]+(\\s+asc|\\s+desc)?)*",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
}
public String replaceOrderBy(String sql) {
return Holder.ORDER_BY_PATTERN.matcher(sql).replaceAll("");
}
}
此工具类可以将结果集封装为Object[]、Map、Bean、Record等。Record参考JFinal的Record(可以类比Map,但是类型更安全、更易处理)。
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Record
* @author xiongshiyan
*/
public class Record implements Serializable {
private static final long serialVersionUID = 905784513600884082L;
private Map<String, Object> columns = new HashMap<>();
public Record(){
}
public Record(Map<String, Object> columns){
this.columns = columns;
}
public Map<String, Object> getColumns() {
return columns;
}
public Record setColumns(Map<String, Object> columns) {
this.getColumns().putAll(columns);
return this;
}
public Record setColumns(Record record) {
getColumns().putAll(record.getColumns());
return this;
}
public Record remove(String column) {
getColumns().remove(column);
return this;
}
public Record remove(String... columns) {
if (columns != null){
for (String c : columns) {
this.getColumns().remove(c);
}
}
return this;
}
public Record removeNullValueColumns() {
for (java.util.Iterator<Map.Entry<String, Object>> it = getColumns().entrySet().iterator(); it.hasNext();) {
Map.Entry<String, Object> e = it.next();
if (e.getValue() == null) {
it.remove();
}
}
return this;
}
/**
* Keep columns of this record and remove other columns.
* @param columns the column names of the record
*/
public Record keep(String... columns) {
if (columns != null && columns.length > 0) {
Map<String, Object> newColumns = new HashMap<String, Object>(columns.length); // getConfig().containerFactory.getColumnsMap();
for (String c : columns) {
if (this.getColumns().containsKey(c)) { // prevent put null value to the newColumns
newColumns.put(c, this.getColumns().get(c));
}
}
this.getColumns().clear();
this.getColumns().putAll(newColumns);
}else {
this.getColumns().clear();
}
return this;
}
/**
* Keep column of this record and remove other columns.
* @param column the column names of the record
*/
public Record keep(String column) {
if (getColumns().containsKey(column)) { // prevent put null value to the newColumns
Object keepIt = getColumns().get(column);
getColumns().clear();
getColumns().put(column, keepIt);
}else {
getColumns().clear();
}
return this;
}
public Record clear() {
getColumns().clear();
return this;
}
public Record set(String column, Object value) {
getColumns().put(column, value);
return this;
}
public <T> T get(String column) {
return (T)getColumns().get(column);
}
public <T> T get(String column, Object defaultValue) {
Object result = getColumns().get(column);
return (T)(result != null ? result : defaultValue);
}
/**
* Get column of mysql type: varchar, char, enum, set, text, tinytext, mediumtext, longtext
*/
public String getStr(String column) {
return (String)getColumns().get(column);
}
/**
* Get column of mysql type: int, integer, tinyint(n) n > 1, smallint, mediumint
*/
public Integer getInt(String column) {
return (Integer)getColumns().get(column);
}
/**
* Get column of mysql type: bigint
*/
public Long getLong(String column) {
return (Long)getColumns().get(column);
}
/**
* Get column of mysql type: unsigned bigint
*/
public java.math.BigInteger getBigInteger(String column) {
return (java.math.BigInteger)getColumns().get(column);
}
/**
* Get column of mysql type: date, year
*/
public java.util.Date getDate(String column) {
return (java.util.Date)getColumns().get(column);
}
/**
* Get column of mysql type: time
*/
public java.sql.Time getTime(String column) {
return (java.sql.Time)getColumns().get(column);
}
/**
* Get column of mysql type: timestamp, datetime
*/
public java.sql.Timestamp getTimestamp(String column) {
return (java.sql.Timestamp)getColumns().get(column);
}
/**
* Get column of mysql type: real, double
*/
public Double getDouble(String column) {
return (Double)getColumns().get(column);
}
/**
* Get column of mysql type: float
*/
public Float getFloat(String column) {
return (Float)getColumns().get(column);
}
/**
* Get column of mysql type: bit, tinyint(1)
*/
public Boolean getBoolean(String column) {
return (Boolean)getColumns().get(column);
}
/**
* Get column of mysql type: decimal, numeric
*/
public java.math.BigDecimal getBigDecimal(String column) {
return (java.math.BigDecimal)getColumns().get(column);
}
/**
* Get column of mysql type: binary, varbinary, tinyblob, blob, mediumblob, longblob
* I have not finished the test.
*/
public byte[] getBytes(String column) {
return (byte[])getColumns().get(column);
}
/**
* Get column of any type that extends from Number
*/
public Number getNumber(String column) {
return (Number)getColumns().get(column);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString()).append(" {");
boolean first = true;
for (Map.Entry<String, Object> e : getColumns().entrySet()) {
if (first){
first = false;
}else {
sb.append(", ");
}
Object value = e.getValue();
if (value != null) {
value = value.toString();
}
sb.append(e.getKey()).append(":").append(value);
}
sb.append("}");
return sb.toString();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Record)) {
return false;
}
if (o == this) {
return true;
}
return this.getColumns().equals(((Record)o).getColumns());
}
@Override
public int hashCode() {
return getColumns() == null ? 0 : getColumns().hashCode();
}
/**
* Return column names of this record.
*/
public String[] getColumnNames() {
Set<String> attrNameSet = getColumns().keySet();
return attrNameSet.toArray(new String[attrNameSet.size()]);
}
/**
* Return column values of this record.
*/
public Object[] getColumnValues() {
java.util.Collection<Object> attrValueCollection = getColumns().values();
return attrValueCollection.toArray(new Object[attrValueCollection.size()]);
}
/**
* Return json string of this record.
*/
public String toJson() {
throw new UnsupportedOperationException("还未实现");
}
}
Page也是参考JFinal的实现,代表一页信息。
import java.io.Serializable;
import java.util.List;
/**
* Page is the result of Model.paginate(......) or Db.paginate(......)
*/
public class Page<T> implements Serializable {
private static final long serialVersionUID = -5395997221963176643L;
private List<T> list; // list result of this page
private int pageNumber; // page number
private int pageSize=10; // result amount of this page
private int totalPage; // total page
private int totalRow; // total row
public Page(int pageNumber) {
this.pageNumber = pageNumber;
}
/**
* Constructor.
* @param list the list of paginate result
* @param pageNumber the page number
* @param pageSize the page size
* @param totalPage the total page of paginate
* @param totalRow the total row of paginate
*/
public Page(List<T> list, int pageNumber, int pageSize, int totalPage, int totalRow) {
this.list = list;
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalPage = totalPage;
this.totalRow = totalRow;
}
public Page(int pageNumber, int pageSize) {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
}
/**
* Return list of this page.
*/
public List<T> getList() {
return list;
}
/**
* Return page number.
*/
public int getPageNumber() {
return pageNumber;
}
/**
* Return page size.
*/
public int getPageSize() {
return pageSize;
}
/**
* Return total page.
*/
public int getTotalPage() {
totalPage = totalRow / pageSize;
if (totalRow % pageSize > 0) {
totalPage++;
}
return totalPage;
}
/**
* Return total row.
*/
public int getTotalRow() {
return totalRow;
}
public boolean isFirstPage() {
return pageNumber == 1;
}
public boolean isLastPage() {
return pageNumber == totalPage;
}
public void setList(List<T> list) {
this.list = list;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public void setTotalRow(int totalRow) {
this.totalRow = totalRow;
}
@Override
public String toString() {
return "Page{" +
"list=" + list +
", pageNumber=" + pageNumber +
", pageSize=" + pageSize +
", totalPage=" + totalPage +
", totalRow=" + totalRow +
'}';
}
}
其中的一个关键实现,Map-->Bean依赖于一个工具类Map2Bean,支持基本数据类型、下划线转驼峰命名方式、继承(域名不能相同),但是不支持map、list、set等,查询返回的结果集应该不存在这些数据类型吧。应该还有更好的实现。
import java.lang.reflect.Field;
import java.util.*;
/**
* 将查询结果 map 封装成对应的javaBean,支持级联 ,但是属性不能重复
* 对应的javaBean的属性名必须以小驼峰形式命名,否则无法填充数据
* @author zenghl
* @author xiongshiyan
*/
public class Map2Bean {
private Map2Bean(){
}
/**
* 将 map 数据封装成javaBean
* @param map Map类型数据
* @param clazz 需要转换的JavaBean
* @param <T> 泛型
* @return JavaBean
*/
public static <T> T convert(Map<String,Object> map,Class<T> clazz){
Map<String, Object> tmp = _2CamelWith(map);
try {
final T instance = clazz.newInstance();
//Field[] fields = clazz.getDeclaredFields();
List<Field> fields = new ArrayList<>();
parseAllFields(clazz , fields);
for (Field field : fields) {
String fieldName = field.getName();
Class<?> type = field.getType();
if(!ObjectKit.canSetValueDirectly(type)){
ObjectKit.setValue(instance, field, convert(tmp, type));
}else {
Object value = tmp.get(fieldName);
if(null == value){
//null没得必要设置
continue;
}
ObjectKit.setValue(instance, field, value);
/*map.forEach((k,v)->{
String columnToField = StrKit._2Camel(k);
if(fieldName.equals(columnToField)) {
ObjectKit.setValue(instance, field, v);
}
});*/
}
}
return instance;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 递归获取某个类的所有的属性
* getDeclaredFields 获取某个类的所有的字段,包括私有的,但是不包括父类的
* getFields 获得某个类的所有的公共(public)的字段,包括父类中的字段
*/
private static void parseAllFields(Class<?> clazz , List<Field> list){
if(clazz != Object.class){
Field[] fields = clazz.getDeclaredFields();
list.addAll(Arrays.asList(fields));
parseAllFields(clazz.getSuperclass() , list);
}
}
/**
* 将map中的下划线转换为小驼峰key添加进去
*/
private static Map<String, Object> _2CamelWith(Map<String, Object> map) {
final Map<String , Object> tmp = new LinkedHashMap<>(map);
map.forEach((k , v) ->{
if(k.contains("_")){
//原来的还是保留,万一有些Bean中字段有"_"呢 map.remove(k);
tmp.put(StrKit._2Camel(k) , v);
}}
);
return tmp;
}
}
此工具类的关键是注入一个EntityManager。在SpringBoot环境中,我们可以将Pagination注入,然后使用即可。其中注入EntityManager在单数据源和多数据源的情况还需要特别注意。见下一篇博文。
SQL语句的动态生成见另外一篇博文: