Java、Mysql、MyBatis 中枚举 enum 的使用
From: https://yulaiz.com/java-mysql-enum/
Java 和 MySql 中都有枚舉的概念,合理的使用枚舉,可以讓代碼閱讀和數(shù)據(jù)庫數(shù)據(jù)查詢更加直觀、高效。那么我們怎么使用呢,什么時候使用,兩者之間怎么進行數(shù)據(jù)關聯(lián)呢?(本文使用 MyBatis 做為 Java 與 MySql 之間的關聯(lián))
文章目錄 [hide]
- 1 1. 當年我們怎么定義狀態(tài)
- 1.1 1.1 數(shù)據(jù)庫設計
- 1.2 1.2 Java Bean 代碼
- 1.3 1.3 MyBatis 代碼
- 1.4 1.4 代碼效果
- 2 2. 現(xiàn)在我們怎么定義狀態(tài)
- 2.1 2.1 Java Bean 代碼
- 2.2 2.2 數(shù)據(jù)庫設計
- 2.3 2.3 MyBatis 代碼
- 2.4 2.4 代碼效果
- 3 3. 怎么把當年定義的狀態(tài)改成使用枚舉
- 3.1 3.1 數(shù)據(jù)庫設計
- 3.2 3.2 Java Bean 代碼
- 3.3 3.3 MyBatis 代碼
- 3.3.1 3.3.1 內(nèi)置枚舉轉(zhuǎn)換器
- 3.3.1.1 3.3.1.1 EnumTypeHandler
- 3.3.1.2 3.3.1.2 EnumOrdinalTypeHandler
- 3.3.2 3.3.2 自定義枚舉轉(zhuǎn)換器
- 3.3.3 3.3.3 在 Mapper.xml 中配置自定義的枚舉轉(zhuǎn)換器
- 3.3.3.1 方法1:修改指定xml文件,指定的Mapper生效
- 3.3.3.2 方法2:全局指定枚舉轉(zhuǎn)換器,無需單獨修改Mapper.xml
- 3.3.3.3 方法3:不使用枚舉轉(zhuǎn)換器
- 3.3.1 3.3.1 內(nèi)置枚舉轉(zhuǎn)換器
- 4 4. 枚舉到底好不好用
- 4.1 4.1 Java 枚舉的實現(xiàn)
- 4.1.1 4.1.1 基礎聲明、屬性、構(gòu)造函數(shù)
- 4.1.2 4.1.2 常用方法
- 4.1.2.1 4.1.2.1 name() 方法
- 4.1.2.2 4.1.2.2 ordinal() 方法。
- 4.1.2.3 4.1.2.3 toString() 方法
- 4.1.2.4 4.1.2.4 equals(Object other) 方法
- 4.1.2.5 4.1.2.5 compareTo(E o) 方法
- 4.1.2.6 4.1.2.6 values() 方法
- 4.1.2.7 4.1.2.7 valueOf(String name) 方法
- 4.1.3 4.1.3 比較兩個枚舉值是否一致
- 4.2 4.2 MySql 中的枚舉
- 4.2.1 4.2.1 DDL 操作效率
- 4.2.1.1 4.2.1.1 新增一個枚舉狀態(tài)
- 4.2.1.2 4.2.1.2 修改一個枚舉狀態(tài)
- 4.2.1.3 4.2.1.3 刪除一個枚舉狀態(tài)
- 4.2.2 4.2.2 查詢效率
- 4.2.1 4.2.1 DDL 操作效率
- 4.1 4.1 Java 枚舉的實現(xiàn)
- 5 5. 枚舉使用總結(jié)
- 6 6. 參考鏈接
1. 當年我們怎么定義狀態(tài)
我們定義狀態(tài)變量時,通常需要約定幾個狀態(tài),比如交易訂單中,我們就有常見的創(chuàng)建、交易中、支付成功、支付失敗等等狀態(tài),當年我們都是約定好 0,1,2,3,4,5 這樣的字段來表示上述幾個字段。
1.1 數(shù)據(jù)庫設計
例如我們的數(shù)據(jù)庫設計時,直接這樣描述:
CREATE TABLE `order_test` (`id` int(20) NOT NULL AUTO_INCREMENT,`status` int(1) NOT NULL COMMENT '0-創(chuàng)建,1-支付中,2-支付成功,3-支付失敗,4-取消訂單',PRIMARY KEY (`id`) USING BTREE )SQL
0-創(chuàng)建,1-支付中,2-支付成功,3-支付失敗,4-取消訂單 這就是我們的約定了。
當然也有惡心的設計數(shù)據(jù)庫直接使用 varchar 數(shù)據(jù)類型,這就更惡心了,搜索都不好了。
1.2 Java Bean 代碼
在 Java 中我們代碼這樣寫:
public class OrderInfo {private int id;//0-創(chuàng)建,1-支付中,2-支付成功,3-支付失敗,4-取消訂單private int status; }Java
有時候牛逼點,我們定義點常量:
public interface class OrderConstants {//創(chuàng)建int ORDER_STATUS_CREATE = 0;//支付中int ORDER_STATUS_PAYING = 1;//支付成功int ORDER_STATUS_IN_PROGRESS = 2;//支付失敗int ORDER_STATUS_FAILED = 3;//取消訂單int ORDER_STATUS_REVERSED = 4; }Java
1.3 MyBatis 代碼
然后用 MyBatis 插入查詢什么的時候也簡單:
@Mapper public interface OrderMapper {int addOrder(@Param("item") OrderInfo info);OrderInfo getOrderById(@Param("id") String id); }Java
<insert id="addOrder" parameterType="com.yulaiz.model.order.entity.OrderInfo">INSERT INTO order_test ( status )VALUES (#{item.status}) </insert> <select id="getOrderById" resultType="com.yulaiz.model.order.entity.OrderInfo">SELECT id, statusFROM order_testWHERE id = #{id} </select>XML
1.4 代碼效果
我們最后拿到的就是 0,1,2,3,4 這樣的數(shù)值來表示訂單狀態(tài)了,當然現(xiàn)在的例子實在簡單,實際項目中,那可不僅僅就這么 0-4,還有很多,并且一個訂單的狀態(tài),可不僅僅就這些,還有其他字段來描述,那么在一些復雜的查詢中,那就亂套了,一堆狀態(tài),都是數(shù)字,沒辦法一個個查吧,這可是真麻煩。
2. 現(xiàn)在我們怎么定義狀態(tài)
2.1 Java Bean 代碼
一般在我設計枚舉字段的時候,我會先設計 Java 部分的枚舉字段,因為嘛,我覺得這樣粘貼復制方便一點:
public enum OrderStatus {CREATE("創(chuàng)建"),PAYING("支付中"),IN_PROGRESS("支付成功"),FAILED("支付失敗"),REVERSED("取消訂單");private String value;OrderStatus(String value) {this.value = value;}public String getValue() {return value;} }Java
在使用中也很簡單,首先 Java Bean 設計時直接將數(shù)據(jù)類型改為 OrderStatus :
public class OrderInfo {private int id;private OrderStatus status; }Java
賦值的時候使用:
orderInfo.setStatus(OrderStatus.CREATE);Java
2.2 數(shù)據(jù)庫設計
在 Mysql 中這樣描述 enum 字段:
CREATE TABLE `order_test` (`id` int(20) NOT NULL AUTO_INCREMENT,`status` enum('CREATE','PAYING','IN_PROGRESS','FAILED','REVERSED') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,PAYING-支付中,IN_PROGRESS-支付成功,FAILED-支付失敗,REVERSED-取消訂單',PRIMARY KEY (`id`) USING BTREE )SQL
2.3 MyBatis 代碼
接著 MyBatis 中插入查詢完全不用修改,還是上次那個原樣就ok,是不是很簡單。
2.4 代碼效果
這樣一來我們在 Java 代碼中就可以直接看到這個狀態(tài)是 CREATE 或者 PAYING 或者其他狀態(tài),在sql查詢中也會顯示 CREATE 或者 PAYING 或者其他狀態(tài),清晰明了,不需要再去一個個的查字段定義,非常方便閱讀。
其中Java代碼里枚舉類有一個隱藏的屬性叫 name 就是我們定義時候使用的 CREATE("創(chuàng)建"),PAYING("支付中") 當中的 CREATE 和 PAYING ,MyBatis在插入數(shù)據(jù)庫的時候,會自動使用這個 name 屬性作為賦值,而查詢的時候也是通過數(shù)據(jù)庫查詢的內(nèi)容和 name 屬性進行匹配。
3. 怎么把當年定義的狀態(tài)改成使用枚舉
好了,由于本人比較懶,發(fā)現(xiàn)枚舉的好處之后就覺得,當年寫的到底是什么垃圾代碼,由此也就想把之前定義的都替換成枚舉。
3.1 數(shù)據(jù)庫設計
首先,咱從底層入手,先看看數(shù)據(jù)庫。
要想修改成枚舉,那么我就要先新增一個枚舉字段,然后根據(jù)原有的映射,來分別 update 對應數(shù)據(jù),但是這樣是很糟糕的操作:
那么數(shù)據(jù)庫就先不改了,咱直接把新功能用上枚舉吧,之前的代碼不改動了,新功能的 Java 部分直接用上枚舉,盡量讓自己寫代碼舒服點,看著爽一些。
3.2 Java Bean 代碼
我們?nèi)耘f想使用 orderInfo.setStatus(OrderStatus.CREATE); 的方式進行賦值,但是現(xiàn)在數(shù)據(jù)庫中的數(shù)據(jù)是 0,1,2... 跟枚舉類 OrderStatus 的 name屬性 不能對應上了。
首先我們還是先寫 Java 的枚舉,由于這次要跟數(shù)據(jù)庫對應上,數(shù)據(jù)結(jié)構(gòu)就有點區(qū)別了:
public enum OrderStatus {CREATE(0, "創(chuàng)建"),PAYING(1, "支付中"),IN_PROGRESS(2, "支付成功"),FAILED(3, "支付失敗"),REVERSED(4, "取消訂單");private int value;private String desc;OrderStatus(int value, String desc) {this.value = value;this.desc = desc;}public int getValue() {return value;}public String getDesc() {return desc;} }Java
當然,也可以:
public enum OrderStatus {CREATE(0),PAYING(1),IN_PROGRESS(2),FAILED(3),REVERSED(4);private int value;OrderStatus(int value) {this.value = value;}public int getValue() {return value;} }Java
但我覺得還是加個中文方便,那么我就想用 CREATE(0, "創(chuàng)建") 這種方式。
3.3 MyBatis 代碼
實際上這里的關鍵就是 MyBatis 了,怎么樣讓 MyBatis 知道我們在賦值 orderInfo.setStatus(OrderStatus.CREATE); 的時候用 CREATE(0, "創(chuàng)建") 中的 0 而不是 CREATE 呢。
首先,我們先看看 MyBatis 是否能夠滿足我們的需求。MyBatis 內(nèi)置了兩個枚舉轉(zhuǎn)換器分別是org.apache.ibatis.type.EnumTypeHandler 和 org.apache.ibatis.type.EnumOrdinalTypeHandler。
3.3.1 內(nèi)置枚舉轉(zhuǎn)換器
3.3.1.1 EnumTypeHandler
這是默認的枚舉轉(zhuǎn)換器,轉(zhuǎn)換器將枚舉實例轉(zhuǎn)換為實例名稱的字符串,即 name 屬性,也就是將 OrderStatus.CREATE 轉(zhuǎn)換為 CREATE。就是我們在 2.現(xiàn)在我們怎么定義狀態(tài) 中所使用的方式。
3.3.1.2 EnumOrdinalTypeHandler
這個轉(zhuǎn)換器將枚舉實例的 ordinal 屬性作為取值,這個屬性可以通過 orderInfo.getStatus().ordinal() 來獲取。
public final int ordinal()Null
Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial constant is assigned an ordinal of zero). Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data structures, such as EnumSet and EnumMap.
- Returns:
the ordinal of this enumeration constant
通過官方文檔并且通過自己的實驗來看,這個 ordinal 屬性其實是一個序號,這個序號從 0 開始,只跟定義枚舉類時的順序有關。
當我們按照下述方式定義枚舉時:
public enum OrderStatus {CREATE("創(chuàng)建"),PAYING("支付中"),IN_PROGRESS("支付成功"),FAILED("支付失敗"),REVERSED("取消訂單");//省略部分代碼... }Java
其 ordinal 屬性就是跟這個先后順序有關,OrderStatus.CREATE.ordinal()==0 OrderStatus.PAYING.ordinal()==1 剛好跟我們后來自定義的 value 屬性同步了。
但是我們不能相信這個屬性,所以 MyBatis 提供的兩種枚舉轉(zhuǎn)換器均不適用,我們也就只好繼續(xù)自定義了。
3.3.2 自定義枚舉轉(zhuǎn)換器
MyBatis 提供了 org.apache.ibatis.type.BaseTypeHandler 類用于我們自己擴展類型轉(zhuǎn)換器,上面的 EnumTypeHandler 和 EnumOrdinalTypeHandler 也都實現(xiàn)了這個接口。
public class EnumOrderStatusHandler extends BaseTypeHandler<OrderStatus> {/*** 設置配置文件設置的轉(zhuǎn)換類以及枚舉類內(nèi)容,供其他方法更便捷高效的實現(xiàn)** @param type 配置文件中設置的轉(zhuǎn)換類*/public EnumOrderStatusHandler(Class<OrderStatus> type) {if (type == null)throw new IllegalArgumentException("Type argument cannot be null");this.type = type;this.enums = type.getEnumConstants();if (this.enums == null)throw new IllegalArgumentException(type.getSimpleName()+ " does not represent an enum type.");}//用于定義設置參數(shù)時,該如何把Java類型的參數(shù)轉(zhuǎn)換為對應的數(shù)據(jù)庫類型@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, OrderStatus parameter, JdbcType jdbcType) throws SQLException {// 根據(jù)數(shù)據(jù)庫存儲類型決定獲取類型,本例子中數(shù)據(jù)庫中存放int類型// ps.setStringps.setInt(i, parameter.getValue());}//用于定義通過字段名稱獲取字段數(shù)據(jù)時,如何把數(shù)據(jù)庫類型轉(zhuǎn)換為對應的Java類型@Overridepublic OrderStatus getNullableResult(ResultSet rs, String columnName) throws SQLException {// 根據(jù)數(shù)據(jù)庫存儲類型決定獲取類型,本例子中數(shù)據(jù)庫中存放int類型// String i = rs.getString(columnName);int i = rs.getInt(columnName);if (rs.wasNull()) {return null;} else {// 根據(jù)數(shù)據(jù)庫中的值,定位Enum子類return locateEnum(i);}}//用于定義通過字段索引獲取字段數(shù)據(jù)時,如何把數(shù)據(jù)庫類型轉(zhuǎn)換為對應的Java類型@Overridepublic OrderStatus getNullableResult(ResultSet rs, int columnIndex) throws SQLException {// 根據(jù)數(shù)據(jù)庫存儲類型決定獲取類型,本例子中數(shù)據(jù)庫中存放int類型// String i = rs.getString(columnIndex);int i = rs.getInt(columnIndex);if (rs.wasNull()) {return null;} else {// 根據(jù)數(shù)據(jù)庫中的值,定位Enum子類return locateEnum(i);}}//用定義調(diào)用存儲過程后,如何把數(shù)據(jù)庫類型轉(zhuǎn)換為對應的Java類型@Overridepublic OrderStatus getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {// 根據(jù)數(shù)據(jù)庫存儲類型決定獲取類型,本例子中數(shù)據(jù)庫中存放int類型// String i = cs.getString(columnIndex);int i = cs.getInt(columnIndex);if (cs.wasNull()) {return null;} else {// 根據(jù)數(shù)據(jù)庫中的值,定位Enum子類return locateEnum(i);}}/*** 枚舉類型轉(zhuǎn)換** @param value 數(shù)據(jù)庫中存儲的自定義屬性* @return value對應的枚舉類*/private OrderStatus locateEnum(int value) {for (OrderStatus status : OrderStatus.values()) {if (status.getValue() == value) {return status;}}throw new IllegalArgumentException("未知的枚舉類型:" + value);} }Java
類轉(zhuǎn)換器就這樣編寫,注意的是,各個重寫方法中,數(shù)據(jù)類型,用對應數(shù)據(jù)庫數(shù)據(jù)的枚舉屬性的數(shù)據(jù)類型,本次就是 value 屬性對應數(shù)據(jù)庫中的內(nèi)容,所以是 int 類型,再有就是在最后的方法中,給出枚舉匹配的方法。
3.3.3 在 Mapper.xml 中配置自定義的枚舉轉(zhuǎn)換器
方法1:修改指定xml文件,指定的Mapper生效
我們只需要在 insert update select 等語句中配置 typeHandler , 而 Java 文件無需改動。
<insert id="addOrder" parameterType="com.yulaiz.model.order.entity.OrderInfo">INSERT INTO order_test ( status) VALUES (#{item.status, typeHandler=com.yulaiz.model.order.entity.enums.mybatis.EnumOrderStatusHandler}) </insert> <resultMap id="get" type="com.yulaiz.model.order.entity.OrderInfo"><result column="status" property="status"typeHandler="com.yulaiz.model.order.entity.enums.mybatis.EnumOrderStatusHandler"/> </resultMap> <select id="getOrderById" id="getOrderById" resultMap="get">SELECT id, statusFROM order_testWHERE id = #{id} </select>XML
方法2:全局指定枚舉轉(zhuǎn)換器,無需單獨修改Mapper.xml
直接在 Mybatis 的配置文件中配置自定義的類型轉(zhuǎn)換,這里我使用的 Spring-Boot,直接在 application.yml 文件中配置:
mybatis:type-handlers-package: com.yulaiz.model.order.entity.enums.mybatisYml
方法3:不使用枚舉轉(zhuǎn)換器
對于 insert 和 update 語句,還有一個簡單的方法:
<insert id="addOrder" parameterType="com.yulaiz.model.order.entity.OrderInfo">INSERT INTO order_test ( status) VALUES (#{item.status.value}) </insert>XML
對于 Java Bean 來說,也可以手動設置 get set 方法
public class OrderInfo {private int id;private OrderStatus status;public int getStatus() {return status.getValue();}public void setStatus(int value) {for (OrderStatus status : OrderStatus.values()) {if (status.getValue() == value) {this.status = status;break;}}} }Java
4. 枚舉到底好不好用
枚舉這樣直觀方便,在大數(shù)據(jù)量的情況下到底好不好用呢,下面我們就這么一個問題進行分析。
4.1 Java 枚舉的實現(xiàn)
4.1.1 基礎聲明、屬性、構(gòu)造函數(shù)
java 的枚舉類 以關鍵字 enum 聲明,該關鍵字隱含著該類為 java.lang.Enum 的子類,Java編譯器在編譯枚舉類時,會生成一個相關的類,這個類就是實際的枚舉類,繼承自 java.lang.Enum 。
public enum OrderStatus {CREATE("創(chuàng)建"), PAYING("支付中"), IN_PROGRESS("支付成功"), FAILED("支付失敗"), REVERSED("取消訂單"); }Java
該類有兩個屬性,分別是 name 和 ordinal ,在自定義的枚舉類中,我們聲明 CREATE("創(chuàng)建"), PAYING("支付中"), IN_PROGRESS("支付成功"), FAILED("支付失敗"), REVERSED("取消訂單") 的時候,前面括號外部分就是 name 屬性,比如:CREATE ,而 ordinal 屬性則是我們聲明中寫的順序,從 0 開始。這兩個屬性在父類的構(gòu)造函數(shù)中進行賦值,我們自定義的枚舉類中不用去費心這兩個屬性的賦值。子類中如果需要,只需要在構(gòu)造方法中定義其他自定義屬性的賦值即可。
protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal; }Java
而 CREATE("創(chuàng)建") 中括號內(nèi)就是我們自定義的屬性,也就是我們聲明的 value 屬性。
4.1.2 常用方法
4.1.2.1 name() 方法
返回 Enum 對象的 name 屬性。
4.1.2.2 ordinal() 方法。
返回 Enum 對象的 ordinal 屬性。
4.1.2.3 toString() 方法
返回 Enum 對象的名稱,也就是 name 屬性。
4.1.2.4 equals(Object other) 方法
比較兩個對象是否相等。詳細用法在下方 4.1.3 比較兩個枚舉值是否一致
4.1.2.5 compareTo(E o) 方法
比較兩個枚舉對象的順序,在該對象小于、等于或大于指定對象時,分別返回負整數(shù)、零或正整數(shù)。詳細用法在下方 4.1.3 比較兩個枚舉值是否一致
4.1.2.6 values() 方法
這個方法我們查看JDK中源碼沒有看到,并且在API文檔中也沒有找到。但確實是有這么一個方法。
在 Oracle的文檔 中可以找到這么一段話
The compiler automatically adds some special methods when it creates an enum. For example, they have a static values method that returns an array containing all of the values of the enum in the order they are declared. This method is commonly used in combination with the for-each construct to iterate over the values of an enum type.
大致意思就是,Java 編譯器在編譯枚舉類的時候會自動為該類加入一些靜態(tài)方法,比如說 values() ,這個方法返回一個包含這個枚舉類所有枚舉項的數(shù)組,這個數(shù)組是按照聲明的順序來排列(意味著 ordinal 屬性可以當成這個數(shù)組的下標),這個方法通常與 for-each 語句配合在遍歷枚舉值時使用。
注意,這是一個 static 靜態(tài)方法,意味著使用的時候直接通過枚舉類來調(diào)用,比如 OrderStatus.values() 。
4.1.2.7 valueOf(String name) 方法
根據(jù)傳入的 name 名稱,找到相對應 name 屬性的枚舉值。
這個方法實際上也是 Java 編譯器在編譯的時候加入的方法,在父類 java.lang.Enum 中,同樣存在 valueOf 方法,不過父類的方法是有兩個參數(shù),而這個第一個參數(shù)就是用來指定是哪一個枚舉類,那我們在子類的時候使用這個方法就很明確是這個類本身,所以編譯器直接在編譯的時候加入了一個方法,只需要傳入 name 參數(shù)即可。
注意,這是一個 static 靜態(tài)方法,意味著使用的時候直接通過枚舉類來調(diào)用,比如: OrderStatus.valueOf("CREATE") 。
4.1.3 比較兩個枚舉值是否一致
我們在實際代碼中經(jīng)常根據(jù)狀態(tài)來進行流程向?qū)?#xff0c;而枚舉類大部分時間就是用來表示狀態(tài),那么我們就需要對比枚舉類是否為某個值來進行判斷:
- 通過 switch 語句
通過 switch 語句來判斷,簡單方便,好看:
OrderStatus status = OrderStatus.CREATE; switch (status) {case CREATE:System.out.println("CREATE");break;case PAYING:System.out.println("PAYING");break;default:System.out.println("default"); }Java
- 通過 equals() 方法
用 String 的時候經(jīng)常使用的方法:
OrderStatus status = OrderStatus.CREATE; boolean result = status.equals(OrderStatus.CREATE);Java
- 通過 == 運算符
實際上效果跟 equals() 方法是一樣的,我們可以點進 equals() 方法的源碼查看,實際上就是使用 == 運算符來實現(xiàn)的:
/*** Returns true if the specified object is equal to this* enum constant.** @param other the object to be compared for equality with this object.* @return true if the specified object is equal to this* enum constant.*/public final boolean equals(Object other) {return this==other;}Java
實際使用上就更簡單了:
OrderStatus status = OrderStatus.CREATE; if (status == OrderStatus.CREATE) {System.out.println("=="); }Java
- 通過compareTo(E o) 方法
稍微麻煩一點:
OrderStatus status = OrderStatus.CREATE; if (status.compareTo(OrderStatus.CREATE) == 0) {System.out.println("=="); }Java
?
4.2 MySql 中的枚舉
首先,我們要知道對于 select 語句,枚舉對于其展示出來的數(shù)據(jù)是非常友好的,可以直觀的看到具體的含義。而枚舉的優(yōu)點就是固定的有限的枚舉項,那么就需要我們在定義數(shù)據(jù)庫的時候就定義好這個枚舉可能用到的所有值,相比int類型,我們來看看對于枚舉類型 enum 的枚舉項的刪除和修改操作的效率如何。
我在數(shù)據(jù)庫中分別創(chuàng)建了兩張表:order_test 、order_test_enum 。
CREATE TABLE `order_test` (`id` int(20) NOT NULL AUTO_INCREMENT,`status` int(1) NOT NULL COMMENT '0-創(chuàng)建,1-支付中,2-支付成功,3-支付失敗,4-取消訂單',PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;CREATE TABLE `order_test_enum` (`id` int(20) NOT NULL AUTO_INCREMENT,`status` enum('CREATE','PAYING','IN_PROGRESS','FAILED','REVERSED') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,PAYING-支付中,IN_PROGRESS-支付成功,FAILED-支付失敗,REVERSED-取消訂單',PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;SQL
在兩張表中分別插入了 1000W 條數(shù)據(jù),其數(shù)據(jù)格式就是每個枚舉項都依次循環(huán)插入,保證每個枚舉項的數(shù)量盡可能平均的分布。
4.2.1 DDL 操作效率
對于狀態(tài)的修改,我們一般會有幾種操作:
- 新增一個狀態(tài)。
- 修改一個狀態(tài)。
- 刪除一個狀態(tài)。
4.2.1.1 新增一個枚舉狀態(tài)
對于 int 類型來說,這個直接無需操作了,要什么新狀態(tài),直接 insert 的時候直接插入就行了。
對于 enum 類型來說,這個就需要進行 DDL 操作:
ALTER TABLE `test`.`order_test_enum` MODIFY COLUMN `status` enum('CREATE','PAYING','IN_PROGRESS','FAILED','REVERSED','TEST') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,PAYING-支付中,IN_PROGRESS-支付成功,FAILED-支付失敗,REVERSED-取消訂單,TEST-新增測試';SQL
執(zhí)行時間:0.008s 還不錯的速度。
4.2.1.2 修改一個枚舉狀態(tài)
比方說我們定義的狀態(tài),寫錯值了,又不想將錯就錯,int 類型應該不會出現(xiàn)這個問題吧,奈何英文水平實在太差,拼錯了單詞,或者單詞用的不合理,我們這里假設將 PAYING 換成 PAYING1 :
ALTER TABLE `test`.`order_test_enum` MODIFY COLUMN `status` enum('CREATE','PAYING1','IN_PROGRESS','FAILED','REVERSED','TEST') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,PAYING1-支付中,IN_PROGRESS-支付成功,FAILED-支付失敗,REVERSED-取消訂單,TEST-新增測試';SQL
1265 - Data truncated for column 'status' at row 2 修改失敗,Mysql 不允許修改已經(jīng)使用的枚舉項。
那么我們想到達換枚舉值的情況就只能曲折一點,先新增一個,再 update 原有的值到新值,剛剛已經(jīng)建好的TEST 值,那么我們現(xiàn)在就把 PAYING 修改成 TEST :
UPDATE order_test_enum SET `status` = 'TEST' WHERE`status` = 'PAYING';SQL
執(zhí)行時間: 12.456s 這樣的一個時間說得過去,因為我剛剛也在 order_test 表中做了一個差不多的操作執(zhí)行時間是 10.459s 。
UPDATE order_test SET `status` = 5 WHERE`status` = 1;SQL
4.2.1.3 刪除一個枚舉狀態(tài)
同樣 int 類型無需修改,修改 enum 類型進行 DDL 操作,假設我們刪除 IN_PROGRESS 這項:
ALTER TABLE `test`.`order_test_enum` MODIFY COLUMN `status` enum('CREATE','PAYING','FAILED','REVERSED','TEST') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,PAYING-支付中,FAILED-支付失敗,REVERSED-取消訂單,TEST-新增測試';SQL
1265 - Data truncated for column 'status' at row 2 刪除失敗,這是由于MySql不允許刪除已經(jīng)被使用的枚舉項。那我們刪除剛剛已經(jīng)把數(shù)據(jù)改為 TEST 的 PAYING 試試:
ALTER TABLE `test`.`order_test_enum` MODIFY COLUMN `status` enum('CREATE','IN_PROGRESS','FAILED','REVERSED','TEST') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'CREATE' COMMENT 'CREATE-創(chuàng)建,IN_PROGRESS-支付成功,FAILED-支付失敗,REVERSED-取消訂單,TEST-新增測試';SQL
執(zhí)行時間: 34.971s 這個時間是真的不快。
4.2.2 查詢效率
可能有些人會說枚舉對于大數(shù)據(jù)查詢是非常不友好的,畢竟是字符型。
而實際上 Mysql 中的 enum 類型,還恰恰不是字符型,而是整型存儲。
實際上在建立 enum 字段的時候,MySql 會根據(jù)設定的幾個字段的順序來編號,而這個編號實際上才是 MySql 真正存儲的內(nèi)容,這個編號在某些文章里也會稱呼為索引值。
在上面的例子中,建立的枚舉值分別為 'CREATE','PAYING','IN_PROGRESS','FAILED','REVERSED' ,那么他們的索引值就分別為:
| NULL | NULL |
| ” | 0 |
| ‘CREATE’ | 1 |
| ‘PAYING’ | 2 |
| ‘IN_PROGRESS’ | 3 |
| ‘FAILED’ | 4 |
| ‘REVERSED’ | 5 |
所以在搜索的時候 WHERE 條件中的 status = 'CREATE' 和 status = 1 是等效的,這里也要注意就是 enum 字段同樣有 NULL 值 和 ” 的情況,需要我們在定義數(shù)據(jù)類型的時候就需要注意。
SELECTid,`status` FROMorder_test_enum WHEREid = 1 AND `status` = 1;SQL
SELECTid,`status` FROMorder_test_enum WHEREid = 1 AND `status` = 'CREATE';SQL
上面兩個sql查詢到的內(nèi)容就是一樣的,那么同是整型,在查詢上的差距,咱就可以忽略不計了吧。
5. 枚舉使用總結(jié)
雖然 Java 中的枚舉比 C 或 C++ 中的 enum 更成熟,但它仍然是一個“小”功能,Java 沒有它也已經(jīng)(雖然有點笨拙)存在很多年了。而本章正好說明了一個“小”功能所能帶來的價值。有時恰恰因為它,你才能夠優(yōu)雅而干凈地解決問題。正如我在本書中一再強調(diào)的那樣,優(yōu)雅與清晰很重要,正是它們區(qū)別了成功的解決方案與失敗的解決方案。而失敗的解決方案就是因為其他人無法理解它。
直接引用了《Thinking in Java》這么一段話(《Thinking in Java》第四版 19章12節(jié)),很明顯,很清楚,至少在 Java 中使用枚舉是值得的。
而對于數(shù)據(jù)庫來說,根據(jù)實際情況來構(gòu)建,至少枚舉時一個特別方便的結(jié)構(gòu),如果有舊表的狀態(tài)描述字段沒有使用枚舉,請慎重是否進行改進,需要考慮存量數(shù)據(jù)的改造,以及對接代碼的改造。
即便 MySql 中依舊使用數(shù)字來表達狀態(tài),依然不妨礙在 Java 中改造成枚舉,具體改造的范圍需要自行斟酌,防止接口對上下游系統(tǒng)的不友好。
枚舉是非常適合用來描述狀態(tài)的結(jié)構(gòu),并且不推薦使用數(shù)字來定義,請使用字符串來表達各個狀態(tài),否則在查看代碼,查看數(shù)據(jù)庫數(shù)據(jù)的時候,還是一個數(shù)字,那我們使用枚舉的意義何在。
總的來說,請盡量合理的使用枚舉,功能雖小,但咱們很優(yōu)雅。
6. 參考鏈接
mysql enum類型存在大量數(shù)據(jù)的時候方便修改數(shù)據(jù)項么
mysql 數(shù)據(jù)庫枚舉類型enum,方便添加新的枚舉項嗎?
MySQL中的enum類型有什么優(yōu)點?
深入理解Java枚舉類型(enum)
重新認識java(十) —- Enum(枚舉類)
java enum(枚舉)使用詳解 + 總結(jié)
如何在MyBatis中優(yōu)雅的使用枚舉
MYSQL中 ENUM 類型
java枚舉enum類中的values()
MyBatis對于Java對象里的枚舉類型處理
MyBatis對于Java對象里的枚舉類型處理
總結(jié)
以上是生活随笔為你收集整理的Java、Mysql、MyBatis 中枚举 enum 的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 顺序栈的实验报告c语言,顺序栈的基本操作
- 下一篇: c语言开根函数不用math,c语言开平方