1

我在 Java 中构建了一个 _VERY_ 基本实用程序类来处理数据库操作(连接检索、插入等),如下所示:

// define the package name
package com.foo.bar.helpers;

// import all needed resources
import com.foo.bar.helpers.database.MySQL;
import com.foo.bar.helpers.database.SQLite;
import java.lang.reflect.Method;
import java.sql.Array;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * database
 * @author John Doe <...>
 */
class Database {
    // private constructor to prevent instantiation
    private Database() throws InstantiationException {
        // throw the appropriate exception
        throw new InstantiationException();
    }

    // database classes
    public static final String SQLITE_CLASS = SQLite.class.getCanonicalName();
    public static final String MYSQL_CLASS = MySQL.class.getCanonicalName();

    /**
     * returns a connection to the database using a set of parameters
     * @param parameters the connection parameters
     * @return a connection to the database
     * @author John Doe <...>
     */
    public static Connection getConnection(Object... parameters) {
        Connection output = null;

        try {
            if (parameters.length > 0) {
                // create an instance of the target class
                Class<?> target_class = Class.forName(parameters[0].getClass().getCanonicalName());

                // remove the first parameter (database class)
                Object[] class_parameters = Arrays.copyOfRange(parameters, 1, parameters.length);

                // retrieve the class type for each parameter
                Class<?>[] class_types = new Class[class_parameters.length];

                for (int i = 0; i < class_parameters.length; i++) {
                    class_types[i] = class_parameters[i].getClass();
                }

                // reflect the target class method
                Method class_method = target_class.getDeclaredMethod("getConnection", class_types);

                // output the database connection
                output = (Connection) class_method.invoke(null, class_parameters);
            } else {
                throw new Throwable("unable to establish a connection with the database (no parameters were provided)");
            }
        } catch (Throwable e) {
            // print the stack trace
            e.printStackTrace();
        }

        return output;
    }
}

除了数据库助手之外,我还有两个像这样的数据库连接器(MySQL 和 SQLite)(显示 MySQL 连接器):

// define the package name
package com.foo.bar.helpers.database;

// import all needed resources
import com.foo.bar.helpers.Configuration;
import com.foo.bar.helpers.Log;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;

/**
 * MySQL
 * @author John Doe <....>
 */
public class MySQL {
    // private constructor to prevent instantiation
    private MySQL() throws InstantiationException {
        // throw the appropriate exception
        throw new InstantiationException();
    }

    // connection key
    public static final String CONNECTION_KEY = "mysql";

    // default connection profile
    public static final String DEFAULT_CONNECTION_PROFILE = "default";

    /**
     * returns a connection to the database
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection() {
        Connection output = null;

        try {
            // compose the database connection profile key
            String profile_key = String.format("%s_%s", CONNECTION_KEY, DEFAULT_CONNECTION_PROFILE);

            // retrieve the database connection profile keyset
            HashMap<String, String> keyset = Configuration.getConfiguration(profile_key);

            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", keyset.get("host"), keyset.get("port"), keyset.get("schema")), keyset.get("username"), keyset.get("password"));
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }

    /**
     * returns a connection to the database
     * @param profile the database configuration profile
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection(String profile) {
        Connection output = null;

        try {
            // compose the database connection profile key
            String profile_key = String.format("%s_%s", CONNECTION_KEY, profile);

            // retrieve the database connection profile keyset
            HashMap<String, String> keyset = Configuration.getConfiguration(profile_key);

            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", keyset.get("host"), keyset.get("port"), keyset.get("schema")), keyset.get("username"), keyset.get("password"));
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }

    /**
     * returns a connection to the database
     * @param host the database host
     * @param port the database port
     * @param schema the database schema
     * @param username the database username
     * @param password the database user password
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection(String host, int port, String schema, String username, String password) {
        Connection output = null;

        try {
            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", host, port, schema), username, password);
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }
}

免责声明:请不要过分关注诸如使用snake_case(更多的是Javascript / PHP / Python / R-ish)变量命名约定,使用Throwable而不是,我正在构建实用程序类而不是完整Exception的事实- 成熟的类及其方法、属性以及其域(公共、私有、受保护等)正确设置的所有内容,以及许多其他应该存在但不存在的东西。这是(实际上)我使用 Java 的第二周,我非常愿意提出改进建议,我承认这里还有很多工作要做,所以要仁慈:P

也就是说,如果我尝试这样做:

// define the package name
package com.foo.xxxxxxxx;

// import all needed resources
import com.foo.bar.helpers.Database;
import java.sql.Connection;

/**
 * application
 * @author John Doe <...>
 */
class Application {
    public static void main(String[] args) {
        Connection connection = Database.getConnection(Database.MYSQL_CLASS, "localhost", 3306, "foo_db", "john_doe_123", "this_is_not_my_real_password!");
    }
}

我明白了:

java.lang.NoSuchMethodException: java.lang.String.getConnection(java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String)
    at java.base/java.lang.Class.getDeclaredMethod(Class.java:2475)
    at com.foo.bar.helpers.Database.getConnection(Database.java:146)
    at com.foo.xxxxxxxx.Application.main(Application.java:61)

如果我确实正确阅读了文档,我需要获取我打算反映的类的实例,获取我想要使用的特定方法getDeclaredMethod(因为我的任何实用程序类中的每个方法都是静态的),方法名称为String和可变数量的参数(或一个数组,如果我使用正确的话)以及我将要调用该方法的每个参数的类类型。

完成后,我需要调用null作为第一个参数传递的方法(因为它是一个静态方法,而静态方法不需要我试图为其调用特定方法的类的实例)和可变数量的参数(相同和以前一样)与参数本身。

我得到的那个错误e.printStackTrace()告诉我方法获取失败,或者是因为我没有正确指定类类型(我很怀疑使用Class<?>[]而不是,Class[]但 IntelliJ 抱怨Raw use of parameterized class 'Class')或者我没有真正得到一个实例我打算从中获取实例的类,而我得到了某种通用类对象(所以我看不到我正在寻找的方法)。

或者可能是因为我声明了一个私有构造函数来避免实例化(但我认为,在阅读了一些文章之后,实用程序类(如果你真的需要使用它们)应该有一个来避免实例化......因此私有构造函数声明)但是,不管怎样,我现在有点搞砸了:(

这个想法是能够连接到任何给定的数据库(因为现在只有 MySQL 和 SQLite,但将来可能是 Amazon Redshift、BigQuery、PostgreSQL、Oracle 等),但我可能会想到以错误的方式进行通用访问。

你能给我一个提示吗?

4

1 回答 1

1

您给出的异常暗示您试图getConnection()在类中找到该方法java.lang.String。我怀疑你没有把它放在那里,所以它不会找到任何东西。

Database#getConnection课堂上我注意到以下陈述

Class<?> target_class = Class.forName(parameters[0].getClass().getCanonicalName());

这基本上意味着您将第一个参数作为类对象处理。(您首先获取类类型实例,然后从那里获取名称)。但是在你的测试中,你给它一个字符串类型的参数。

所以这里有一点需要注意。每个对象(即每个非原始值)都有一个类类型,甚至返回的类类型本身。如果不小心,这会让人感到困惑。


因此,我可以在几秒钟内想到 3 种情况,您可以处理这个特殊问题:

传递一个Class实例

Database.getConnection(MySQL.class,  ... );

// in the #getConnection class
Class<?> target_class = parameters[0] // type is already a class, so just assign

传递所需类类型的实例,例如

Database.getConnection(new MySQL() ,  ... ); // defenitly not recommended, only really useable if an instance itself is needed (e.g. Non-static access) 

// in the #getConnection class
Class<?> target_class = parameters[0].getClass() // get the class-type instance

传递所需类类型的字符串表示形式(规范名称)

Database.getConnection(MtSQL.DB_CLASS_NAME,  ... ); // pass String type argument

// in the #getConnection class
Class<?> target_class = Class.forName(parameters[0]) // the #forName needs a String argument, so we can pass it directly.

在最新的示例中,您可以使用ClassLoader's 等。它提供了很好的功能,例如缓存和类卸载。但它相当复杂,所以可能不适合你的第一个方法。

最后,作为一般建议。Java 是强类型的,具有方法重载等特性。所以为了你自己的理智,尽量(ab)使用它。上述 3 种情况很容易被重载,从而使参数验证成为一项不那么痛苦的任务。它也使 API 用户使用它“万无一失”,因为在编译过程中会注意到类型不匹配。

于 2020-06-07T20:32:38.137 回答