All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.chengyuxing.sql.BakiDao Maven / Gradle / Ivy

Go to download

Light wrapper of JDBC, support ddl, dml, query, plsql/procedure/function, transaction and manage sql file.

There is a newer version: 9.0.2
Show newest version
package com.github.chengyuxing.sql;

import com.github.chengyuxing.common.DataRow;
import com.github.chengyuxing.common.io.FileResource;
import com.github.chengyuxing.common.tuple.Pair;
import com.github.chengyuxing.common.tuple.Triple;
import com.github.chengyuxing.sql.datasource.DataSourceUtil;
import com.github.chengyuxing.sql.dsl.*;
import com.github.chengyuxing.sql.dsl.clause.GroupBy;
import com.github.chengyuxing.sql.dsl.clause.Having;
import com.github.chengyuxing.sql.dsl.clause.OrderBy;
import com.github.chengyuxing.sql.dsl.clause.Where;
import com.github.chengyuxing.sql.dsl.clause.condition.Criteria;
import com.github.chengyuxing.sql.dsl.types.FieldReference;
import com.github.chengyuxing.sql.dsl.types.OrderByType;
import com.github.chengyuxing.sql.exceptions.ConnectionStatusException;
import com.github.chengyuxing.sql.exceptions.IllegalSqlException;
import com.github.chengyuxing.sql.exceptions.UncheckedSqlException;
import com.github.chengyuxing.sql.page.IPageable;
import com.github.chengyuxing.sql.page.PageHelper;
import com.github.chengyuxing.sql.page.impl.Db2PageHelper;
import com.github.chengyuxing.sql.page.impl.MysqlPageHelper;
import com.github.chengyuxing.sql.page.impl.OraclePageHelper;
import com.github.chengyuxing.sql.page.impl.PGPageHelper;
import com.github.chengyuxing.sql.plugins.*;
import com.github.chengyuxing.sql.support.*;
import com.github.chengyuxing.sql.support.executor.Executor;
import com.github.chengyuxing.sql.support.executor.QueryExecutor;
import com.github.chengyuxing.sql.types.Param;
import com.github.chengyuxing.sql.annotation.SqlStatementType;
import com.github.chengyuxing.sql.utils.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 

Default implementation of Baki interface

*

If {@link XQLFileManager } configured, all methods will be support replace sql statement to sql name ({@code &.}).

*

Example:

*
 * try ({@link Stream}<{@link DataRow}> s = baki.query("&sys.getUser").stream()) {
 *     s.forEach(System.out::println);
 * }
*/ public class BakiDao extends JdbcSupport implements Baki { private final static Logger log = LoggerFactory.getLogger(BakiDao.class); private final Map xqlMappingHandlers = new HashMap<>(); private final Map queryCacheLocks = new ConcurrentHashMap<>(); private final DataSource dataSource; private DatabaseMetaData metaData; private String databaseId; private SqlGenerator sqlGenerator; private EntityManager entityManager; //---------optional properties------ /** * Global custom page helper provider. */ private PageHelperProvider globalPageHelperProvider; /** * Custom sql interceptor. */ private SqlInterceptor sqlInterceptor; /** * Custom prepared sql statement parameter value handler. */ private StatementValueHandler statementValueHandler; /** * Do something after parse dynamic sql. */ private SqlParseChecker sqlParseChecker; /** * Sql watcher. */ private SqlWatcher sqlWatcher; /** * XQL file manager. */ private XQLFileManager xqlFileManager; /** * Batch size for execute batch. */ private int batchSize = 1000; /** * Named parameter prefix symbol. */ private char namedParamPrefix = ':'; /** * Load {@code xql-file-manager-}{@link #databaseId() databaseId}{@code .yml} first if exists, * otherwise {@code xql-file-manager.yml} */ private boolean autoXFMConfig = false; /** * Page query page number argument key. */ private String pageKey = "page"; /** * Page query page size argument key. */ private String sizeKey = "size"; /** * Jdbc execute sql timeout({@link Statement#setQueryTimeout(int)}) handler. */ private QueryTimeoutHandler queryTimeoutHandler; /** * Query cache manager. */ private QueryCacheManager queryCacheManager; /** * Query dsl condition operator white list. */ private Set operatorWhiteList; /** * Constructs a new BakiDao with initial datasource. * * @param dataSource datasource */ public BakiDao(@NotNull DataSource dataSource) { this.dataSource = dataSource; init(); } /** * Initialize default configuration properties. */ protected void init() { this.sqlGenerator = new SqlGenerator(namedParamPrefix); this.entityManager = new EntityManager(namedParamPrefix); this.statementValueHandler = (ps, index, value, metaData) -> JdbcUtil.setStatementValue(ps, index, value); this.queryTimeoutHandler = (sql, args) -> 0; this.using(c -> { try { this.metaData = c.getMetaData(); this.databaseId = this.metaData.getDatabaseProductName().toLowerCase(); return 0; } catch (SQLException e) { throw new UncheckedSqlException("initialize metadata error.", e); } }); } /** * Returns the mapper interface proxy instance. * * @param mapperInterface mapper interface * @param interface type * @return interface instance * @throws IllegalAccessException not interface or has no @XQLMapper */ public T proxyXQLMapper(@NotNull Class mapperInterface) throws IllegalAccessException { return XQLMapperUtil.getProxyInstance(mapperInterface, new XQLInvocationHandler() { @Override protected @NotNull BakiDao baki() { return BakiDao.this; } }); } /** * Caches the query result if necessary. * * @param key cache key * @param sql sql name or sql string * @param args args * @return query result */ public Stream executeQueryStream(@NotNull final String key, @NotNull final String sql, Map args) { if (Objects.isNull(queryCacheManager) || !queryCacheManager.isAvailable(key, args)) { return executeQueryStream(sql, args); } String uniqueKey = queryCacheManager.uniqueKey(key, args); Stream cache = queryCacheManager.get(uniqueKey); if (Objects.nonNull(cache)) { log.debug("Hits cache({}, {}), returns data from cache.", key, args); return cache; } Object lock = queryCacheLocks.computeIfAbsent(uniqueKey, k -> new Object()); synchronized (lock) { cache = queryCacheManager.get(uniqueKey); if (Objects.nonNull(cache)) { log.debug("Hits cache({}, {}) after lock, returns data from cache.", key, args); return cache; } List prepareCache = new ArrayList<>(); Stream queryStream = executeQueryStream(sql, args) .peek(prepareCache::add) .onClose(() -> queryCacheManager.put(uniqueKey, prepareCache)); log.debug("Put query result({}, {}) to cache.", key, args); return queryStream; } } @Override public QueryExecutor query(@NotNull String sql) { return new QueryExecutor(sql) { @Override public Stream stream() { return watchSql(sql, sql, args, () -> executeQueryStream(sql, sql, args)); } @Override public List> maps() { try (Stream s = stream()) { return s.collect(Collectors.toList()); } } @Override public List rows() { try (Stream s = stream()) { return s.collect(Collectors.toList()); } } @Override public List entities(Class entityClass) { try (Stream s = stream()) { return s.map(d -> EntityUtil.mapToEntity(d, entityClass)).collect(Collectors.toList()); } } @Override public IPageable pageable(int page, int size) { IPageable iPageable = new SimplePageable(sql, page, size); return iPageable.args(args); } @Override public IPageable pageable(@NotNull String pageKey, @NotNull String sizeKey) { Integer page = (Integer) args.get(pageKey); Integer size = (Integer) args.get(sizeKey); if (page == null || size == null) { throw new IllegalArgumentException("page or size is null."); } return pageable(page, size); } /** * {@inheritDoc} *
    *
  • Default page number key: {@link BakiDao#getPageKey()}
  • *
  • Default page size key: {@link BakiDao#getSizeKey()}
  • *
* @return IPageable instance */ @Override public IPageable pageable() { return pageable(pageKey, sizeKey); } @Override public @NotNull DataRow findFirstRow() { return findFirst().orElseGet(() -> new DataRow(0)); } @Override public T findFirstEntity(Class entityClass) { return findFirst().map(d -> EntityUtil.mapToEntity(d, entityClass)).orElse(null); } @Override public Optional findFirst() { try (Stream s = stream()) { return s.findFirst(); } } }; } @Override public > Query query(@NotNull Class clazz) { EntityManager.EntityMeta entityMeta = entityManager.getEntityMeta(clazz); return new Query() { private final Set selectColumns = new LinkedHashSet<>(); private final List finalWhereCriteria = new ArrayList<>(); private final Set> finalOrderBy = new LinkedHashSet<>(); private final Set finalGroupByAggColumns = new LinkedHashSet<>(); private final Set finalGroupByColumns = new LinkedHashSet<>(); private final List finalHavingCriteria = new ArrayList<>(); private Triple> createQuery() { String select = selectColumns.isEmpty() ? entityMeta.getSelect() : entityMeta.getSelect(selectColumns); String countSelect = entityMeta.getCountSelect(); Pair> where = new InternalWhere<>(clazz, finalWhereCriteria).getWhere(); if (!where.getItem1().isEmpty()) { select += where.getItem1(); countSelect += where.getItem1(); } String orderBy = new InternalOrderBy<>(clazz, finalOrderBy).getOrderBy(); if (!orderBy.isEmpty()) { select += orderBy; } return Triple.of(select, countSelect, where.getItem2()); } private InternalGroupBy createGroupBy() { Pair> where = new InternalWhere<>(clazz, finalWhereCriteria).getWhere(); String orderBy = new InternalOrderBy<>(clazz, finalOrderBy).getOrderBy(); InternalGroupBy groupBy = new InternalGroupBy<>(clazz, finalGroupByAggColumns, finalGroupByColumns, finalHavingCriteria); groupBy.setWhereClause(where.getItem1()); groupBy.setOrderByClause(orderBy); groupBy.setArgs(where.getItem2()); return groupBy; } @Override public SELF where(@NotNull Function, Where> where) { Where gotten = where.apply(new InternalWhere<>(clazz)); InternalWhere wrapper = new InternalWhere<>(clazz, gotten); finalWhereCriteria.addAll(wrapper.getCriteria()); //noinspection unchecked return (SELF) this; } @Override public SELF groupBy(@NotNull Function, GroupBy> groupBy) { GroupBy gotten = groupBy.apply(new InternalGroupBy<>(clazz)); InternalGroupBy wrapper = new InternalGroupBy<>(clazz, gotten); finalGroupByAggColumns.addAll(wrapper.getAggColumns()); finalGroupByColumns.addAll(wrapper.getGroupColumns()); finalHavingCriteria.addAll(wrapper.getHavingCriteria()); //noinspection unchecked return (SELF) this; } @Override public SELF orderBy(@NotNull Function, OrderBy> orderBy) { OrderBy gotten = orderBy.apply(new InternalOrderBy<>(clazz)); InternalOrderBy wrapper = new InternalOrderBy<>(clazz, gotten); finalOrderBy.addAll(wrapper.getOrders()); //noinspection unchecked return (SELF) this; } @Override public SELF select(@NotNull List> columns) { selectColumns.clear(); for (FieldReference column : columns) { String columnName = EntityUtil.getFieldNameWithCache(column); // excludes the field which annotated with @Transient if (entityMeta.getColumns().containsKey(columnName)) { selectColumns.add(columnName); } } //noinspection unchecked return (SELF) this; } @Override public SELF deselect(@NotNull List> columns) { if (columns.isEmpty()) { //noinspection unchecked return (SELF) this; } selectColumns.clear(); Set deselectColumns = columns.stream().map(EntityUtil::getFieldNameWithCache).collect(Collectors.toSet()); for (String column : entityMeta.getColumns().keySet()) { if (!deselectColumns.contains(column)) { selectColumns.add(column); } } //noinspection unchecked return (SELF) this; } @Override public SELF peek(@NotNull BiConsumer>> consumer) { Triple> query = createQuery(); consumer.accept(query.getItem1(), Pair.of(query.getItem2(), Collections.unmodifiableMap(query.getItem3()))); //noinspection unchecked return (SELF) this; } @Override public Stream toRowStream() { // there is no any group by columns, just execute query without group by clause. if (finalGroupByColumns.isEmpty()) { if (!finalGroupByAggColumns.isEmpty()) { throw new IllegalStateException("group by clause must have at least one column"); } Triple> query = createQuery(); String key = entityCallKey(clazz, query.getItem1()); return watchSql(key, query.getItem1(), query.getItem3(), () -> executeQueryStream( key, query.getItem1(), query.getItem3() )); } return createGroupBy().query(); } @Override public Stream toStream() { return toRowStream().map(d -> EntityUtil.mapToEntity(d, clazz)); } @Override public List toList() { try (Stream s = toStream()) { return s.collect(Collectors.toList()); } } @Override public List toList(@NotNull Function mapper) { try (Stream s = toStream()) { return s.map(mapper).collect(Collectors.toList()); } } @Override public R collect(@NotNull Function mapper, @NotNull Collector collector) { try (Stream s = toStream()) { return s.map(mapper).collect(collector); } } @Override public R collect(@NotNull Collector collector) { try (Stream s = toStream()) { return s.collect(collector); } } @Override public @NotNull Optional findFirst() { try (Stream s = toStream()) { return s.findFirst(); } } @Override public @Nullable T getFirst() { return findFirst().orElse(null); } @Override public @NotNull PagedResource toPagedResource(@Range(from = 1, to = Integer.MAX_VALUE) int page, @Range(from = 1, to = Integer.MAX_VALUE) int size, @Nullable PageHelperProvider pageHelperProvider) { Triple> query = createQuery(); return new SimplePageable(query.getItem1(), page, size) .args(query.getItem3()) .pageHelper(pageHelperProvider) .count(query.getItem2()) .collect(d -> EntityUtil.mapToEntity(d, clazz)); } @Override public @NotNull PagedResource toPagedResource(@Range(from = 1, to = Integer.MAX_VALUE) int page, @Range(from = 1, to = Integer.MAX_VALUE) int size) { return toPagedResource(page, size, null); } @Override public @NotNull PagedResource toPagedRowResource(@Range(from = 1, to = Integer.MAX_VALUE) int page, @Range(from = 1, to = Integer.MAX_VALUE) int size, @Nullable PageHelperProvider pageHelperProvider) { String query; String countQuery; Map args; if (finalGroupByColumns.isEmpty()) { if (!finalGroupByAggColumns.isEmpty()) { throw new IllegalStateException("group by clause must have at least one column"); } Triple> queryObj = createQuery(); query = queryObj.getItem1(); countQuery = queryObj.getItem2(); args = queryObj.getItem3(); } else { // group by paged query InternalGroupBy groupBy = createGroupBy(); Pair> querySqlObj = groupBy.getQuerySql(); query = querySqlObj.getItem1(); args = querySqlObj.getItem2(); countQuery = sqlGenerator.generateCountQuery( entityMeta.getSelect(groupBy.getGroupColumns()) + groupBy.getWhereClause() + groupBy.getGroupByClause() + groupBy.getHavingClause().getItem1() ); } return new SimplePageable(query, page, size) .args(args) .pageHelper(pageHelperProvider) .count(countQuery) .collect(); } @Override public @NotNull PagedResource toPagedRowResource(@Range(from = 1, to = Integer.MAX_VALUE) int page, @Range(from = 1, to = Integer.MAX_VALUE) int size) { return toPagedRowResource(page, size, null); } @Override public boolean exists() { String query = entityMeta.getExistsSelect(); Pair> where = new InternalWhere<>(clazz, finalWhereCriteria).getWhere(); if (where.getItem1().isEmpty()) { throw new IllegalSqlException("Exists query must have condition."); } query += where.getItem1(); final String existQuery = query; String key = entityCallKey(clazz, existQuery); return watchSql(key, existQuery, where.getItem2(), () -> { try (Stream s = executeQueryStream(key, existQuery, where.getItem2())) { return s.findFirst().isPresent(); } }); } @Override public @Range(from = 0, to = Long.MAX_VALUE) long count() { String countSelect; Map args = Collections.emptyMap(); if (finalGroupByColumns.isEmpty()) { if (!finalGroupByAggColumns.isEmpty()) { throw new IllegalStateException("group by clause must have at least one column"); } countSelect = entityMeta.getCountSelect(); Pair> where = new InternalWhere<>(clazz, finalWhereCriteria).getWhere(); if (!where.getItem1().isEmpty()) { countSelect += where.getItem1(); args = where.getItem2(); } } else { InternalGroupBy groupBy = createGroupBy(); countSelect = sqlGenerator.generateCountQuery( entityMeta.getSelect(groupBy.getGroupColumns()) + groupBy.getWhereClause() + groupBy.getGroupByClause() + groupBy.getHavingClause().getItem1() ); args = groupBy.getQuerySql().getItem2(); } final String countQuery = countSelect; final Map myArgs = args; String key = entityCallKey(clazz, countQuery); return watchSql(key, countQuery, myArgs, () -> { try (Stream s = executeQueryStream(key, countQuery, myArgs)) { return s.findFirst() .map(d -> d.getLong(0)) .orElse(0L); } }); } @Override public R collectRow(@NotNull Function mapper, @NotNull Collector collector) { try (Stream s = toRowStream()) { return s.map(mapper).collect(collector); } } @Override public R collectRow(@NotNull Collector collector) { try (Stream s = toRowStream()) { return s.collect(collector); } } @Override public @NotNull Optional findFirstRow() { try (Stream s = toRowStream()) { return s.findFirst(); } } @Override public @NotNull DataRow getFirstRow() { return findFirstRow().orElse(DataRow.of()); } @Override public List toRows() { try (Stream s = toRowStream()) { return s.collect(Collectors.toList()); } } @Override public List> toMaps() { try (Stream s = toRowStream()) { return s.collect(Collectors.toList()); } } @Override public @NotNull Pair> getSql() { Triple> query = createQuery(); return Pair.of(query.getItem1(), Collections.unmodifiableMap(query.getItem3())); } }; } @Override public int insert(@NotNull T entity) { @SuppressWarnings("unchecked") Class clazz = (Class) entity.getClass(); String insert = entityManager.getEntityMeta(clazz).getInsert(); Map data = Args.ofEntity(entity); String key = entityCallKey(clazz, insert); return watchSql(key, insert, data, () -> executeUpdate(insert, data)); } @Override public int insert(@NotNull Collection entities) { if (entities.isEmpty()) return 0; @SuppressWarnings("unchecked") Class clazz = (Class) entities.iterator().next().getClass(); String insert = entityManager.getEntityMeta(clazz).getInsert(); List> data = entities.stream() .map(Args::ofEntity) .collect(Collectors.toList()); String key = entityCallKey(clazz, insert); return watchSql(key, insert, data, () -> executeBatchUpdate(insert, data, batchSize)); } @Override public int update(@NotNull T entity, boolean ignoreNull) { @SuppressWarnings("unchecked") Class clazz = (Class) entity.getClass(); EntityManager.EntityMeta entityMeta = entityManager.getEntityMeta(clazz); Map data = Args.ofEntity(entity); String update = ignoreNull ? sqlGenerator.generateNamedParamUpdate( entityMeta.getTableName(), entityMeta.getUpdateColumns(), data, true ) + entityMeta.getWhereById() : entityMeta.getUpdateById(); String key = entityCallKey(clazz, update); return watchSql(key, update, data, () -> executeUpdate(update, data)); } @Override public int update(@NotNull Collection entities, boolean ignoreNull) { if (entities.isEmpty()) return 0; @SuppressWarnings("unchecked") Class clazz = (Class) entities.iterator().next().getClass(); EntityManager.EntityMeta entityMeta = entityManager.getEntityMeta(clazz); List> data = entities.stream() .map(Args::ofEntity) .collect(Collectors.toList()); String update = ignoreNull ? sqlGenerator.generateNamedParamUpdate( entityMeta.getTableName(), entityMeta.getUpdateColumns(), data.get(0), true ) + entityMeta.getWhereById() : entityMeta.getUpdateById(); String key = entityCallKey(clazz, update); return watchSql(key, update, data, () -> executeBatchUpdate(update, data, batchSize)); } @Override public int delete(@NotNull T entity) { @SuppressWarnings("unchecked") Class clazz = (Class) entity.getClass(); EntityManager.EntityMeta entityMeta = entityManager.getEntityMeta(clazz); String delete = entityMeta.getDeleteById(); Map data = Args.ofEntity(entity); String key = entityCallKey(clazz, delete); return watchSql(key, delete, data, () -> executeUpdate(delete, data)); } @Override public int delete(@NotNull Collection entities) { if (entities.isEmpty()) { return 0; } @SuppressWarnings("unchecked") Class clazz = (Class) entities.iterator().next().getClass(); EntityManager.EntityMeta entityMeta = entityManager.getEntityMeta(clazz); String delete = entityMeta.getDeleteById(); List> data = entities.stream() .map(Args::ofEntity) .collect(Collectors.toList()); String key = entityCallKey(clazz, delete); return watchSql(key, delete, data, () -> executeBatchUpdate(delete, data, batchSize)); } @Override public Executor of(@NotNull String sql) { return new Executor() { @Override public @NotNull DataRow execute() { return watchSql(sql, sql, Collections.emptyMap(), () -> BakiDao.super.execute(sql, Collections.emptyMap())); } @Override public @NotNull DataRow execute(Map args) { return watchSql(sql, sql, args, () -> BakiDao.super.execute(sql, args)); } @Override public int executeBatch(String... moreSql) { List sqlList = new ArrayList<>(Arrays.asList(moreSql)); sqlList.add(0, sql); String s = String.join("###", sqlList); return watchSql(s, s, Collections.emptyMap(), () -> BakiDao.super.executeBatch(sqlList, batchSize)); } @Override public int executeBatch(@NotNull List moreSql) { String s = String.join("###", moreSql); return watchSql(s, s, Collections.emptyMap(), () -> BakiDao.super.executeBatch(moreSql, batchSize)); } @Override public int executeBatch(@NotNull Collection> data) { Map arg = data.isEmpty() ? new HashMap<>() : data.iterator().next(); Pair> parsed = parseSql(sql, arg); return watchSql(sql, parsed.getItem1(), data, () -> { Collection> newData; if (parsed.getItem2().containsKey(XQLFileManager.DynamicSqlParser.FOR_VARS_KEY) && parsed.getItem1().contains(XQLFileManager.DynamicSqlParser.VAR_PREFIX)) { List> list = new ArrayList<>(); for (Map item : data) { list.add(parseSql(sql, item).getItem2()); } newData = list; } else { newData = data; } return executeBatchUpdate(parsed.getItem1(), newData, batchSize); }); } @Override public @NotNull DataRow call(Map params) { return watchSql(sql, sql, params, () -> executeCallStatement(sql, params)); } }; } /** * Watch sql execution status. * * @param sourceSql source sql * @param targetSql target sql * @param args args * @param supplier supplier * @param type * @return any */ protected T watchSql(String sourceSql, String targetSql, Object args, Supplier supplier) { if (Objects.isNull(sqlWatcher)) { return supplier.get(); } long startTime = System.currentTimeMillis(); Throwable throwable = null; try { return supplier.get(); } catch (Exception e) { throwable = e; throw e; } finally { sqlWatcher.watch(sourceSql, targetSql, args, startTime, System.currentTimeMillis(), throwable); } } @Override public T using(Function func) { Connection connection = null; try { connection = getConnection(); return func.apply(connection); } finally { releaseConnection(connection, getDataSource()); } } @Override public DatabaseMetaData metaData() { return this.metaData; } @Override public @NotNull String databaseId() { return this.databaseId; } final class InternalWhere extends Where { private final Class clazz; private final Map columns; InternalWhere(Class clazz) { super(clazz); this.clazz = clazz; this.columns = entityManager.getColumns(this.clazz); } InternalWhere(Class clazz, Where other) { super(clazz, other); this.clazz = clazz; this.columns = entityManager.getColumns(clazz); } InternalWhere(Class clazz, List criteria) { super(clazz); this.clazz = clazz; this.columns = entityManager.getColumns(clazz); this.criteria = criteria; } @Override protected Where newInstance() { return new InternalWhere<>(clazz); } @Override protected char namedParamPrefix() { return namedParamPrefix; } @Override protected Set columnWhiteList() { return columns.keySet(); } @Override protected Set operatorWhiteList() { return operatorWhiteList; } private Pair> getWhere() { return build(); } private List getCriteria() { return criteria; } } final class InternalOrderBy extends OrderBy { private final Map columns; InternalOrderBy(Class clazz) { super(clazz); this.columns = entityManager.getColumns(clazz); } InternalOrderBy(Class clazz, OrderBy other) { super(clazz, other); this.columns = entityManager.getColumns(clazz); } InternalOrderBy(Class clazz, Set> orders) { super(clazz); this.columns = entityManager.getColumns(clazz); this.orders = orders; } private Set> getOrders() { return orders; } private String getOrderBy() { return build(); } @Override protected Set columnWhiteList() { return columns.keySet(); } @Override protected Set operatorWhiteList() { return operatorWhiteList; } } final class InternalGroupBy extends GroupBy { private final Map columns; private String whereClause = ""; private String orderByClause = ""; private Map args = Collections.emptyMap(); InternalGroupBy(@NotNull Class clazz) { super(clazz); this.columns = entityManager.getColumns(clazz); } InternalGroupBy(@NotNull Class clazz, Set aggColumns, Set groupColumns, List havingCriteria) { super(clazz); this.columns = entityManager.getColumns(clazz); this.groupColumns = groupColumns; this.aggColumns = aggColumns; this.havingCriteria = havingCriteria; } InternalGroupBy(@NotNull Class clazz, GroupBy other) { super(clazz, other); this.columns = entityManager.getColumns(clazz); } @Override public GroupBy having(Function, Having> having) { Having gotten = having.apply(new InternalHaving<>(clazz)); InternalHaving wrapper = new InternalHaving<>(clazz, gotten); havingCriteria.addAll(wrapper.getCriteria()); return this; } @Override protected Stream query() { Pair> query = getQuerySql(); String key = entityCallKey(clazz, query.getItem1()); return watchSql(key, query.getItem1(), query.getItem2(), () -> executeQueryStream(key, query.getItem1(), query.getItem2())); } @Override protected Set columnWhiteList() { return columns.keySet(); } @Override protected Set operatorWhiteList() { return operatorWhiteList; } private Pair> getQuerySql() { Map allArgs = new HashMap<>(); String query = entityManager.getEntityMeta(clazz).getSelect(getSelectColumns()); if (!whereClause.isEmpty()) { query += whereClause; } String groupByClause = buildGroupByClause(); if (!groupByClause.isEmpty()) { query += groupByClause; } Pair> having = new InternalHaving<>(clazz, havingCriteria).getHavingClause(); if (!having.getItem1().isEmpty()) { query += having.getItem1(); } if (!orderByClause.isEmpty()) { query += orderByClause; } allArgs.putAll(args); allArgs.putAll(having.getItem2()); return Pair.of(query, allArgs); } private Pair> getHavingClause() { return new InternalHaving<>(clazz, havingCriteria).getHavingClause(); } private List getHavingCriteria() { return havingCriteria; } public Map getArgs() { return args; } public String getWhereClause() { return whereClause; } public String getOrderByClause() { return orderByClause; } public String getGroupByClause() { return buildGroupByClause(); } private void setWhereClause(String whereClause) { this.whereClause = whereClause; } private void setOrderByClause(String orderByClause) { this.orderByClause = orderByClause; } private void setArgs(Map args) { this.args = args; } private Set getGroupColumns() { return groupColumns; } private Set getAggColumns() { return aggColumns; } } final class InternalHaving extends Having { InternalHaving(@NotNull Class clazz) { super(clazz); } InternalHaving(@NotNull Class clazz, Having other) { super(clazz, other); } InternalHaving(@NotNull Class clazz, List criteria) { super(clazz); this.criteria = criteria; } @Override protected Having newInstance() { return new InternalHaving<>(clazz); } @Override protected char namedParamPrefix() { return namedParamPrefix; } public Pair> getHavingClause() { return build(); } private List getCriteria() { return criteria; } } /** * Simple page helper implementation. */ final class SimplePageable extends IPageable { /** * Constructs a SimplePageable. * * @param recordQuery record query statement * @param page current page * @param size page size */ public SimplePageable(String recordQuery, int page, int size) { super(recordQuery, page, size); } @Override public PagedResource collect(Function mapper) { Pair> result = parseSql(recordQuery, args); String query = result.getItem1(); Map myArgs = result.getItem2(); if (count == null) { String cq = countQuery == null ? sqlGenerator.generateCountQuery(query) : countQuery; count = watchSql(recordQuery, cq, myArgs, () -> { try (Stream s = executeQueryStream(recordQuery, cq, myArgs)) { return s.findFirst() .map(d -> d.getInt(0)) .orElse(0); } }); } PageHelper pageHelper = null; if (pageHelperProvider != null) { pageHelper = pageHelperProvider.customPageHelper(metaData, databaseId, namedParamPrefix); } if (pageHelper == null) { pageHelper = defaultPager(); } pageHelper.init(page, size, count); Args pagedArgs = pageHelper.pagedArgs(); myArgs.putAll(rewriteArgsFunc == null ? pagedArgs : rewriteArgsFunc.apply(pagedArgs)); String executeQuery = disablePageSql ? query : pageHelper.pagedSql(namedParamPrefix, query); final PageHelper finalPageHelper = pageHelper; return watchSql(recordQuery, executeQuery, myArgs, () -> { try (Stream s = executeQueryStream(recordQuery, executeQuery, myArgs)) { List list = s.peek(d -> d.remove(PageHelper.ROW_NUM_KEY)) .map(mapper) .collect(Collectors.toList()); return PagedResource.of(finalPageHelper, list); } }); } } /** * For {@link #watchSql(String, String, Object, Supplier)} and {@link #executeQueryStream(String, String, Map)} * * @param clazz entity type * @param sql sql * @return unique key */ protected String entityCallKey(Class clazz, String sql) { return "@" + clazz.getName() + ":" + sql; } /** * Built-in default page helper. * * @return PageHelper instance * @throws UnsupportedOperationException there is no default implementation of your database * @throws ConnectionStatusException connection status exception */ protected PageHelper defaultPager() { if (Objects.nonNull(globalPageHelperProvider)) { PageHelper pageHelper = globalPageHelperProvider.customPageHelper(metaData, databaseId, namedParamPrefix); if (Objects.nonNull(pageHelper)) { return pageHelper; } } switch (databaseId) { case "oracle": return new OraclePageHelper(); case "postgresql": case "sqlite": return new PGPageHelper(); case "mysql": case "mariadb": return new MysqlPageHelper(); case "z/os": case "sqlds": case "iseries": case "db2 for unix/windows": case "cloudscape": case "informix": return new Db2PageHelper(); default: throw new UnsupportedOperationException("pager of \"" + databaseId + "\" default not implement currently, see method 'setGlobalPageHelperProvider'."); } } /** * Reload xql file manager by database id if necessary. */ protected void loadXFMConfigByDatabaseId() { if (Objects.nonNull(xqlFileManager)) { String pathByDb = "xql-file-manager-" + databaseId + ".yml"; FileResource resource = new FileResource(pathByDb); if (!resource.exists()) { resource = new FileResource(XQLFileManager.YML); } if (resource.exists()) { XQLFileManagerConfig config = new XQLFileManagerConfig(); config.loadYaml(resource); config.copyStateTo(xqlFileManager); xqlFileManager.init(); log.debug("{} detected by '{}' and loaded!", resource.getFileName(), databaseId); } } } /** * Get sql from {@link XQLFileManager} by sql name if first arg starts with symbol ({@code &}).
* Sql name format: {@code &.} * * @param sql sql statement or sql name * @param args args * @return sql * @throws NullPointerException if first arg starts with symbol ({@code &}) but {@link XQLFileManager} not configured * @throws IllegalSqlException sql interceptor reject sql */ @Override protected Pair> parseSql(@NotNull String sql, Map args) { Map myArgs = new HashMap<>(); if (Objects.nonNull(args)) { myArgs.putAll(args); } String mySql = SqlUtil.trimEnd(sql.trim()); if (mySql.startsWith("&")) { if (Objects.nonNull(xqlFileManager)) { Pair> result = xqlFileManager.get(mySql.substring(1), myArgs); mySql = result.getItem1(); // #for expression temp variables stored in _for variable. if (!result.getItem2().isEmpty()) { myArgs.put(XQLFileManager.DynamicSqlParser.FOR_VARS_KEY, result.getItem2()); } } else { throw new NullPointerException("can not find property 'xqlFileManager'."); } } if (mySql.contains("${")) { mySql = SqlUtil.formatSql(mySql, myArgs, sqlGenerator.getTemplateFormatter()); if (Objects.nonNull(xqlFileManager)) { mySql = SqlUtil.formatSql(mySql, xqlFileManager.getConstants(), sqlGenerator.getTemplateFormatter()); } } if (Objects.nonNull(sqlParseChecker)) { mySql = sqlParseChecker.handle(mySql); } if (Objects.nonNull(sqlInterceptor)) { boolean request = sqlInterceptor.preHandle(mySql, myArgs, metaData); if (!request) { throw new IllegalSqlException("permission denied, reject to execute sql.\nSQL: " + mySql + "\nArgs: " + myArgs); } } return Pair.of(mySql, myArgs); } @Override protected @NotNull SqlGenerator sqlGenerator() { return sqlGenerator; } @Override protected @NotNull DataSource getDataSource() { return dataSource; } @Override protected @NotNull Connection getConnection() { try { return DataSourceUtil.getConnection(dataSource); } catch (SQLException e) { throw new ConnectionStatusException("fetch connection failed.", e); } } @Override protected void releaseConnection(Connection connection, DataSource dataSource) { DataSourceUtil.releaseConnection(connection, dataSource); } @Override protected void doHandleStatementValue(@NotNull PreparedStatement ps, @Range(from = 1, to = Integer.MAX_VALUE) int index, @Nullable Object value) throws SQLException { statementValueHandler.handle(ps, index, value, metaData); } @Override protected int queryTimeout(String sql, Map args) { return queryTimeoutHandler.handle(sql, args); } public SqlGenerator getSqlGenerator() { return sqlGenerator; } public void setGlobalPageHelperProvider(PageHelperProvider globalPageHelperProvider) { this.globalPageHelperProvider = globalPageHelperProvider; } public void setSqlInterceptor(SqlInterceptor sqlInterceptor) { this.sqlInterceptor = sqlInterceptor; } public void setStatementValueHandler(StatementValueHandler statementValueHandler) { if (Objects.nonNull(statementValueHandler)) this.statementValueHandler = statementValueHandler; } public void setXqlFileManager(XQLFileManager xqlFileManager) { if (Objects.nonNull(xqlFileManager)) { this.xqlFileManager = xqlFileManager; this.xqlFileManager.setDatabaseId(databaseId); this.xqlFileManager.setTemplateFormatter(sqlGenerator.getTemplateFormatter()); if (autoXFMConfig) { loadXFMConfigByDatabaseId(); return; } if (!this.xqlFileManager.isInitialized()) { this.xqlFileManager.init(); } } } public XQLFileManager getXqlFileManager() { return xqlFileManager; } public int getBatchSize() { return batchSize; } public void setBatchSize(@Range(from = 1, to = Integer.MAX_VALUE) int batchSize) { this.batchSize = batchSize; } public char getNamedParamPrefix() { return namedParamPrefix; } public void setNamedParamPrefix(char namedParamPrefix) { this.namedParamPrefix = namedParamPrefix; this.sqlGenerator = new SqlGenerator(this.namedParamPrefix); } public boolean isAutoXFMConfig() { return autoXFMConfig; } public void setAutoXFMConfig(boolean autoXFMConfig) { this.autoXFMConfig = autoXFMConfig; if (this.autoXFMConfig) { loadXFMConfigByDatabaseId(); } } public String getPageKey() { return pageKey; } public void setPageKey(@NotNull String pageKey) { this.pageKey = pageKey; } public String getSizeKey() { return sizeKey; } public void setSizeKey(@NotNull String sizeKey) { this.sizeKey = sizeKey; } public void setSqlWatcher(SqlWatcher sqlWatcher) { this.sqlWatcher = sqlWatcher; } public void setQueryTimeoutHandler(QueryTimeoutHandler queryTimeoutHandler) { if (Objects.nonNull(queryTimeoutHandler)) { this.queryTimeoutHandler = queryTimeoutHandler; } } public Map getXqlMappingHandlers() { return xqlMappingHandlers; } public void registerXqlMappingHandler(SqlStatementType type, SqlInvokeHandler handler) { xqlMappingHandlers.put(type, handler); } public QueryCacheManager getQueryCacheManager() { return queryCacheManager; } public void setQueryCacheManager(QueryCacheManager queryCacheManager) { this.queryCacheManager = queryCacheManager; } /** * Could be cleared if necessary. * * @return query cache locks object. */ public Map getQueryCacheLocks() { return queryCacheLocks; } public void setSqlParseChecker(SqlParseChecker sqlParseChecker) { this.sqlParseChecker = sqlParseChecker; } public Set getOperatorWhiteList() { return operatorWhiteList; } public void setOperatorWhiteList(Set operatorWhiteList) { this.operatorWhiteList = operatorWhiteList; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy