- Использование базовых типов данных
- Сопоставление типов данных
- Извлечение данных в виде строки
- Извлечение данных по типу данных
- Обновление данных по типу данных
- Обновление по параметризированному запросу
- Передача параметров хранимой процедуре
- Извлечение параметров из хранимой процедуры
- Типы данных, поддерживаемые Java и SQL Server
- Дальнейшие действия
- Работа со временем
- Современные проблемы требуют современных решений
- Получение даты
- Настройка часового пояса в MySQL
Использование базовых типов данных
Драйвер Microsoft JDBC Driver для SQL Server использует базовые типы данных JDBC для преобразования типов данных SQL Server в формат языка программирования Java и обратно. Драйвер JDBC обеспечивает поддержку API JDBC 4.0, в том числе типа данных SQLXML и типов данных Юникода для национальных символов: NCHAR, NVARCHAR, LONGNVARCHAR и NCLOB.
Сопоставление типов данных
В следующей таблице перечислены все сопоставления по умолчанию между базовыми типами данных SQL Server, типами данных JDBC и типами данных языка программирования Java:
1 Чтобы использовать java.sql.Time с типом времени SQL Server, нужно задать для свойства подключения sendTimeAsDatetime значение «false».
2 Значения datetimeoffset можно получить программным образом с помощью класса DateTimeOffset.
3 Обратите внимание, что значения java.sql.Timestamp больше нельзя использовать для сравнения значений из столбца datetime, начиная с SQL Server 2016. Это ограничение обусловлено изменением на стороне сервера, которое иначе преобразует datetime в datetime2, в результате чего получаются не равнозначные значения. Чтобы решить эту проблему, можно либо изменить столбцы datetime на datetime2(3), использовать строку вместо java.sql.Timestamp или изменить уровень совместимости базы данных на 120 или ниже.
В следующих разделах приводятся примеры использования драйвера JDBC и базовых типов данных. Более подробный пример использования базовых типов данных в приложении Java см. в разделе Образец базовых типов данных.
Извлечение данных в виде строки
Если вам нужно получить из источника данные, соответствующие каким-либо базовым типам данных JDBC, и просмотреть их в виде строки, или если строго типизированные данные не требуются, вы можете воспользоваться методом getString класса SQLServerResultSet, как показано далее:
try(Statement stmt = con.createStatement();)
Извлечение данных по типу данных
Если вам нужно получить из источника данные известного типа, воспользуйтесь одним из методов get класса SQLServerResultSet, также известных как методы получения. С методами get можно использовать имя столбца или его индекс:
try(Statement stmt = con.createStatement();)
Применение getUnicodeStream и getBigDecimal в сочетании с методами масштабирования считается устаревшим и не поддерживается драйвером JDBC.
Обновление данных по типу данных
Если вам нужно обновить значение поля в источнике данных, воспользуйтесь одним из методов update класса SQLServerResultSet. В следующем примере для обновления данных в источнике используется метод updateInt совместно с методом updateRow:
try (Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);)
Драйвер JDBC не может обновить столбец SQL Server, если имя столбца длиннее, чем 127 символов. При попытке обновить столбец, имя которого длиннее 127 символов, возникнет исключение.
Обновление по параметризированному запросу
Если вам нужно обновить данные в источнике с помощью параметризированного запроса, вы можете задать тип данных для параметров одним из методов set класса SQLServerPreparedStatement, также известных как методы задания. В следующем примере метод prepareStatement используется для предварительной компиляции параметризированного запроса, затем метод setString задает строковое значение параметра, после чего вызывается метод executeUpdate.
try(PreparedStatement pstmt = con.prepareStatement("UPDATE employee SET fname = ? WHERE (lname = 'Brown')");)
Дополнительные сведения о параметризованных запросах см. в этой статье.
Передача параметров хранимой процедуре
Если вам нужно передать параметры типа хранимой процедуре, вы можете задать параметры по имени или индексу с помощью методов set класса SQLServerCallableStatement. В следующем примере метод prepareCall используется для вызова хранимой процедуры, затем с помощью метода setString задается параметр для вызова, после чего вызывается метод executeQuery.
try(CallableStatement cstmt = con.prepareCall("");)
В данном примере возвращается результирующий набор с результатами запуска хранимой процедуры.
Дополнительные сведения об использовании драйвера JDBC с хранимыми процедурами и входными параметрами см. в этой статье.
Извлечение параметров из хранимой процедуры
Если вам нужно получить параметры обратно из хранимой процедуры, то нужно сначала зарегистрировать параметр OUT по имени или индексу при помощи метода registerOutParameter класса SQLServerCallableStatement, а затем после вызова хранимой процедуры назначить возвращаемый параметр OUT надлежащей переменной. В следующем примере сначала используется метод prepareCall для настройки вызова хранимой процедуры, затем — метод registerOutParameter для настройки параметра OUT, затем — метод setString для задания параметра вызова, после чего вызывается метод executeQuery. Значение, возвращаемое параметром OUT хранимой процедуры, извлекается с помощью метода getShort.
try(CallableStatement cstmt = con.prepareCall("");)
В дополнение к возвращаемому параметру OUT также можно вернуть результирующий набор с результатами запуска хранимой процедуры.
Дополнительные сведения об использовании драйвера JDBC с хранимыми процедурами и выходными параметрами см. в этой статье.
Типы данных, поддерживаемые Java и SQL Server
В этой статье сопоставляются типы данных и типы данных Java для структур данных и параметров в sp_execute_external_script.
В настоящее время поддерживаются следующие типы данных SQL и Java для наборов входных и выходных данных, а также для входных и выходных параметров.
Тип данных SQL | Тип данных Java | Комментировать |
---|---|---|
bit | Логическое | |
Tinyint | short | |
Smallint | short | |
Int | INT | |
Real | float | |
Bigint | long | |
FLOAT | double | |
nchar(n) | Строка | |
nvarchar(n) | Строка | |
binary(n) | byte[] | |
varbinary(n) | byte[] | |
nvarchar(max) | Строка | |
varbinary(max) | byte[] | |
UNIQUEIDENTIFIER | Строка | |
char(n) | Строка | Поддерживаются только строки UTF8 |
varchar(n) | Строка | Поддерживаются только строки UTF8 |
varchar(max) | Строка | Поддерживаются только строки UTF8 |
Дата | java.sql.date | |
NUMERIC | java.math.BigDecimal | |
Decimal | java.math.BigDecimal | |
money | java.math.BigDecimal | |
smallmoney | java.math.BigDecimal | |
smalldatetime | java.sql.timestamp | |
DATETIME | java.sql.timestamp | |
datetime2 | java.sql.timestamp |
Дальнейшие действия
Работа со временем
Со времен, когда придумали JDBC и стандартизировали его интерфейсы, прошло лет 20, и за это время много чего изменилось.
Во-первых, мир стал глобальным и теперь один сервер может обслуживать пользователей со всего мира. Скорость интернета порешала. Поэтому в SQL был добавлен еще один тип данных для работы со временем. Теперь типы выглядят вот так:
- DATE – хранит дату: год, месяц, день.
- TIME – хранит время: часы, минуты, секунды.
- TIMESTAMP – хранит конкретный момент времени: дата, время и миллисекунды.
- TIMESTAMP WITH TIME ZONE – TIMESTAMP и временная зона (имя зоны или смещение).
Во-вторых, в Java появился DateTime API для глобальной работы со временем. В нем появились такие классы:
- Дата и время:
- LocalDate
- LocalTime
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Третий интересный момент состоит в том, что очень многим SQL-клиентам хотелось бы получать время с сервера уже в своей локальное зоне . Преобразовывать время на лету конечно можно, но это не удобно, да и ошибки бывают.
Например, я хочу получить из базы все задания на сегодня. У SQL-сервера есть функция CURDATE() для этого дела. Только вот сервер находится в США, а я – в Японии. И хотелось бы чтобы он мне вернул все записи за “мое сегодня”, а не “его сегодня”.
В общем, SQL-сервер тоже должен уметь по-умному работать с клиентами в разных временных зонах.
Современные проблемы требуют современных решений
В принципе, новые типы из Java DateTime API и типы из SQL можно удобно сопоставлять. Для того чтобы представить тип DATE в Java нужно использовать класс java.time.LocalDate из JDK 8 DateTime API.
Тип TIME из базы данных можно представить двумя типами из Java: java.time.LocalTime и java.time.OffsetTime . Тоже ничего сложного.
Конкретный момент времени, представленный типом TIMESTAMP в базе, в Java можно представить 4 типами:
- java.time.Instant
- java.time.LocalDateTime
- java.time.OffsetDateTime
- java.time.ZonedDateTime
Ну, и наконец, TIMESTAMP WITH TIME ZONE можно представить двумя типами:
Так как ты уже знаком с DateTime API, то запомнить это дело тебе труда не составит 🙂
Запишу в виде таблицы, так будет проще:
SQL TYPE Java Type DATE java.time.LocalDate TIME java.time.LocalTime
java.time.OffsetTimeTIMESTAMP java.time.Instant
java.time.LocalDateTime
java.time.OffsetDateTime
java.time.ZonedDateTimeTIMESTAMP WITH TIME ZONE java.time.OffsetDateTime
java.time.ZonedDateTimeПолучение даты
У меня для тебя хорошая новость. Первая за долгое время. Мы можем обойти ограничение метода getDate() , который возвращает тип java.sql Date.
Дело в том, что у объекта ResultSet есть еще один интересный метод — getObject() . Этот метод принимает два параметра: колонку и тип, и возвращает значение колонки, преобразованное к заданному типу. Общий вид метода такой:
ИмяКласса имя = getObject(column, ИмяКласса);
И если вы хотите преобразовать тип DATE к типу java.time.LocalDate , то нужно написать что-то типа:
LocalDate localDate = results.getObject(4, LocalDate.class);
А любой TIMESTAMP вообще можно преобразовать к куче типов:
java.time.Instant instant = results.getObject(9, java.time.Instant.class); java.time.LocalDateTime local = results.getObject(9, java.time. LocalDateTime.class); java.time.OffsetDateTime offset = results.getObject(9, java.time. OffsetDateTime.class); java.time.ZonedDateTime zoned = results.getObject(9, java.time. ZonedDateTime.class);
Важно! Этот код не будет работать если у вас устаревший MySQL JDBC Driver . Обрати внимание на версию “mysql-connector-java”, прописанную в твоем pom.xml, или добавленную в Libraries в настройках проекта.
Кстати, таким же способом можно обойти и невозможность хранить null у примитивных типов. Если колонка таблицы имеет тип INT, то есть пара способов получить из нее null. Смотри пример ниже:
Integer id1 = results.getObject(8, Integer.class); //так будет работать Integer id2 = results.getObject(8, int.class); //так тоже будет работать int id3 = results.getObject(8, Integer.class); //метод вернет null, JVM кинет NPE int id4 = results.getObject(8, int.class); //метод вернет null, JVM кинет NPE
Настройка часового пояса в MySQL
С MySQL тоже произошло много всего интересного. Как ты знаешь, при создании соединения с MySQL к нему можно добавлять различные параметры:
mysql://localhost:3306/db_scheme?имя=значение&имя=значение
Так вот, для работы с временными зонами в MySQL добавили три параметра. Эти параметры ты можешь передавать, когда устанавливаешь соединение с сервером.
Ниже я приведу таблицу с ними:
Параметр Значения Значение по умолчанию connectionTimeZone LOCAL | SERVER | user-zone SERVER forceConnectionTimeZoneToSession true | false true preserveInstants true | false false С помощью параметра connectionTimeZone мы выбираем временную зону (часовой пояс), в котором будут выполняться все запросы. С точки зрения клиента, сервер работает в указанной временной зоне.
Параметр forceConnectionTimeZoneToSession заставляет игнорировать переменную time_zone сессии и заменить ее на connectionTimeZone.
И наконец параметр preserveInstants управляет преобразование точного-момента-времени между JVM timeZone и connectionTimeZone.
Самые частые конфигурации такие:
- connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=false — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=true.
- connectionTimeZone=LOCAL & forceConnectionTimeZoneToSession=true — новый режим, обеспечивающий наиболее естественный способ обработки значений даты и времени.
- connectionTimeZone=SERVER & preserveInstants=true — соответствует старому MySQL JDBC драйверу версии 5.1 с параметром useLegacyDatetimeCode=false.
- connectionTimeZone=user_defined & preserveInstants=true — помогает преодолеть ситуацию, когда часовой пояс сервера не может быть распознан коннектором, потому что он установлен как общая аббревиатура, такая как CET/CEST.
Да, даты — тема интересная и проблем с ними много. Как говорится: это конечно страшно, но и я не сыкло! 🙂