POOL Аварийных сообщений с Web интерфейсом, + Desktop клиент для компьютера под управлением Windows

В рамках развития проекта Менеджер SMS тревог был доработан функционал Andoid приложения (добавлена функция отправки полученных сообщений в Pool), а также были реализованы следующий компоненты : 

AlertMessagePool

 Представляет из  себя Java Spring Boot приложение с RestAPI и WebSocket интерфейсом. 

Назначение :  1) Приём входящих сообщений от телефона 2) Каталогизация их в базе данных MySQL 3) Предоставление API для добавления и запроса списков сообщений относительно даты/времени 3) RealTime отображение поступающих сообщений в Web интерфейсе.

Размещение : Временно размещён на личных бесплатных серверах VM Oracle Cloud (до запуска служебной VM, в пределах гипервизора предприятия ) по адресу http://vpkits.legan.by/MessageAlertPool/

Инструкция по внедрению 

Для очистки пула НАЖАТЬ , и добавить serviceKey заданный администратором

Для очистки записей старше указанной даты выполнить ЗАПРОС заполнив параметры GET запроса date в формате ISO.DATE_TIME и serviceKey заданный администратором

AlertMessageNotifer

Представляет из себя .NET C# WPF Win приложение

Назначение : Осуществляет запрос сообщений из пула (AlertMessagePool) и отображает их в виде всплывающих оповещений со звуковым сигналом, на компьютере.

 СКАЧАТЬ AlertMessageNotifer

Примечание : Для работы приложение требуется установленный Microsoft .NET Framework 4.7.2

ПС. Версии всех программных продуктов находятся в beta тесте и могут (и явно содержат:) ) обилие глюков 

Менеджер SMS тревог

Приложение предназначено для оповещения о срабатывании автоматики защиты и оповещения о авариях

Для установки APK нужно разрешить установку из непроверенных источников. Вот ТУТ и ТУТ написано как это сделать !

ЗАГРУЗИТЬ ПРИЛОЖЕНИЕ

Проверено работа на версии Android от 4.4.3

Поведение приложения отличается от версии Android.

  1. Для версий с 4.х по 6.х — При срабатывании оповещения, приложение открывается на весь экран и издаёт сирену, в не зависимости что на текущий момент делает пользователь.
  2. Для версий с 7.х и выше При срабатывании оповещения, если телефон заблокирован приложение откроется на полный экран и будет слышна сирена, если телефон разблокирован и пользователь с ним работает — будет выведено всплывающее сообщение и будет звучать сирена. При нажатии на это сообщение вы попадаете на экран оповещения. (Такое поведение навязала Google, что бы не раздражать пользователей не с того ни с сего выскакивающими окнами)

 Инструкция по использованию. 

  1. Сразу после установки вам следует принять два запроса на разрешения для работы с SMS и внутренней памятью, они нужны для нормальной работы приложения.
  2. Главное окно приложения состоит из двух вкладок, Список объектов и История тревог. В каждом из которых в свою очередь будут отображаться созданные нами в последующем объекты и история сработок нашего приложения. 
  3. Для того что бы получать сигнал тревоги в удобочитаемой форме с чётким звуковым сигналом, нам следует создать для каждого нашего объекта, на котором установлен контролер GSM оповещений, объект в приложении, и сделать это можно нажав на 3 точки в правом углу и выбрав «Добавить объект»
  4. Будет создан новый объект в приложении и теперь его следует настроить, заполнить все поля в разделе параметры, также можно сменить логотип объекта, просто нажмите на изображение, особое внимание стоит уделить пункту «Номер SIM GSM контроллера» , его приложение будет использовать для идентификации объекта по номеру в SMS сообщении.
  5.   Далее следует перейти во вкладку «Оповещения» и создать нужное количество оповещений , нажимая на кнопку «добавить оповещение», заполнить карточки состоящие из 2 полей ввода. В первое «Текст SMS с текстом команды» — сюда следует вводить тот код который приходить со стороны GSM контроллера установленного на объекте. В поле же «Расшифровка кода SMS (словами)» — то что вы хотите видеть при получении сигнала. Примерно так это должно выглядеть:
  6. На этом настройка объекта закончена, и следует нажать кнопку СОХРАНИТЬ в нижней части экрана. он будет отображаться у нас сдери остальных на главной странице приложения
  7.  Аналогичные действия следует произвести для каждого объекта, на котором установлен GSM контроллер оповещения.
  8. Результатом работы программы будет визуальное оповещение, в котором будет видно на каком объекте имеется сработка и какая авария там произошла. Параллельно с визуальным оповещением подаётся сигнал тревоги, прекратить который можно нажав на кнопку «Сообщение принято».

 

Пишем Telegram Bot для оповещения о коммите в git репозитарий на базе Gitea и разворачиваем его в Google Cloud Platform

Здравствуйте как и обещал в продолжение моей статьи о Автоматической публикации приложения в Google Play , рассмотрю в деталях процесс написания Telegram Bot`a для оповещения команды тестировщиков о выпуске новой версии.

Регистрация Bota в Telegram и получение ID

Просто напишите пользователю @BotFather и следуйте его инструкциям.

Выполните последовательно следующий команды

/start
/newbot
bot_name
В итоге вы должны получить сообщение

Из этого сообщение нам понадобятся собственно

  • t.me/bot_name — Имя бота по которому мы будем добавлять его в каналы или писать в ЛС
  • token — это наш ключ для доступа к API

Подготовка проекта и подключение необходимых библиотек

Наш бот будет написан на Java и будет представлять из себя Spring Boot Web приложение, в качестве системы сборки будет использоваться maven

1) Создайте обычный Spring Boot проект, проще всего это сделать через встроенный конфигуратор в IntelliJ IDEA , либо используя Spring Initializr.

Выберите те зависимости которые посчитаете нужными, но для начала нам подойдёт минимальный набор

pom.xml
Минимальный проект будет иметь примерно такую структуру :

А теперь детальней по каждому классу :

BotConfig — Конфигурация подтягивающая настройки бота из application.properties
BotInitializer — Component регистрации/инициализации бота в системе Telegram

Телеграм API должно быть зарегистрировано в системе и делать это нужно уже после поднятия контекста Spring, по этому вешаем слушатель на ContextRefreshedEvent

Bot — Сервис инкапсулирующий в себе реализацию TelegramLongPollingBot,

Данный класс представляет из себя основной функционал для взаимодействия с Telegram

  • метод onUpdateReceived принимает и обрабатывает сообщения пришедшие в личку или в канал где бот администратор
WebHook — RestController обслуживающий API для реакции на события WebHook Gitea

Данный RestController обслуживает RestAPI с точкой входа http://you_ip:port/api/public/gitea/webhook , сюда наша система контроля версий Gitea будет делать PUSH запрос с JSON данными WebHook возникающего при различных событиях происходящих с вашим репозитарием.

TelegramBotGiteaApplication — Стартовый метод нашего Spring Boot проекта

Все те классы что вы наблюдаете в пакете Model , представляют сгенерированную модель GiteaWebHook по JSON-Schema взятому из оф документации по GiteaWebHookApi , удобнее всего это делать при помощи http://www.jsonschema2pojo.org/

Полный исходный код вы можете взять ЗДЕСЬ

Настройка Gitea для выполнения WebHook к нашему боту

Данная статья рассматривает вариант обслуживания API предоставляемое системой контроля версий Gitea но это не значит что вы не сможете сделать оповещение и без неё. Проявив некую долю усердия всё те-же WebHook можно реализовать через .git\hooks\post-update и curl , или некое API GitHub но эти реализации я доверяю вам, и здесь мы рассмотрим лишь вариант с Gitea:

  1. Итак, перейдите в репозитарий вашего проекта и войдите в его Настройки в раздел Автоматическое обновление . Нажмите на кнопку Добавить Webhook , выберете вариант Gitea
  2. В качестве URL обработчика укажите URL на котором у нас висит RestController http://you_ip:port/api/public/gitea/webhook
  3. Тип содержимого — application/json
  4. Секретный ключ — любой набор символов который мы в последующем внесём в наш application.properties
  5. На какие события этот webhook должен срабатывать? — выберите PUSH
  6. Галочку Активности оставьте включённой.

Всё это хорошо бот написан, Gitea настроена но нашему боту нужно где-то жить.

Публикация нашего бота в Google Cloud Platform, бесплатно

New customers get $300 in free credits to spend on Google Cloud. All customers get free usage of 20+ products. See offer details.

Такое сообщение мы видим на главной страницы этого сервиса, а именно они дают 300$ бесплатно на год для функционирования нашего приложения у них в облаке, и нам этого вполне себе хватит.

И так приступим, авторизуйтесь в Google Cloud Platform

1) В боковом меню перейдите в раздел Сompute Engine, и дождитесь его инициализации

2) В разделе Экземпляры ВМ, нажмите создать

Выберете конфигурацию инстанса к примеру вот такую, это будет оптимальным решением для нашего не требовательного приложения

Давайте зададим сразу правило Брандмауэра разрешающего трафик по порту 8080

Зайдите Cеть VPS — Брандмауэр

Создайте правило для порта 8080 по аналогии с 80 портом

Подключитесь к VM через SSH , непосредственно через браузер

Теперь мы может перейти к настройке нашей виртуальной машины

Для обновления информации об новейших версиях пакетов вводим

sudo apt update

Установим Java

sudo apt install default-jdk

Проверьте это выполнив

java - version

Если всё прошло хорошо вы должны наблюдать версию Java в консоли SSH

Теперь установим maven

sudo apt install maven

Сделаем clone репозитария с нашим ботом заготовкой

git clone https://github.com/leganas/TelegramBotGiteaLesson.git

Сделайте необходимые манипуляции по настройке appliation.properties , укажите валидный bot_name и token

nano ./TelegramBotGiteaLesson/src/main/resources/application.properties

Зайдём в папку с нашим ботом и соберём его при помощи maven

cd TelegramBotGiteaLesson/
mvn package

После того как maven соберёт наш проект

Бот полностью готов к запуску, выполните команду

java -jar ./target/telegrambotgitea-0.0.1-SNAPSHOT.jar

Используя внешний IP указанный в web консоли управления виртуальными машинами, проверьте его работоспособность открыв в браузере страницу http://YOU_IP:8080/

Если наш бот успешно подключился к Telegram мы можем написать ему проверочное сообщение в ЛС /hello , на что он должен ответить нам Привет

И так теперь у нас всё готово для того что бы добавить нашего бота в Telegram канал, дать ему права администратора (что бы он мог читать сообщения) и протестировать оповещение !

Добавил бота в канал и дав ему права , вы можете написать

/chartId

Теперь это ID можно добавить в application.properties и либо пересобрать проект , либо подкинуть этот файл рядом с jar. Внесите необходимые правки IP адреса в WebHook в настройках Gitea и опробуйте вашего бота.

У кого получилось вот так , тот молодец, о том как сделать jar сервисом писать уже не буду, уж очень много про это написано на хабре, а если не найдёте пишите в личку.

Надеюсь моё повествование было достаточно подробным, чтобы любой смог запустить свою зверушку.

Автоматизация публикации приложения в Google Play при помощи Jenkins

Для этого нам понадобится

1) Действующий account Google Play Developer

1) Сервер Linux с предустановленным Docker, в моём случае это Ubuntu 16.04

2) Установленный Android SDK

2) Jenkins — в данном случае развернём его при помощи Docker

3) Gitea — Удобная служба для собственного Git-репозитория (это не обязательно можно использовать и GItHub) её мы подымем также на базе Docker контейнера

Настройка Google Play Account

Давайте предположим что вы уже действующий разработчик и осуществляли публикацию своих приложений в ручном режиме, что даёт основание полагать что вам известны основные моменты процесса и их озвучивать не стоит, коснёмся лишь того что нам понадобится для автоматического развёртывания

1) Авторизуйтесь в Google Cloud Platform, если еще не создан проект то создаём его

2) В разделе IAM и администрирование — Сервисные аккаунты жмём Создать сервисный аккаунт

3) После заполнения соответствующих полей он будет создан и появится в списке, жмём на три точки с права и выбираем создать ключ, выбираем JSON, сохраните его он нам понадобится для настройки Jenkins

4) Авторизуйтесь в Gooogle Play Developer Console

5) В разделе Пользователи и разрешения нажимаем пригласить пользователя сервисного аккаунта по сгенерированному email , выставляем ему роль Релиз менеджер (при необходимости можете настроить права этого пользователя более детально)

6) Я предполагаю что Сертификат для ключа подписи приложения, и Сертификат ключа загрузки у вас уже настроен и это не требует пояснений.

Установка Android SDK

# Install latest JDK
sudo apt install default-jdk

sudo apt install android-sdk

Добавьте Android SDK в свой PATH, откройте ~/.bashrc в редакторе и скопируйте следующие строки

# Export the Android SDK path 
export ANDROID_HOME=$HOME/android-sdk
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools

# Fixes sdkmanager error with java versions higher than java 8
export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'

Для проверки запустите

source ~/.bashrc

Для того что бы просмотреть все доступные инструменты сборки выполните команду

sdkmanager --list

По аналогии с выполните установку интересующих вас версий инструментов для нужных платформ

sdkmanager "platform-tools" "platforms;android-28"

На этом установка Android SDK может считаться завершённой, и в дальнейшем нам понадобится лишь путь к установленным инструментам, в моём случае это выглядит как то так

Установка Gitea

Этот шаг является опциональным и совсем не обязательным если вы предпочитаете использовать git репозитарии такие как GitHub и им подобные и его можно пропустить, это малой степени повлияет на конечный результат. (На базе gitea в дальнейшем будет обсуждаться тема создания Telegram Bot`a для оповещения о публикациях)

Подробно по установке и настройке написано на оф сайте https://docs.gitea.io/en-us/install-with-docker/

По факту установка сводится к выполнению 2х действий

1) Перейдите на официальный репозитарий Gitea , на Docker HUB и скопируйте то что там написано в предварительно созданный файл

version: '2'
services:
  web:
    image: gitea/gitea:1.12.4
    volumes:
      - ./data:/data
    ports:
      - "3000:3000"
      - "22:22"
    depends_on:
      - db
    restart: always
  db:
    image: mariadb:10
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=changeme
      - MYSQL_DATABASE=gitea
      - MYSQL_USER=gitea
      - MYSQL_PASSWORD=changeme
    volumes:
      - ./db/:/var/lib/mysql

2) Сохраните и запустите команду docker-compose you_filename

3) Gitea установлена и доступна по URL http://you_IP:3000/

4) Создайте пользователя, репозитарий, сделайте PUSH исходного кода вашего проекта, и в моём случае для упрощения процесса публикации приложения все необходимые ключи для его подписи и деплоя я храню в месте с исходным кодом в системе контроля версий (да знаю это не совсем верно и вы вольны делать так как считаете нужным, к примеру создать отдельный volume для ключей и пробрасывать их jenkins для дальнейшего их использования gradle при подписании приложения, описание этого лишь раздует статью и потому не будет рассматриваться)

Предварительная настройка проекта

Для подписания нашего apk файла в автоматическом режиме на стороне сервера, нам нужен файл нашего keystore и правильно настроенный скрипт gradle , для этого добавим в него несколько секций

// Load keystore
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

// GenerateNameVersion
def getVersionNameTimestamp() {
    return new Date().format('yy.MM.ddHHmm')
}

// GenerateVersionCode
def getVersionCodeTimestamp() {
    def date = new Date()
    def formattedDate = date.format('yyMMddHHmm')
    def code = formattedDate.toInteger()
    println sprintf("VersionCode: %d", code)
    return code
}

......

android {
    signingConfigs {
        config {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }
......
    defaultConfig {
        versionCode getVersionCodeTimestamp()
        versionName "${getVersionNameTimestamp()}"

Содержимое файла keystore.properties

storePassword=you_password_keystore
keyPassword=you_password_key
keyAlias=you_key_name
storeFile=path_to_keystore_file
  • Я храню как файл с keystore.properties так и сам keystore в системе контроля версий, что не совсем правильно и не безопасно при публикации исходного кода в открытом виде к примеру на GitHub, по этому я не рекомендую использовать данный подход если у вас OpenSource проект , храните и подтягивайте их из отдельной папки.
  • Для публикации в Google Play требуется уникальная версия сборки, и я генерирую её скриптом Gradle на основании текущей даты и времени, и как вы понимаете это исключает использование каких либо вразумительных номеров версий и если вам это важно , то вам придётся придумать некий другой механизм автоматизации версионирования, я же остановился на этом варианте — потому как хотел что бы deploy происходил по нажатию одной лишь кнопки (PUSH)

Установка Jenkins

Его мы развернём используя Docker , я применяю уже настроенный образ с нужными мне инструментами из своего репозитария, вы же вольны использовать официальный, принципиально ни чего не измениться

docker run -it -d -v jenkins_home:/var/jenkins_home -v /usr/lib/android-sdk:/usr/lib/android-sdk -p 8080:8080 -p 50000:50000 --env JENKINS_OPTS="--prefix=/jenkins" --restart always leganas/ls_repository:jenkins

пробрасываем в качестве volumes в в наш контейнер папку с установленным Android SDK

Теперь он запущен и доступен по адресу http://you_IP:8080/jenkins/

После не хитрых манипуляций по заданию пароля Jenkins готов к работе, и теперь нам предстоит его настроить

1) Для начала нам нужно настроить окружение , заходим System Configuration — Глобальные настройки , и добавляем Environment variables (возможно ваши пути будут отличатся)

2) В разделе Настройки — Конфигурация глобальных инструментов проверяем настройки Git и Gradle (по факту обычно там всё уже настроено)

3) Заходим Настройки — Управление пользователями , выбираем пользователя и его настройки, ищем строку API Token , создаём новый и сохраняем его , он нам понадобится.

4) В разделе управления плагинами проверяем и если нужно устанавливаем плагины Git, Git clientGoogle OAuth Credentials plugin, Google Play Android Publisher

5) Заходим Настройки — Manage Credentials Configure credentials -Store — Jenkins — Global credentials (unrestricted)- и создаём там 2 ключа доступа |

  • для доступа к Git репозитарию

    в моём случае т.к. я использую Gitea я создаю обычную пару login/password , для GitHub есть специальный плагин и инструмент для авторизации

  • для публикации приложения в Google Play Market

    создаём ключ используя JSON файл который создали в первом разделе данной инструкции

6) Теперь создайте проект вашего приложения в Jenkins и настройте его

  • Управление исходным кодом — установите Git , укажите Repository URL и Credentials (которые создали на прошлом этапе)
  • Триггеры сборки — выставите Trigger builds remotely (e.g., from scripts), в поле ввода введите любой случайный текст (он нам понадобится для удалённой активизации процесса сборки) при помощи GIt huck
  • Добавьте шаг сборки Invoke Gradle script , и После сборочные операции — публикацию

7) Запустите проект в ручную использую Web интерфейс Jenkins и посмотрите на результат в истории сборок — Вывод консоли Если вы всё сделали верно то ваше приложение должно быть подтянуто из Git репозитария до актуальной версии ветки /master , собрано , подписано и опубликовано на Google Play.

Автоматизация запуска сборки

Если мы всё сделали правильно и проект удачно был опубликован на Google Play , можно перейти к настройке его автоматического deploy по Git событию PUSH в /master ветку

1) Если вы используете Gitea как я то зайдите в репозитарий вашего проекта — Настройки — Git хуки, и в post-receive нажмите редактировать, и добавьте нечто этого рода

#!/bin/bash
while read oldrev newrev refname
do
    branch=$(git rev-parse --symbolic --abbrev-ref $refname)
    if [ "master" = "$branch" ]; then
       curl --user you_user_name:you_user_token http://you_url_jenkins/job/you_project/build?token=you_tocken_build
    fi
done
  • you_user_name — Имя пользователя от лица которого jenkins будет производить сборку
  • you_user_token — Токен который был сгенерирован в настройках пользователя
  • you_url_jenkins и вообще весь путь до хука можно посмотреть на странице настройки проекта Jenkins и будет выглядеть он примерно так :

После проведения такого рода манипуляций сборка проекта Jenkins будет начинаться автоматически после PUSH события в master вашего репозитария.

Заключение

В случае положительных фитбека от статьи, в следующей части я расскажу как написать Telegram бота для оповещения ваших тестировщиков о наличии обновления приложения на Google Play

Надеюсь эта информация была полезна и как то облегчит вам жизнь по настройки автоматической публикации

Эффективное управление транзакциями в Spring

Всем добрый день!

Что ж, конец месяца у нас всегда интенсивные, вот и тут остался всего день до старта второго потока курса «Разработчик на Spring Framework» — замечательного и интересного курса, который ведёт не менее прекрасный и злой Юрий (как его называют некоторые студент за уровень требований в ДЗ), так что давайте рассмотрим ещё один материал, который мы подготовили для вас.

Поехали.

Введение

Большую часть времени разработчики не придают значения управлению транзакциями. В результате либо большую часть кода приходится переписывать позже, либо разработчик реализует управление транзакциями без знаний того, как оно на самом деле должно работать или какие аспекты необходимо использовать конкретно в их случае.

Важный аспект в управлении транзакциями — определение правильных границы транзакции, когда транзакция должна начинаться и когда заканчиваться, когда данные должны быть добавлены в БД и когда они должны быть откачены обратно (в случае возникновения исключения).

Самый важный аспект для разработчиков — это понять как реализовать управление транзакциями в приложении наилучшим образом. Поэтому давайте рассмотрим различные варианты.

Способы управления транзакциями

Транзакции могут управляться следующими способами:

1. Программное управление путем написания пользовательского кода

Это старый способ управления транзакциями.

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME");                                       EntityManager entityManager = entityManagerFactory.createEntityManager();                   
Transaction transaction = entityManager.getTransaction()                  
try                                       
{  
   transaction.begin();                   
   someBusinessCode();                    
   transaction.commit();  
}                  
catch(Exception ex)                   
{                     
   transaction.rollback();  
   throw ex;                  
}

Плюсы:

  • Границы транзакции очевидны в коде.

Минусы:

  • Он повторяющийся и подвержен ошибкам
  • Любая ошибка может иметь очень большое влияние.
  • Нужно написать множество шаблонов, также, если вы хотите вызвать другой метод из этого метода, вам снова нужно управлять им из кода.

2. Использование Spring для управления транзакциями

Spring поддерживает два типа управления транзакциями

1. Программное управление транзакциями: Вы должны управлять транзакциями с помощью программирования. Это способ достаточно гибкий, но его сложно поддерживать.

2. Декларативное управление транзакциями: Вы отделяете управление транзакциями от бизнес-логики. Вы используете только аннотации в конфигурации на основе XML для управления транзакциями.

Мы настоятельно рекомендуем использовать декларативные транзакции. Если вы хотите узнать причины, тогда читайте дальше, иначе переходите сразу к разделу Декларативное управление транзакциями, если хотите реализовать этот вариант.

Теперь давайте рассмотрим каждый подход детально.

2.1. Программное управление транзакциями:

Фреймворк Spring предоставляет два средства для программного управления транзакциями.

a. Использование TransactionTemplate (рекомендовано командой Spring):

Давайте рассмотрим как можно реализовать этот тип на примере кода, представленного ниже (взято из документации Spring с некоторыми изменениями)

Обратите внимание, что фрагменты кода взяты из Spring Docs.

Файл Context Xml:

<!-- Initialization for data source -->   
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">      <property name="driverClassName" value="com.mysql.jdbc.Driver"/>      
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>      
<property name="username" value="root"/>      
<property name="password" value="password"/>   
</bean>
<!-- Initialization for TransactionManager -->   
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">    
<property name="dataSource"  ref="dataSource" />  
</bean>
<!-- Definition for ServiceImpl bean -->   
<bean id="serviceImpl" class="com.service.ServiceImpl">    
<constructor-arg ref="transactionManager"/>   
</bean>

Класс Service:

public class ServiceImpl implements Service
{       
 private final TransactionTemplate transactionTemplate;
 // используйте инъекцию в конструктор чтобы предоставить PlatformTransactionManager   
 public ServiceImpl(PlatformTransactionManager transactionManager)
 {    
this.transactionTemplate = new TransactionTemplate(transactionManager);  
 }
// параметры транзакции здесь могут быть установлены явно, если это необходимо, обеспечивая больше контроля
 // Также мы можем сделать это через xml файл              
 this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);                   this.transactionTemplate.setTimeout(30); //30 секунд    
 /// и так далее

 public Object someServiceMethod()
 {       
   return transactionTemplate.execute(new TransactionCallback()
   {
    // код в этом методе выполняется в транзакционном контексте         
    public Object doInTransaction(TransactionStatus status)
    {               
    updateOperation1();    
       return resultOfUpdateOperation2();   
    }
  });  
}}

Если нет возвращаемого значения, используйте удобный класс TransactionCallbackWithoutResult с анонимным классом, как показано ниже:

transactionTemplate.execute(new TransactionCallbackWithoutResult()
{   
protected void doInTransactionWithoutResult(TransactionStatus status)
{      
 updateOperation1();     
  updateOperation2(); 
}
});

 

  • Экземпляры класса TransactionTemplate потокобезопасные, поэтому поддерживают не все диалоговые состояния.
  • Экземпляры TransactionTemplate тем не менее поддерживают конфигурационное состояние, поэтому, если классу необходимо использовать TransactionTemplate с разными настройками (например, другой уровень изоляции), то вам нужно создать два различных экземпляра TransactionTemplate, хотя в некоторых классах может использоваться один экземпляр TransactionTemplate.

b. Использование реализации PlatformTransactionManager напрямую:

Давайте снова посмотрим на эту опцию в коде.

<!-- Initialization for data source --> 
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">   
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>    
<property name="url" value="jdbc:mysql://localhost:3306/TEST"/>     
<property name="username" value="root"/>     
<property name="password" value="password"/> 
</bean>
<!-- Initialization for TransactionManager -->  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">     
<property name="dataSource"  ref="dataSource" />      
</bean>
public class ServiceImpl implements Service
{   
private PlatformTransactionManager transactionManager;
public void setTransactionManager( PlatformTransactionManager transactionManager)
{   
  this.transactionManager = transactionManager; 
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// Явное указание имени транзакции - это то, что может быть сделано только программно
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try
{   
// выполняем вашу бизнес-логику здесь
}
catch (Exception ex)
{   
txManager.rollback(status);  
throw ex;
}
txManager.commit(status);
}

Теперь, перед тем как переходить к следующему способу управления транзакциями, давайте посмотрим как определиться, какой из типов управления транзакциями выбрать.

Выбор между Программным и Декларативным управлением транзакциями:

  • Программное управление транзакциями — хороший выбор только в том случае, если у вас небольшое количество транзакционных операций. (В большинстве случаев это не транзакции.)
  • Имя транзакции может быть явно установлено только при Программном управлении транзакциями.
  • Программное управление транзакциями должно быть использовано когда вы хотите явно контролировать управление транзакциями.
  • С другой стороны, если в вашем приложении содержаться многочисленные транзакционные операции, стоит использовать декларативное управление.
  • Декларативное управление не позволяет управлять транзакциями в бизнес-логике и его несложно настроить.

2.2. Декларативные транзакции (Обычно используется почти во всех сценариях любого веб-приложения)

Шаг 1: Определите менеджер транзакций в контекстном xml файле вашего spring-приложения.

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
<tx:annotation-driven transaction-manager="txManager"/>

Шаг 2: Включите поддержку аннотаций, добавив запись в контекстном xml файле вашего spring-приложения.

ИЛИ добавьте @EnableTransactionManagement в ваш конфигурационный файл, как показано ниже:

@Configuration
@EnableTransactionManagement
public class AppConfig
{
...
}

Spring рекомендует аннотировать только конкретные классы (и методы конкретных классов) с аннотацией @Transactional в сравнении с аннотирующими интерфейсами.

Причина этого заключается в том, что вы помещаете аннотацию на уровень интерфейса, и если вы используете прокси-классы (proxy-target-class = «true») или сплетающий аспект (mode = «aspectj»), тогда параметры транзакции не распознаются инфраструктурой проксирования и сплетения, например Транзакционное поведение не будет применяться.

Шаг 3: Добавьте аннотацию @Transactional в класс (метод класса) или интерфейс (метод интерфейса).

<tx:annotation-driven proxy-target-class="true">

Конфигурация по умолчанию: proxy-target-class="false"

  • Аннотация @Transactional может быть помещена перед определением интерфейса, метода интерфейса, определением класса или публичным методом класса.
  • Если вы хотите, чтобы некоторые методы класса (помеченные аннотацией @Transactional) имели разные настройки атрибутов, такие как уровень изоляции или уровень распространения, разместите аннотацию на уровне метода, чтобы переопределить настройки атрибутов уровня класса.
  • В режиме прокси (который установлен по-умолчанию) могут быть перехвачены только “внешние“ вызовы метода, идущие через прокси. Это означает, что “самостоятельный вызов”, например метод в целевом объекте, вызывающий какой-либо другой метод целевого объекта, не приведет к фактической транзакции во время выполнения даже если вызываемый метод помечен с @Transactional.

Теперь давайте разберем разницу между атрибутами аннотации @Transactional

@Transactional (isolation=Isolation.READ_COMMITTED)

  • По умолчанию установлено Isolation.DEFAULT
  • В большинстве случаев, вы будете использовать настройки по-умолчанию до тех пор, пока у вас не появится особые требования.
  • Сообщает менеджеру транзакции (tx), что для текущего tx должен использоваться следующий уровень изоляции. Должен быть установлен в точке, откуда начинается tx, потому что мы не можем изменить уровень изоляции после запуска tx.

DEFAULT: Использовать уровень изоляции установленный по умолчанию в базовой базе данных.

READ_COMMITTED (чтение фиксированных данных): Постоянная, указывающая, что “грязное” чтение предотвращено; могут возникать неповторяющееся чтение и фантомное чтение.

READ_UNCOMMITTED (чтение незафиксированных данных): Этот уровень изоляции указывает, что транзакция может считывать данные, которые еще не удалены другими транзакциями.

REPEATABLE_READ (повторяемость чтения): Постоянная, указывающая на то, что “грязное” чтение и неповторяемое чтение предотвращаются; может появляться фантомное чтение.

SERIALIZABLE (упорядочиваемость): Постоянная, указывающая, что “грязное” чтение, неповторяемое чтение и фантомное чтение предотвращены.

Что означают эти жаргонизмы: “грязное” чтение, фантомное чтение или повторяемое чтение?

  • “Грязное” чтение (Dirty Read): транзакция «A» производит запись. Между тем, транзакция «B» считывает ту же самую запись до завершения транзакции A. Позже транзакция A решает откатится, и теперь у нас есть изменения в транзакции B, которые несовместимы. Это грязное чтение. Транзакция B работала на уровне изоляции READ_UNCOMMITTED, поэтому она могла считывать изменения, внесенные транзакцией A до того, как транзакция завершилась.
  • Неповторяющееся чтение (Non-Repeatable Read): транзакция «A» считывает некоторые записи. Затем транзакция «B» записывает эту запись и фиксирует ее. Позже транзакция A снова считывает эту же запись и может получить разные значения, поскольку транзакция B вносила изменения в эту запись и фиксировала их. Это неповторяющееся чтение.
  • Фантомные чтение (Phantom Read): транзакция «A» читает ряд записей. Между тем, транзакция «B» вставляет новую запись в этот же ряд, что и транзакция A. Позднее транзакция A снова считывает тот же диапазон и также получит запись, которую только что вставила транзакция B. Это фантомное чтение: транзакция извлекала ряд записей несколько раз из базы данных и получала разные результирующие наборы (содержащие фантомные записи).

@Transactional(timeout=60)

По умолчанию используется таймаут, установленный по умолчанию для базовой транзакционной системы.

Сообщает менеджеру tx о продолжительности времени, чтобы дождаться простоя tx, прежде чем принять решение об откате не отвечающих транзакций.

@Transactional(propagation=Propagation.REQUIRED)

Если не указано, распространяющееся поведение по умолчанию — REQUIRED.

Другие варианты: REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER и NESTED.

REQUIRED

Указывает, что целевой метод не может работать без активного tx. Если tx уже запущен до вызова этого метода, то он будет продолжаться в том же tx, или новый tx начнется вскоре после вызова этого метода.

REQUIRES_NEW

  • Указывает, что новый tx должен запускаться каждый раз при вызове целевого метода. Если уже идет tx, он будет приостановлен, прежде чем запускать новый.

MANDATORY

  • Указывает, что для целевого метода требуется активный tx. Если tx не будет продолжаться, он не сработает, выбросив исключение.

SUPPORTS

  • Указывает, что целевой метод может выполняться независимо от tx. Если tx работает, он будет участвовать в том же tx. Если выполняется без tx, он все равно будет выполняться, если ошибок не будет.

 

  • Методы, которые извлекают данные, являются лучшими кандидатами для этой опции.

NOT_SUPPORTED

  • Указывает, что целевой метод не требует распространения контекста транзакции.
  • В основном те методы, которые выполняются в транзакции, но выполняют операции с оперативной памятью, являются лучшими кандидатами для этой опции.

NEVER

  • Указывает, что целевой метод вызовет исключение, если выполняется в транзакционном процессе.
  • Этот вариант в большинстве случаев не используется в проектах.

@Transactional (rollbackFor=Exception.class)

Значение по умолчанию: rollbackFor=RunTimeException.class

В Spring все классы API бросают RuntimeException, это означает, что если какой-либо метод не выполняется, контейнер всегда откатывает текущую транзакцию.

Проблема заключается только в проверенных исключениях. Таким образом, этот параметр можно использовать для декларативного отката транзакции, если происходит Checked Exception.

@Transactional (noRollbackFor=IllegalStateException.class)

Указывает, что откат не должен происходить, если целевой метод вызывает это исключение.

Теперь последним, но самым важным шагом в управлении транзакциями является размещение аннотации @Transactional. В большинстве случаев возникает путаница, где должна размещаться аннотация: на сервисном уровне или на уровне DAO?

@Transactional: Сервисный или DAO уровень?

Сервис — лучшее место для размещения @Transactional, сервисный уровень должен содержать поведение варианта использования на уровне детализации для взаимодействия пользователя, которое логически переходит в транзакцию.

Существует много CRUD-приложений, у которых нет существенной бизнес-логики, имеющих сервисный уровень, который просто передает данные между контроллерами и объектами доступа к данным, что не является полезным. В этих случаях мы можем поместить аннотацию транзакции на уровень DAO.

Поэтому на практике вы можете поместить их в любом месте, это зависит от вас.

Кроме того, если вы поместите @Transactional в уровень DAO и если ваш уровень DAO будет повторно использоваться разными службами, тогда будет сложно разместить его на уровне DAO, так как разные службы могут иметь разные требования.

Если ваш сервисный уровень извлекает объекты с помощью Hibernate, и, допустим, у вас есть ленивые инициализации в определении объекта домена, тогда вам нужно открыть транзакцию на сервисном уровне, иначе вам придется столкнуться с LazyInitializationException, брошенным ORM.

Рассмотрим другой пример, когда ваш уровень обслуживания может вызывать два разных метода DAO для выполнения операций БД. Если ваша первая операция DAO завершилась неудачей, остальные две могут быть переданы, и вы закончите несогласованное состояние БД. Аннотирование на сервисном уровне может спасти вас от таких ситуаций.

Надеюсь, эта статья вам помогла.

THE END

Original : https://habr.com/ru/company/otus/blog/431508/