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

win.doyto.query.core.JdbcDataAccess Maven / Gradle / Ivy

package win.doyto.query.core;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.jdbc.core.*;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import win.doyto.query.entity.Persistable;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Transient;

import static win.doyto.query.core.Constant.SEPARATOR;

/**
 * JdbcDataAccess
 *
 * @author f0rb
 */
public final class JdbcDataAccess, I extends Serializable, Q extends PageQuery> implements DataAccess {

    private static final Map classRowMapperMap = new ConcurrentHashMap<>();
    private final JdbcOperations jdbcOperations;
    private final RowMapper rowMapper;
    private final CrudBuilder crudBuilder;
    private final String[] columnsForSelect;
    private final boolean isGeneratedId;
    private final BiFunction setIdFunc;

    @SuppressWarnings("unchecked")
    public JdbcDataAccess(JdbcOperations jdbcOperations, Class entityClass, Class idClass, RowMapper rowMapper) {
        this.jdbcOperations = jdbcOperations;
        this.rowMapper = rowMapper;
        crudBuilder = new CrudBuilder<>(entityClass);
        columnsForSelect = Arrays
            .stream(FieldUtils.getAllFields(entityClass))
            .filter(JdbcDataAccess::shouldRetain)
            .map(CommonUtil::selectAs)
            .toArray(String[]::new);

        Field[] idFields = FieldUtils.getFieldsWithAnnotation(entityClass, Id.class);
        isGeneratedId = idFields.length == 1 && idFields[0].isAnnotationPresent(GeneratedValue.class);

        if (idClass.isAssignableFrom(Integer.class)) {
            setIdFunc = (e, key) -> {
                e.setId((I) (Integer) key.intValue());
                return null;
            };
        } else if (idClass.isAssignableFrom(Long.class)) {
            setIdFunc = (e, key) -> {
                e.setId((I) (Long) key.longValue());
                return null;
            };
        } else {
            setIdFunc = (e, key) -> {
                e.setId((I) key);
                return null;
            };
        }
    }

    private static boolean shouldRetain(Field field) {
        return !field.getName().startsWith("$")              // $jacocoData
            && !Modifier.isStatic(field.getModifiers())      // static field
            && !field.isAnnotationPresent(Transient.class)   // Transient field
            ;
    }

    @Override
    public final List query(Q q) {
        return queryColumns(q, rowMapper, columnsForSelect);
    }

    @Override
    @SuppressWarnings("unchecked")
    public final  List queryColumns(Q q, Class clazz, String... columns) {
        columns = StringUtils.join(columns, SEPARATOR).split(",");
        RowMapper customRowMapper;
        if (Map.class.isAssignableFrom(clazz)) {
            customRowMapper = new ColumnMapRowMapper();
        } else {
            customRowMapper = classRowMapperMap.computeIfAbsent(clazz, columns.length == 1 ? SingleColumnRowMapper::new : BeanPropertyRowMapper::new);
        }
        return queryColumns(q, customRowMapper, columns);
    }

    private  List queryColumns(Q q, RowMapper rowMapper, String... columns) {
        SqlAndArgs sqlAndArgs = crudBuilder.buildSelectColumnsAndArgs(q, columns);
        return jdbcOperations.query(sqlAndArgs.sql, sqlAndArgs.args, rowMapper);
    }

    @Override
    public final long count(Q q) {
        SqlAndArgs sqlAndArgs = crudBuilder.buildCountAndArgs(q);
        return jdbcOperations.queryForObject(sqlAndArgs.sql, sqlAndArgs.args, Long.class);
    }

    @Override
    public final int delete(Q q) {
        return doUpdate(crudBuilder.buildDeleteAndArgs(q));
    }

    @Override
    public final E get(I id) {
        return getEntity(crudBuilder.buildSelectById(id, columnsForSelect));
    }

    @Override
    public final int delete(I id) {
        return jdbcOperations.update(crudBuilder.buildDeleteById(), id);
    }

    @Override
    public final E get(E e) {
        return getEntity(crudBuilder.buildSelectById(e, columnsForSelect));
    }

    private E getEntity(SqlAndArgs sqlAndArgs) {
        List list = jdbcOperations.query(sqlAndArgs.sql, sqlAndArgs.args, rowMapper);
        return list.isEmpty() ? null : list.get(0);
    }

    @Override
    public final int delete(E e) {
        return jdbcOperations.update(crudBuilder.buildDeleteById(e), e.getId());
    }

    @Override
    public final void create(E e) {
        List args = new ArrayList<>();
        String sql = crudBuilder.buildCreateAndArgs(e, args);

        if (isGeneratedId) {
            KeyHolder keyHolder = new GeneratedKeyHolder();
            jdbcOperations.update(connection -> {
                PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                int i = 1;
                for (Object arg : args) {
                    ps.setObject(i++, arg);
                }
                return ps;
            }, keyHolder);
            setIdFunc.apply(e, keyHolder.getKey());
        } else {
            jdbcOperations.update(sql, args.toArray());
        }
    }

    @Override
    public int batchInsert(Iterable entities, String... columns) {
        if (!entities.iterator().hasNext()) {
            return 0;
        }
        return doUpdate(crudBuilder.buildCreateAndArgs(entities, columns));
    }

    private int doUpdate(SqlAndArgs sqlAndArgs) {
        return jdbcOperations.update(sqlAndArgs.sql, sqlAndArgs.args);
    }

    @Override
    public final int update(E e) {
        return doUpdate(crudBuilder.buildUpdateAndArgs(e));
    }

    @Override
    public final int patch(E e) {
        return doUpdate(crudBuilder.buildPatchAndArgsWithId(e));
    }

    @Override
    public final int patch(E e, Q q) {
        return doUpdate(crudBuilder.buildPatchAndArgsWithQuery(e, q));
    }

    @Override
    public List queryIds(Q query) {
        SqlAndArgs sqlAndArgs = crudBuilder.buildSelectIdAndArgs(query);
        return jdbcOperations.query(sqlAndArgs.sql, sqlAndArgs.args, new SingleColumnRowMapper<>());
    }

}