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

net.freeutils.util.db.ResultsHandlers Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2003-2024 Amichai Rothman
 *
 *  This file is part of JElementary - the Java Elementary Utilities package.
 *
 *  JElementary is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JElementary is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JElementary.  If not, see .
 *
 *  For additional info see https://www.freeutils.net/source/jelementary/
 */

package net.freeutils.util.db;

import net.freeutils.util.Reflect;
import net.freeutils.util.Strings;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * A utility class containing commonly used ResultsHandler implementations.
 */
public class ResultsHandlers {

    // private constructor to prevent instantiation
    private ResultsHandlers() {}

    /**
     * A ResultsHandler that returns true if there are any results
     * (the result set is not empty), or false if there are no results
     * (the result set is empty).
     */
    public static final ResultsHandler EXISTS_HANDLER = new ResultsHandler() {
        @Override
        public Boolean handle(ResultSet rs) throws SQLException {
            return rs.next();
        }
    };

    /**
     * A ResultsHandler that returns the object at the first column of the first row,
     * or null if the result set is empty.
     */
    public static final ResultsHandler SCALAR_HANDLER = new ResultsHandler() {
        @Override
        public Object handle(ResultSet rs) throws SQLException {
            return rs.next() ? rs.getObject(1) : null;
        }
    };

    /**
     * A ResultsHandler that returns the objects of the first row as an array
     * (in column order), or null if the result set is empty.
     */
    public static final ResultsHandler ARRAY_HANDLER = new ResultsHandler() {
        @Override
        public Object[] handle(ResultSet rs) throws SQLException {
            if (!rs.next())
                return null;
            int len = rs.getMetaData().getColumnCount();
            Object[] arr = new Object[len];
            for (int i = 0; i < len; i++)
                arr[i] = rs.getObject(i + 1);
            return arr;
        }
    };

    /**
     * A ResultsHandler that returns the objects in all rows as list of arrays,
     * one for each row, or an empty list if the result set is empty.
     */
    public static final ResultsHandler> ARRAY_LIST_HANDLER = new ResultsHandler>() {
        @Override
        public List handle(ResultSet rs) throws SQLException {
            List list = new ArrayList<>();
            int len = rs.getMetaData().getColumnCount();
            while (rs.next()) {
                Object[] arr = new Object[len];
                for (int i = 0; i < len; i++)
                    arr[i] = rs.getObject(i + 1);
                list.add(arr);
            }
            return list;
        }
    };

    /**
     * A ResultsHandler class that converts the first result row to a bean
     * object of a specified class and sets its properties from the column
     * data, or returns null if the result set is empty.
     *
     * @param  the bean type
     */
    public static class BeanResultsHandler implements ResultsHandler {

        protected final Class cls;
        protected final boolean ignoreErrors;

        /**
         * Creates a new BeanResultsHandler for the given bean type.
         *
         * @param cls          the bean type
         * @param ignoreErrors specifies whether to ignore errors when
         *                     setting bean properties that cannot be found
         */
        public BeanResultsHandler(Class cls, boolean ignoreErrors) {
            this.cls = cls;
            this.ignoreErrors = ignoreErrors;
        }

        /**
         * Creates a new BeanResultsHandler for the given bean type.
         *
         * @param cls the bean type
         */
        public BeanResultsHandler(Class cls) {
            this(cls, false);
        }

        @Override
        public T handle(ResultSet rs) throws SQLException {
            return rs.next() ? toBean(rs, rs.getMetaData()) : null;
        }

        /**
         * Converts the current row of the given result set, with its
         * associated metadata, into a bean.
         *
         * @param rs   a result set whose current row is not after the last row
         * @param meta the result set's metadata (note that while this can be
         *             retrieved from the result set by this method's implementation,
         *             this can potentially have a performance impact in some JDBC
         *             implementations if this method is invoked multiple times with
         *             the same result set; thus it is taken instead as a parameter
         *             to this method, so the caller need retrieve it only once)
         * @return the bean representing the row
         * @throws SQLException             if an error occurs
         * @throws IllegalArgumentException if this instance does not ignore
         *                                  errors and a column in the result set cannot be assigned
         *                                  to a corresponding bean property
         */
        protected T toBean(ResultSet rs, ResultSetMetaData meta) throws SQLException {
            T bean;
            int len = meta.getColumnCount();
            try {
                bean = cls.getDeclaredConstructor().newInstance();
                for (int i = 1; i <= len; i++)
                    setProperty(bean, rs, meta, i);
            } catch (Exception e) {
                throw new SQLException("error converting results to bean", e);
            }
            return bean;
        }

        /**
         * Sets a bean property using the value from a given
         * result set column, converting it if necessary.
         *
         * @param bean   the bean on which the property is set
         * @param rs     a result set whose current row contains the column value
         * @param meta   the result set's meta data
         * @param column the index of the column within the result set row
         * @throws SQLException             if an error occurs
         * @throws IllegalArgumentException if this instance does not ignore
         *                                  errors and the column in the result set cannot be assigned
         *                                  to a corresponding bean property
         */
        protected void setProperty(T bean, ResultSet rs, ResultSetMetaData meta, int column) throws SQLException {
            setProperty(bean, meta.getColumnName(column), rs.getObject(column));
        }

        /**
         * Sets a named bean property to the given value.
         *
         * @param bean  the bean on which the property is set
         * @param name  the property name
         * @param value the property value to set
         * @throws IllegalArgumentException if this instance does not ignore
         *                                  errors and the column in the result set cannot be assigned
         *                                  to a corresponding bean property
         */
        protected void setProperty(T bean, String name, Object value) {
            String field = Strings.toCamelCase(name);
            if (!Reflect.setProperty(bean, field, value) && !ignoreErrors)
                throw new IllegalArgumentException("can't find bean property " + field);
        }
    }

    /**
     * A ResultsHandler class that converts the result rows to a list
     * of bean objects using a {@link BeanResultsHandler} for each,
     * or returns an empty list if the result set is empty.
     *
     * @param  the bean type
     */
    public static class BeanListResultsHandler implements ResultsHandler> {

        protected final BeanResultsHandler handler;

        /**
         * Creates a new BeanResultsHandler that will use the given
         * {@link BeanResultsHandler} to handle each bean.
         *
         * @param handler the BeanResultsHandler
         */
        public BeanListResultsHandler(BeanResultsHandler handler) {
            this.handler = handler;
        }

        /**
         * Creates a new BeanListResultsHandler for the given bean type.
         *
         * @param cls          the bean type
         * @param ignoreErrors specifies whether to ignore errors when
         *                     setting bean properties that cannot be found
         */
        public BeanListResultsHandler(Class cls, boolean ignoreErrors) {
            this(new BeanResultsHandler<>(cls, ignoreErrors));
        }

        /**
         * Creates a new BeanResultsHandler for the given bean type.
         *
         * @param cls the bean type
         */
        public BeanListResultsHandler(Class cls) {
            this(new BeanResultsHandler<>(cls));
        }

        @Override
        public List handle(ResultSet rs) throws SQLException {
            ResultSetMetaData meta = rs.getMetaData();
            List list = new ArrayList<>();
            while (rs.next())
                list.add(handler.toBean(rs, meta));
            return list;
        }
    }
}