В своем блоге « Подключение к Cassandra из Java» я упомянул, что одним из преимуществ реализации Java-разработчиков Cassandra на Java является возможность создания пользовательских типов данных Cassandra . В этом посте я расскажу, как это сделать более подробно.
Cassandra имеет множество встроенных типов данных , но есть ситуации, в которых можно добавить пользовательский тип. Пользовательские типы данных Cassandra реализованы в Java путем расширения класса org.apache.cassandra.db.marshal.AbstractType . Класс, который расширяет это, должен в конечном итоге реализовать три метода со следующими сигнатурами:
|
1
|
public ByteBuffer fromString(final String) throws MarshalException |
|
1
|
public TypeSerializer getSerializer() |
|
1
|
public int compare(Object, Object) |
Пример реализации AbstractType в этом посте показан в следующем листинге кода.
UnitedStatesState.java — расширяет абстрактный тип
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
package dustin.examples.cassandra.cqltypes;import org.apache.cassandra.db.marshal.AbstractType;import org.apache.cassandra.serializers.MarshalException;import org.apache.cassandra.serializers.TypeSerializer;import java.nio.ByteBuffer;/** * Representation of a state in the United States that * can be persisted to Cassandra database. */public class UnitedStatesState extends AbstractType{ public static final UnitedStatesState instance = new UnitedStatesState(); @Override public ByteBuffer fromString(final String stateName) throws MarshalException { return getStateAbbreviationAsByteBuffer(stateName); } @Override public TypeSerializer getSerializer() { return UnitedStatesStateSerializer.instance; } @Override public int compare(Object o1, Object o2) { if (o1 == null && o2 == null) { return 0; } else if (o1 == null) { return 1; } else if (o2 == null) { return -1; } else { return o1.toString().compareTo(o2.toString()); } } /** * Provide standard two-letter abbreviation for United States * state whose state name is provided. * * @param stateName Name of state whose abbreviation is desired. * @return State's abbreviation as a ByteBuffer; will return "UK" * if provided state name is unexpected value. */ private ByteBuffer getStateAbbreviationAsByteBuffer(final String stateName) { final String upperCaseStateName = stateName != null ? stateName.toUpperCase().replace(" ", "_") : "UNKNOWN"; String abbreviation; try { abbreviation = upperCaseStateName.length() == 2 ? State.fromAbbreviation(upperCaseStateName).getStateAbbreviation() : State.valueOf(upperCaseStateName).getStateAbbreviation(); } catch (Exception exception) { abbreviation = State.UNKNOWN.getStateAbbreviation(); } return ByteBuffer.wrap(abbreviation.getBytes()); }} |
Приведенный выше список классов ссылается на перечисление State , которое показано далее.
State.java
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
package dustin.examples.cassandra.cqltypes;/** * Representation of state in the United States. */public enum State{ ALABAMA("Alabama", "AL"), ALASKA("Alaska", "AK"), ARIZONA("Arizona", "AZ"), ARKANSAS("Arkansas", "AR"), CALIFORNIA("California", "CA"), COLORADO("Colorado", "CO"), CONNECTICUT("Connecticut", "CT"), DELAWARE("Delaware", "DE"), DISTRICT_OF_COLUMBIA("District of Columbia", "DC"), FLORIDA("Florida", "FL"), GEORGIA("Georgia", "GA"), HAWAII("Hawaii", "HI"), IDAHO("Idaho", "ID"), ILLINOIS("Illinois", "IL"), INDIANA("Indiana", "IN"), IOWA("Iowa", "IA"), KANSAS("Kansas", "KS"), LOUISIANA("Louisiana", "LA"), MAINE("Maine", "ME"), MARYLAND("Maryland", "MD"), MASSACHUSETTS("Massachusetts", "MA"), MICHIGAN("Michigan", "MI"), MINNESOTA("Minnesota", "MN"), MISSISSIPPI("Mississippi", "MS"), MISSOURI("Missouri", "MO"), MONTANA("Montana", "MT"), NEBRASKA("Nebraska", "NE"), NEVADA("Nevada", "NV"), NEW_HAMPSHIRE("New Hampshire", "NH"), NEW_JERSEY("New Jersey", "NJ"), NEW_MEXICO("New Mexico", "NM"), NORTH_CAROLINA("North Carolina", "NC"), NORTH_DAKOTA("North Dakota", "ND"), NEW_YORK("New York", "NY"), OHIO("Ohio", "OH"), OKLAHOMA("Oklahoma", "OK"), OREGON("Oregon", "OR"), PENNSYLVANIA("Pennsylvania", "PA"), RHODE_ISLAND("Rhode Island", "RI"), SOUTH_CAROLINA("South Carolina", "SC"), SOUTH_DAKOTA("South Dakota", "SD"), TENNESSEE("Tennessee", "TN"), TEXAS("Texas", "TX"), UTAH("Utah", "UT"), VERMONT("Vermont", "VT"), VIRGINIA("Virginia", "VA"), WASHINGTON("Washington", "WA"), WEST_VIRGINIA("West Virginia", "WV"), WISCONSIN("Wisconsin", "WI"), WYOMING("Wyoming", "WY"), UNKNOWN("Unknown", "UK"); private String stateName; private String stateAbbreviation; State(final String newStateName, final String newStateAbbreviation) { this.stateName = newStateName; this.stateAbbreviation = newStateAbbreviation; } public String getStateName() { return this.stateName; } public String getStateAbbreviation() { return this.stateAbbreviation; } public static State fromAbbreviation(final String candidateAbbreviation) { State match = UNKNOWN; if (candidateAbbreviation != null && candidateAbbreviation.length() == 2) { final String upperAbbreviation = candidateAbbreviation.toUpperCase(); for (final State state : State.values()) { if (state.stateAbbreviation.equals(upperAbbreviation)) { match = state; } } } return match; }} |
Мы также можем предоставить реализацию интерфейса TypeSerializer возвращаемого методом getSerializer() показанным выше. Этот класс, реализующий TypeSerializer , обычно легче всего написать, расширив одну из многочисленных существующих реализаций TypeSerializer которые Cassandra предоставляет в org.apache.cassandra.serializers package . В моем примере мой пользовательский Serializer расширяет AbstractTextSerializer и единственный метод, который мне нужно добавить, имеет public void validate(final ByteBuffer bytes) throws MarshalException . Оба моих пользовательских класса должны предоставлять ссылку на свой экземпляр через статический доступ. Вот класс, который реализует TypeSerializer через расширение AbstractTypeSerializer :
UnitedStatesStateSerializer.java — Реализует TypeSerializer
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
package dustin.examples.cassandra.cqltypes;import org.apache.cassandra.serializers.AbstractTextSerializer;import org.apache.cassandra.serializers.MarshalException;import java.nio.ByteBuffer;import java.nio.charset.StandardCharsets;/** * Serializer for UnitedStatesState. */public class UnitedStatesStateSerializer extends AbstractTextSerializer{ public static final UnitedStatesStateSerializer instance = new UnitedStatesStateSerializer(); private UnitedStatesStateSerializer() { super(StandardCharsets.UTF_8); } /** * Validates provided ByteBuffer contents to ensure they can * be modeled in the UnitedStatesState Cassandra/CQL data type. * This allows for a full state name to be specified or for its * two-digit abbreviation to be specified and either is considered * valid. * * @param bytes ByteBuffer whose contents are to be validated. * @throws MarshalException Thrown if provided data is invalid. */ @Override public void validate(final ByteBuffer bytes) throws MarshalException { try { final String stringFormat = new String(bytes.array()).toUpperCase(); final State state = stringFormat.length() == 2 ? State.fromAbbreviation(stringFormat) : State.valueOf(stringFormat); } catch (Exception exception) { throw new MarshalException("Invalid model cannot be marshaled as UnitedStatesState."); } }} |
После написания классов для создания пользовательского типа данных CQL их необходимо скомпилировать в .class и заархивировать в файл JAR. Этот процесс (компиляция с помощью javac -cp "C:\Program Files\DataStax Community\apache-cassandra\lib\*" -sourcepath src -d classes src\dustin\examples\cassandra\cqltypes\*.java и архивирование сгенерированного .class Файлы .class в JAR с именем CustomCqlTypes.jar с jar cvf CustomCqlTypes.jar * ) показаны на следующем снимке экрана.
JAR с определениями классов пользовательских типов CQL необходимо поместить в каталог lib установки Cassandra, как показано на следующем снимке экрана.
С JAR, содержащим пользовательские реализации классов типов данных CQL в каталоге lib установки Cassandra, Cassandra должен быть перезапущен, чтобы он мог «видеть» эти определения пользовательских типов данных.
В следующем листинге кода показан оператор языка Cassandra Query Language (CQL) для создания таблицы с использованием нового пользовательского типа dustin.examples.cassandra.cqltypes.UnitedStatesState .
createAddress.cql
|
01
02
03
04
05
06
07
08
09
10
|
CREATE TABLE us_address( id uuid, street1 text, street2 text, city text, state 'dustin.examples.cassandra.cqltypes.UnitedStatesState', zipcode text, PRIMARY KEY(id)); |
Следующий снимок экрана демонстрирует результаты выполнения createAddress.cql выше кода createAddress.cql путем описания созданной таблицы в cqlsh .
Приведенный выше снимок экрана демонстрирует, что пользовательский тип dustin.examples.cassandra.cqltypes.UnitedStatesState является типом для столбца состояния таблицы us_address .
Новая строка может быть добавлена в таблицу US_ADDRESS с помощью обычной INSERT . Например, следующий снимок экрана демонстрирует вставку адреса с помощью команды INSERT INTO us_address (id, street1, street2, city, state, zipcode) VALUES (blobAsUuid(timeuuidAsBlob(now())), '350 Fifth Avenue', '', 'New York', 'New York', '10118'); :
Обратите внимание, что, хотя оператор INSERT вставил для штата «Нью-Йорк», он хранится как «NY».
Если я запускаю INSERT в cqlsh, используя для начала аббревиатуру ( INSERT INTO us_address (id, street1, street2, city, state, zipcode) VALUES (blobAsUuid(timeuuidAsBlob(now())), '350 Fifth Avenue', '', 'New York', 'NY', '10118'); ), он все еще работает, как показано в выводе, показанном ниже.
В моем примере недопустимое состояние не предотвращает возникновение INSERT , но вместо этого сохраняет состояние как «UK» (для неизвестного) [см. Реализацию этого в UnitedStatesState.getStateAbbreviationAsByteBuffer(String) ].
Одним из первых преимуществ, который приходит на ум, оправдывая, почему можно реализовать пользовательский тип данных CQL в Java, является возможность использовать поведение, подобное тому, которое обеспечивается проверочными ограничениями в реляционных базах данных. Например, в этом посте мой пример гарантировал, что любой столбец штатов, введенный для новой строки, будет одним из пятидесяти штатов США, округа Колумбия или «Великобритания» для неизвестных. Никакие другие значения не могут быть вставлены в значение этого столбца.
Еще одним преимуществом пользовательского типа данных является возможность втирать данные в предпочтительную форму. В этом примере я изменил каждое название штата на заглавную двузначную аббревиатуру. В других случаях я могу захотеть всегда хранить в верхнем регистре или всегда в нижнем регистре или отображать конечные наборы строк в числовые значения. Пользовательский тип данных CQL позволяет настраивать проверку и представление значений в базе данных Cassandra.
Вывод
Этот пост был ознакомительным обзором реализации пользовательских типов данных CQL в Cassandra. Поскольку я больше играю с этой концепцией и пробую разные вещи, я надеюсь написать еще один пост в блоге о некоторых более тонких наблюдениях, которые я делаю. Как показано в этом посте, довольно легко написать и использовать пользовательский тип данных CQL, особенно для разработчиков Java.
| Ссылка: | Пользовательские типы данных Cassandra от нашего партнера JCG Дастина Маркса в блоге Inspired by Actual Events . |





