从数据库中填充枚举的值

29

我有一个将字符串映射为整数的表。

我想从数据库中获取值来填充这个枚举类型,而不是静态地创建一个枚举。这种做法可行吗?

所以,我不希望像这样静态声明枚举:

public enum Size { SMALL(0), MEDIUM(1), LARGE(2), SUPERSIZE(3) };

我希望动态创建此枚举,因为数字 {0,1,2,3} 实际上是随机的(因为它们是由数据库的自增列自动生成的)。


重复:https://dev59.com/aHRB5IYBdhLWcg3wFz8g - aleemb
4
除了那是针对C#,而不是Java的,我们都知道你可以用Java做更多的事情。 - paxdiablo
1
可能是在C#中的动态枚举的重复问题。 - StayOnTarget
1
从概念上讲,我们大多数人都会理解,在运行时无法从数据库构建编译时的枚举值集。至少你会遇到枚举常量事先不知道的困境。枚举常量在类加载时作为单个单例可用,这是我们最希望得到的便利。如果解决了单例问题,那么值就像内部化版本,直接使用==比较即可。如果另外确保单例在应用程序加载期间自动初始化,那么我们可以避免使用枚举。 - YoYo
6个回答

37

不行。枚举在编译时总是固定的。你能做的唯一方式就是动态生成相关字节码。

尽管如此,你应该明确自己真正感兴趣的枚举方面。假设你不想在它们上使用 switch 语句,因为那意味着静态代码且你不知道静态值...同样,代码中的其他任何引用也是如此。

如果你只需要一个从 StringInteger 的映射表,你可以在执行时使用 Map<String, Integer> 并填充内容,然后完成任务。如果你需要 EnumSet 功能,要以相同效率进行复制将会有点棘手,但是通过一些努力可能是可行的。

因此,在思考实现的任何进一步步骤之前,建议您先确定自己的真正需求。

(编辑:我一直假定这个枚举是完全动态的,即你不知道名称甚至不知道有多少值。如果名称集合是固定的,而你仅仅需要从数据库获取 ID,那就完全不同了 - 参见Andreas' answer。)


1
如果您想在运行时动态生成代码并编译它,请查看BeanShell。 - Peter Lawrey

28

这有点棘手,因为那些值的种群发生在类加载时期。所以你需要对数据库连接进行静态访问。

虽然我非常重视他的回答,但我认为这次Jon Skeet可能是错的。

看一下这个:

public enum DbEnum {
    FIRST(getFromDb("FIRST")), SECOND(getFromDb("second"));

    private static int getFromDb(String s) {
        PreparedStatement statement = null;
        ResultSet rs = null;
        try {
            Connection c = ConnectionFactory.getInstance().getConnection();
            statement = c.prepareStatement("select id from Test where name=?");
            statement.setString(1, s);
            rs = statement.executeQuery();
            return rs.getInt(1);

        }
        catch (SQLException e) {
           throw new RuntimeException("error loading enum value for "+s,e);
        }
        finally {
            try {
                rs.close();
                statement.close();
            } catch (SQLException e) {
                //ignore
            }
        }
        throw new IllegalStateException("have no database");
    }

    final int value;

    DbEnum(int value) {
        this.value = value;
    }
}

7
你认为在编译时就知道了有多少值以及它们应该被称为什么。如果是这样的话,那很好-但我在问题中没有看到任何暗示。 (我认为你的查询应该选择ID,而不仅仅是1 :)) - Jon Skeet
2
一份提醒我们关于枚举的面向对象方面的信息响应 - 谢谢,Andreas。 - Kevin Day
你可以使用反射来填充值,从而使代码更加DRY。这样,你只需要在初始化代码中查找名称,然后调用FIRST()即可。 - Rich
“getFromDb”方法是否会泄漏连接?连接既没有关闭也没有返回到池中。此外,如果在“rs.close()”之前抛出异常,则不再调用“statement.close()”。而这个关闭语句本来就会被“statement.close()”隐式执行。 - MrSnrub

6

Andreas所做的基础上,您可以将数据库内容加载到映射中,以减少所需的数据库连接数量。

public enum DbEnum {
    FIRST(getFromDb("FIRST")),
    SECOND(getFromDb("second"));

    private Map<String,Integer> map;
    private static int getFromDB(String s)
    {
        if (map == null)
        {
           map = new HashMap<String,Integer>();
           // Continue with database code but get everything and
           // then populate the map with key-value pairs.
           return map.get(s);
        }
        else {
            return map.get(s); }
    }
}

在此提及此想法是很好的。但请明确,这段代码在任何现实(也就是说多线程)的生产环境中都表现得十分糟糕。如果您的枚举在应用程序中被广泛使用,那么这个初始化代码注定会返回完全不可靠的值。 - Marco Faustinelli

3

枚举类型是不可变的,所以简短的回答是你不能这样做。

同时,可以参考 Stack Overflow 上的问题 C# 中动态枚举


1

你需要在代码中复制数据库中的内容(或反之亦然)。请参考这个问题获取一些好的建议。


0
在我所了解的所有语言中,枚举都是静态的。编译器可以对它们进行一些优化。因此,简短的答案是否定的。
问题是为什么你想以这种方式使用枚举。你期望什么? 换句话说,为什么不使用集合呢?

传统代码中,我们经常使用枚举,现在我们希望能够从数据库动态加载它。如果您可以即时加载枚举,那么可以节省大量的重构工作... - Brian Knoblauch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接