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

io.github.biezhi.anima.core.AnimaQuery Maven / Gradle / Ivy

There is a newer version: 0.2.6
Show newest version
/**
 * Copyright (c) 2018, biezhi 王爵 ([email protected])
 * 

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.github.biezhi.anima.core; import io.github.biezhi.anima.Anima; import io.github.biezhi.anima.Model; import io.github.biezhi.anima.annotation.BelongsTo; import io.github.biezhi.anima.annotation.HasMany; import io.github.biezhi.anima.annotation.HasOne; import io.github.biezhi.anima.core.functions.TypeFunction; import io.github.biezhi.anima.core.relation.RelationParamBuilder; import io.github.biezhi.anima.enums.DMLType; import io.github.biezhi.anima.enums.ErrorCode; import io.github.biezhi.anima.enums.OrderBy; import io.github.biezhi.anima.exception.AnimaException; import io.github.biezhi.anima.page.Page; import io.github.biezhi.anima.page.PageRow; import io.github.biezhi.anima.utils.AnimaUtils; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.sql2o.Connection; import org.sql2o.Sql2o; import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.*; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Operational database core class * * @author biezhi */ @Slf4j @NoArgsConstructor public class AnimaQuery { private Class modelClass; private static ThreadLocal connectionThreadLocal = new ThreadLocal<>(); private StringBuilder conditionSQL = new StringBuilder(); private StringBuilder orderBySQL = new StringBuilder(); private List excludedColumns = new ArrayList<>(8); private List paramValues = new ArrayList<>(8); private Map updateColumns = new LinkedHashMap<>(8); private Set> relations = new HashSet<>(); private boolean isSQLLimit; private String selectColumns; private String primaryKeyColumn; private String tableName; private DMLType dmlType; public AnimaQuery(DMLType dmlType) { this.dmlType = dmlType; } public AnimaQuery(Class modelClass) { this.parse(modelClass); } public AnimaQuery parse(Class modelClass) { this.modelClass = modelClass; this.tableName = AnimaCache.getTableName(modelClass); this.primaryKeyColumn = AnimaCache.getPKColumn(modelClass); return this; } public AnimaQuery exclude(String... columnNames) { Collections.addAll(excludedColumns, columnNames); return this; } public AnimaQuery exclude(Class relation) { this.relations.add(relation); return this; } public AnimaQuery select(String columns) { if (null != this.selectColumns) { throw new AnimaException("Select method can only be called once."); } this.selectColumns = columns; return this; } public AnimaQuery where(String statement) { conditionSQL.append(" AND ").append(statement); return this; } public AnimaQuery where(TypeFunction function) { String columnName = AnimaUtils.getLambdaColumnName(function); conditionSQL.append(" AND ").append(columnName); return this; } public AnimaQuery where(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); conditionSQL.append(" AND ").append(columnName).append(" = ?"); paramValues.add(value); return this; } public AnimaQuery eq(Object value) { conditionSQL.append(" = ?"); paramValues.add(value); return this; } public AnimaQuery where(String statement, Object value) { conditionSQL.append(" AND ").append(statement); if (!statement.contains("?")) { conditionSQL.append(" = ?"); } paramValues.add(value); return this; } public AnimaQuery notNull() { conditionSQL.append(" IS NOT NULL"); return this; } public AnimaQuery and(String statement, Object value) { return this.where(statement, value); } public AnimaQuery and(TypeFunction function) { return this.where(function); } public AnimaQuery and(TypeFunction function, Object value) { return this.where(function, value); } public AnimaQuery or(String statement, Object value) { conditionSQL.append(" OR (").append(statement); if (!statement.contains("?")) { conditionSQL.append(" = ?"); } conditionSQL.append(')'); paramValues.add(value); return this; } public AnimaQuery notEq(String key, Object value) { conditionSQL.append(" AND ").append(key).append(" != ?"); paramValues.add(value); return this; } public AnimaQuery notEq(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.notEq(columnName, value); } public AnimaQuery notEq(Object value) { conditionSQL.append(" != ?"); paramValues.add(value); return this; } public AnimaQuery notEmpty(String column) { conditionSQL.append(" AND ").append(column).append(" != ''"); return this; } public AnimaQuery notEmpty(TypeFunction function) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.notEmpty(columnName); } public AnimaQuery notEmpty() { conditionSQL.append(" != ''"); return this; } public AnimaQuery notNull(String key) { conditionSQL.append(" AND ").append(key).append(" IS NOT NULL"); return this; } public AnimaQuery like(String key, Object value) { conditionSQL.append(" AND ").append(key).append(" LIKE ?"); paramValues.add(value); return this; } public AnimaQuery like(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.like(columnName, value); } public AnimaQuery like(Object value) { conditionSQL.append(" LIKE ?"); paramValues.add(value); return this; } public AnimaQuery between(String column, Object a, Object b) { conditionSQL.append(" AND ").append(column).append(" BETWEEN ? and ?"); paramValues.add(a); paramValues.add(b); return this; } public AnimaQuery between(TypeFunction function, Object a, Object b) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.between(columnName, a, b); } public AnimaQuery between(Object a, Object b) { conditionSQL.append(" BETWEEN ? and ?"); paramValues.add(a); paramValues.add(b); return this; } public AnimaQuery gt(String column, Object value) { conditionSQL.append(" AND ").append(column).append(" > ?"); paramValues.add(value); return this; } public AnimaQuery gt(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.gt(columnName, value); } public AnimaQuery gt(Object value) { conditionSQL.append(" > ?"); paramValues.add(value); return this; } public AnimaQuery gte(Object value) { conditionSQL.append(" >= ?"); paramValues.add(value); return this; } public AnimaQuery gte(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.gte(columnName, value); } public AnimaQuery lt(Object value) { conditionSQL.append(" < ?"); paramValues.add(value); return this; } public AnimaQuery lt(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.lt(columnName, value); } public AnimaQuery lte(Object value) { conditionSQL.append(" <= ?"); paramValues.add(value); return this; } public AnimaQuery lte(TypeFunction function, Object value) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.lte(columnName, value); } public AnimaQuery gte(String column, Object value) { conditionSQL.append(" AND ").append(column).append(" >= ?"); paramValues.add(value); return this; } public AnimaQuery lt(String column, Object value) { conditionSQL.append(" AND ").append(column).append(" < ?"); paramValues.add(value); return this; } public AnimaQuery lte(String column, Object value) { conditionSQL.append(" AND ").append(column).append(" <= ?"); paramValues.add(value); return this; } public AnimaQuery in(String column, Object... args) { if (null == args || args.length == 0) { log.warn("Column: {}, query params is empty."); return this; } conditionSQL.append(" AND ").append(column).append(" IN ("); for (int i = 0; i < args.length; i++) { if (i == args.length - 1) { conditionSQL.append("?"); } else { conditionSQL.append("?, "); } paramValues.add(args[i]); } conditionSQL.append(")"); return this; } public AnimaQuery in(Object... args) { if (null == args || args.length == 0) { log.warn("Column: {}, query params is empty."); return this; } conditionSQL.append(" IN ("); for (int i = 0; i < args.length; i++) { if (i == args.length - 1) { conditionSQL.append("?"); } else { conditionSQL.append("?, "); } paramValues.add(args[i]); } conditionSQL.append(")"); return this; } public AnimaQuery in(List list) { return this.in(list.toArray()); } public AnimaQuery in(String key, List args) { return this.in(key, args.toArray()); } public AnimaQuery in(TypeFunction function, Object... values) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.in(columnName, values); } public AnimaQuery in(TypeFunction function, List values) { String columnName = AnimaUtils.getLambdaColumnName(function); return this.in(columnName, values); } public AnimaQuery order(String order) { if (this.orderBySQL.length() > 0) { this.orderBySQL.append(','); } this.orderBySQL.append(' ').append(order); return this; } public AnimaQuery order(String columnName, OrderBy orderBy) { if (this.orderBySQL.length() > 0) { this.orderBySQL.append(','); } this.orderBySQL.append(' ').append(columnName).append(' ').append(orderBy.toString()); return this; } public AnimaQuery order(TypeFunction function, OrderBy orderBy) { String columnName = AnimaUtils.getLambdaColumnName(function); return order(columnName, orderBy); } public T find(Class returnType, String sql, Object[] params) { return this.queryOne(returnType, sql, params); } public T byId(Object id) { this.beforeCheck(); this.where(primaryKeyColumn, id); String sql = this.buildSelectSQL(false); T model = this.queryOne(modelClass, sql, paramValues); if (null != model) { this.setRelate(Collections.singletonList(model)); } return model; } public List byIds(Object... ids) { this.in(this.primaryKeyColumn, ids); return this.all(); } public T one() { this.beforeCheck(); String sql = this.buildSelectSQL(true); T model = this.queryOne(modelClass, sql, paramValues); if (null != model) { this.setRelate(Collections.singletonList(model)); } return model; } public List all() { this.beforeCheck(); String sql = this.buildSelectSQL(true); List models = this.queryList(modelClass, sql, paramValues); this.setRelate(models); return models; } public Stream stream() { List all = all(); if (null == all || all.isEmpty()) { return Stream.empty(); } return all.stream(); } public Stream parallel() { return stream().parallel(); } public Stream map(Function function) { return stream().map(function); } public Stream filter(Predicate predicate) { return stream().filter(predicate); } public List limit(int limit) { if (Anima.me().isUseSQLLimit()) { isSQLLimit = true; paramValues.add(limit); return all(); } return stream().limit(limit).collect(Collectors.toList()); } public Page page(int page, int limit) { return this.page(new PageRow(page, limit)); } public Page page(String sql, PageRow pageRow) { return this.page(sql, paramValues, pageRow); } public Page page(String sql, List paramValues, PageRow pageRow) { return this.page(sql, paramValues.toArray(), pageRow); } public Page page(String sql, Object[] params, PageRow pageRow) { this.beforeCheck(); String countSql = "SELECT COUNT(*) FROM (" + sql + ") tmp"; Connection conn = getConn(); try { long count = conn.createQuery(countSql).withParams(params).executeAndFetchFirst(Long.class); String pageSQL = this.buildPageSQL(pageRow); List list = conn.createQuery(pageSQL).withParams(params).setAutoDeriveColumnNames(true).throwOnMappingFailure(false).executeAndFetch(modelClass); this.setRelate(list); Page pageBean = new Page<>(count, pageRow.getPageNum(), pageRow.getPageSize()); pageBean.setRows(list); return pageBean; } finally { if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } this.clean(null); } } public Page page(PageRow pageRow) { String sql = this.buildSelectSQL(false); return this.page(sql, pageRow); } public long count() { this.beforeCheck(); String sql = this.buildCountSQL(); return this.queryOne(Long.class, sql, paramValues); } public AnimaQuery set(String column, Object value) { updateColumns.put(column, value); return this; } public AnimaQuery set(TypeFunction function, Object value) { return this.set(AnimaUtils.getLambdaColumnName(function), value); } public S queryOne(Class type, String sql, Object[] params) { Connection conn = getConn(); try { return conn.createQuery(sql).withParams(params).setAutoDeriveColumnNames(true).throwOnMappingFailure(false).executeAndFetchFirst(type); } finally { if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } this.clean(null); } } public S queryOne(Class type, String sql, List params) { if (Anima.me().isUseSQLLimit()) { sql += " LIMIT 1"; } List list = queryList(type, sql, params); return AnimaUtils.isNotEmpty(list) ? list.get(0) : null; } public List queryList(Class type, String sql, Object[] params) { Connection conn = getConn(); try { return conn.createQuery(sql).withParams(params).setAutoDeriveColumnNames(true).throwOnMappingFailure(false).executeAndFetch(type); } finally { if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } this.clean(null); } } public List queryList(Class type, String sql, List params) { return this.queryList(type, sql, params.toArray()); } public int execute() { switch (dmlType) { case UPDATE: return this.update(); case DELETE: return this.delete(); default: throw new AnimaException("Please check if your use is correct."); } } public int execute(String sql, Object... params) { Connection conn = getConn(); try { return conn.createQuery(sql).withParams(params).executeUpdate().getResult(); } finally { if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } this.clean(conn); } } public int execute(String sql, List params) { return this.execute(sql, params.toArray()); } public ResultKey save(S model) { String sql = this.buildInsertSQL(model); List columnValueList = AnimaUtils.toColumnValues(model, true); Connection conn = getConn(); try { return new ResultKey(conn.createQuery(sql).withParams(columnValueList).executeUpdate().getKey()); } finally { if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } this.clean(conn); } } public int delete() { String sql = this.buildDeleteSQL(null); return this.execute(sql, paramValues); } public int deleteById(S id) { this.where(primaryKeyColumn, id); return this.delete(); } public int deleteByModel(S model) { this.beforeCheck(); String sql = this.buildDeleteSQL(model); List columnValueList = AnimaUtils.toColumnValues(model, false); return this.execute(sql, columnValueList); } public int update() { this.beforeCheck(); String sql = this.buildUpdateSQL(null, updateColumns); List columnValueList = new ArrayList<>(); updateColumns.forEach((key, value) -> columnValueList.add(value)); columnValueList.addAll(paramValues); return this.execute(sql, columnValueList); } public int updateById(Serializable id) { this.where(primaryKeyColumn, id); return this.update(); } public int updateById(S model, Serializable id) { this.where(primaryKeyColumn, id); String sql = this.buildUpdateSQL(model, null); List columnValueList = AnimaUtils.toColumnValues(model, false); columnValueList.add(id); return this.execute(sql, columnValueList); } public int updateByModel(S model) { this.beforeCheck(); String sql = this.buildUpdateSQL(model, null); List columnValueList = AnimaUtils.toColumnValues(model, false); return this.execute(sql, columnValueList); } private String buildSelectSQL(boolean addOrderBy) { SQLParams sqlParams = SQLParams.builder() .modelClass(this.modelClass) .selectColumns(this.selectColumns) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .conditionSQL(this.conditionSQL) .excludedColumns(this.excludedColumns) .isSQLLimit(isSQLLimit) .build(); if (addOrderBy) { sqlParams.setOrderBy(this.orderBySQL.toString()); } return Anima.me().getDialect().select(sqlParams); } private String buildCountSQL() { SQLParams sqlParams = SQLParams.builder() .modelClass(this.modelClass) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .conditionSQL(this.conditionSQL) .build(); return Anima.me().getDialect().count(sqlParams); } private String buildPageSQL(PageRow pageRow) { SQLParams sqlParams = SQLParams.builder() .modelClass(this.modelClass) .selectColumns(this.selectColumns) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .conditionSQL(this.conditionSQL) .excludedColumns(this.excludedColumns) .orderBy(this.orderBySQL.toString()) .pageRow(pageRow) .build(); return Anima.me().getDialect().paginate(sqlParams); } private String buildInsertSQL(S model) { SQLParams sqlParams = SQLParams.builder() .model(model) .modelClass(this.modelClass) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .build(); return Anima.me().getDialect().insert(sqlParams); } private String buildUpdateSQL(S model, Map updateColumns) { SQLParams sqlParams = SQLParams.builder() .model(model) .modelClass(this.modelClass) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .updateColumns(updateColumns) .conditionSQL(this.conditionSQL) .build(); return Anima.me().getDialect().update(sqlParams); } private String buildDeleteSQL(S model) { SQLParams sqlParams = SQLParams.builder() .model(model) .modelClass(this.modelClass) .tableName(this.tableName) .pkName(this.primaryKeyColumn) .conditionSQL(this.conditionSQL) .build(); return Anima.me().getDialect().delete(sqlParams); } private void beforeCheck() { if (null == this.modelClass) { throw new AnimaException(ErrorCode.FROM_NOT_NULL); } } private static Connection getConn() { Connection connection = connectionThreadLocal.get(); if (null == connection) { return getSql2o().open(); } return connection; } public static void beginTransaction() { Connection connection = AnimaQuery.getSql2o().beginTransaction(); connectionThreadLocal.set(connection); } public static void endTransaction() { if (null != connectionThreadLocal.get()) { Connection connection = connectionThreadLocal.get(); if (connection.isRollbackOnClose()) { connection.close(); } connectionThreadLocal.remove(); } } public static void commit() { connectionThreadLocal.get().commit(); } public static void rollback() { if (null != connectionThreadLocal.get()) { log.warn("Rollback connection."); connectionThreadLocal.get().rollback(); } } public static Sql2o getSql2o() { Sql2o sql2o = Anima.me().getSql2o(); if (null == sql2o) { throw new AnimaException("SQL2O instance not is null."); } return sql2o; } private void setRelate(List models) { if (null == models || models.isEmpty()) { return; } if (!relations.contains(BelongsTo.class) && AnimaCache.hasBelongsTo(modelClass)) { this.setBelongs(models); } if (!relations.contains(HasMany.class) && AnimaCache.hasMany(modelClass)) { this.setHasMany(models); } if (!relations.contains(HasOne.class) && AnimaCache.hasOne(modelClass)) { this.setHasOne(models); } this.relations.clear(); } private void setBelongs(List models) { Arrays.stream(modelClass.getDeclaredFields()) .filter(field -> null != field.getAnnotation(BelongsTo.class)) .map(RelationParamBuilder::buildBelongsTo) .forEach(relationParams -> { if (models.size() == 1) { Object fkValue = AnimaUtils.getFieldValue(relationParams.getFk(), models.get(0)); Object fkVal = this.queryOne(relationParams.getType(), relationParams.getRelateSQL(), new Object[]{fkValue}); AnimaUtils.setFieldValue(relationParams.getFieldName(), models.get(0), fkVal); } else { Object[] pkIds = new Object[models.size()]; int pos = 0; StringBuilder placeholder = new StringBuilder(); for (T model : models) { Object fkValue = AnimaUtils.getFieldValue(relationParams.getFk(), model); pkIds[pos++] = fkValue; placeholder.append(",?"); } String belongSQL = "SELECT * FROM " + relationParams.getTableName() + " WHERE " + relationParams.getPk() + " in (" + placeholder.substring(1) + ")"; List list = this.queryList(relationParams.getType(), belongSQL, pkIds); for (int i = 0; i < models.size(); i++) { if (list.size() > i) { AnimaUtils.setFieldValue(relationParams.getFieldName(), models.get(i), list.get(i)); } } } }); } private void setHasMany(List models) { Arrays.stream(modelClass.getDeclaredFields()) .filter(field -> null != field.getAnnotation(HasMany.class)) .map(RelationParamBuilder::buildHasMany) .forEach(relationParams -> { for (T model : models) { if (null != model) { Object fkValue = AnimaUtils.getPKFieldValue(model); Object fkVal = this.queryList(relationParams.getType(), relationParams.getRelateSQL(), new Object[]{fkValue}); AnimaUtils.setFieldValue(relationParams.getFieldName(), model, fkVal); } } }); } private void setHasOne(List models) { Arrays.stream(modelClass.getDeclaredFields()) .filter(field -> null != field.getAnnotation(HasOne.class)) .map(RelationParamBuilder::buildHasOne) .forEach(relationParams -> { if (models.size() == 1) { Object fkValue = AnimaUtils.getPKFieldValue(models.get(0)); Object fkVal = this.queryOne(relationParams.getType(), relationParams.getRelateSQL(), new Object[]{fkValue}); AnimaUtils.setFieldValue(relationParams.getFieldName(), models.get(0), fkVal); } else { Object[] pkIds = new Object[models.size()]; int pos = 0; StringBuilder placeholder = new StringBuilder(); for (T model : models) { Object fkValue = AnimaUtils.getFieldValue(AnimaUtils.methodToFieldName(primaryKeyColumn), model); pkIds[pos++] = fkValue; placeholder.append(",?"); } String hasOneSQL = "SELECT * FROM " + relationParams.getTableName() + " WHERE " + relationParams.getFk() + " in (" + placeholder.substring(1) + ")"; List list = this.queryList(relationParams.getType(), hasOneSQL, pkIds); for (int i = 0; i < models.size(); i++) { if (list.size() > i) { AnimaUtils.setFieldValue(relationParams.getFieldName(), models.get(i), list.get(i)); } } } }); } private void clean(Connection conn) { this.selectColumns = null; this.isSQLLimit = false; this.orderBySQL = new StringBuilder(); this.conditionSQL = new StringBuilder(); this.paramValues.clear(); this.excludedColumns.clear(); this.updateColumns.clear(); if (null == connectionThreadLocal.get() && null != conn) { conn.close(); } } }