06 December, 2010

Сокращаем ссылки на андроиде

Вступление
Встала передо мной задача - сокращать ссылки перед тем, как отправлять их в Twitter. Для решения этой задачи я решил использовать bit.ly, благо, их API внятный и простой.
Программируем!
Решение нарисовалось в виде следующего класса:
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;


/**
 * Helper class to work with bitly.
 *
 * @author Oleg Atamanenko
 * @since 06-Dec-2010 12:49:36
 */
public class Bitly {

    private static final String TAG = "Bitly";

    private static final String SHORTEN = "/v3/shorten";
    private static final String API_URL = "api.bit.ly";
    private static final String RESPONSE_FORMAT = "json";

    private String username;
    private String apiKey;


    public Bitly(String username, String apiKey) {
        this.username = username;
        this.apiKey = apiKey;
    }

    public String shorten(String longUrl) throws BitlyException {
        DefaultHttpClient httpClient = new DefaultHttpClient();

        try {
            List params = new ArrayList();
            params.add(new BasicNameValuePair("login", username));
            params.add(new BasicNameValuePair("apiKey", apiKey));
            params.add(new BasicNameValuePair("longUrl", longUrl));
            params.add(new BasicNameValuePair("format", RESPONSE_FORMAT));


            URI uri = URIUtils.createURI("http", API_URL, -1, SHORTEN, URLEncodedUtils.format(params, "UTF-8"), null);
            HttpGet request = new HttpGet(uri);

            Log.d(TAG, "Sending request: " + request.getURI());

            HttpResponse httpResponse = httpClient.execute(request);

            HttpEntity httpEntity = httpResponse.getEntity();
            String response = EntityUtils.toString(httpEntity);
            Log.i(TAG, "Bitly response is: " + response);
            httpResponse.getEntity().consumeContent();

            JSONObject jsonResponse = new JSONObject(response);

            checkForExceptions(jsonResponse);

            JSONObject data = jsonResponse.getJSONObject("data");
            return data.getString("url");

        } catch (ClientProtocolException e) {
            throw new BitlyException(e);
        } catch (IOException e) {
            throw new BitlyException(e);
        } catch (JSONException e) {
            throw new BitlyException(e);
        } catch (URISyntaxException e) {
            throw new BitlyException(e);
        }
    }

    private void checkForExceptions(JSONObject jsonResponse) throws JSONException, BitlyException {
        int statusCode = jsonResponse.getInt("status_code");
        if (statusCode != 200) {
            String message = jsonResponse.getString("status_txt");
            throw new BitlyException(message);
        }
    }

}
Конструктор класс принимает на вход следующие параметры: Полная документация к Bit.ly API расположена на отдельном проекте в Google Code
Использование
Единственный метод, реализованный сейчас - это метод shorten(). На вход требуется подать полную ссылку longUrl, на выходе получается укороченная версия ссылки, либо кидается исключение с сообщением от bit.ly API.
Пример вызова
Bitly bitly = new Bitly(BITLY_USERNAME, BITLY_API_KEY);
String shortLink = bitly.shorten(link);

Дальнейшие улучшения
Если перед вами стоит задача быстренько сократить ссылку - то вышеприведённого кода достаточно. Но если вам нужно полноценное решение, со всеми возможностями bit.ly - то посмотрите в сторону bitlyj. Правда, я не уверен, что оно взведётся под андроидом.

21 May, 2010

Удаление различных диакритических символов из строки

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

Например, из строки explicación получить explicacion.

package com.blogspot.atamanenko;

import java.text.Normalizer;
import java.text.Normalizer.Form;

public class StringNormalizer {

    public static String normalize(String string) {
        return Normalizer.normalize(string, Form.NFD)
            .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
    }
}

Вызов Normalizer.normalize проводит нормализацию входной строки. Последующий вызов регулярного выражения удаляет все диакритические знаки, полученные после нормализации.

Создание больших объёмов тестовых данных с помощью Databene Benerator

Периодически необходимо решать задачу создания больших ( и не очень) объёмов тестовых данных для проведения различных видов тестирования - функционального, нагрузочного (тестирование стабильности и производительности). При этом часто получается так, что система на тестовых данных ведёт себя совсем иначе, чем на реальных данных. Причина кроется в том, что создать правдоподобные тестовые данные всегда достаточно сложно.

Изучая данный вопрос я наткнулся на замечательный фреймворк - Databene Benerator, основной целью создания которого как раз и является создание правдоподобных тестовых данных для проведения различных видов тестирования.

Установка

Установка фреймворка осуществляется двумя способами - как отдельное приложение и как плагин для Maven.

Установка под Maven

Для использования Databene benerator в проектах, использующих для сборки Apache Maven необходимо добавить в зависимости databene-benerator и сконфигурировать его.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.myorganization</groupId>
  <artifactId>databene-benerator-test</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>
  <name>data generation project</name>
  <dependencies>
    <dependency>
      <groupId>org.databene</groupId>
      <artifactId>databene-benerator</artifactId>
      <version>0.5.9</version>
    </dependency>
    <dependency>
      <groupId>org.databene</groupId>
      <artifactId>databene-webdecs</artifactId>
      <version>0.4.9</version>
    </dependency>
    <dependency>
      <groupId>org.databene</groupId>
      <artifactId>databene-commons</artifactId>
      <version>0.4.9</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.6</version>
    </dependency>
  </dependencies>
  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <encoding>UTF-8</encoding>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.databene</groupId>
        <artifactId>maven-benerator-plugin</artifactId>
        <version>0.5.9</version>
        <executions>
          <execution>
            <phase>compile</phase>
            <goals>
              <goal>generate</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <descriptor>src/main/resources/benerator.ben.xml</descriptor>
          <encoding>UTF-8</encoding>
          <validate>true</validate>
          <dbUrl>jdbc:mysql://localhost:3306/hrtool?useUnicode=true&characterEncoding=UTF-8</dbUrl>
          <dbDriver>com.mysql.jdbc.Driver</dbDriver>
          <dbSchema>database</dbSchema>
          <dbUser>user</dbUser>
          <dbPassword>password</dbPassword>
        </configuration>
        <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.13</version>
            <scope>runtime</scope>
          </dependency>
          <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.5-beta5</version>
            <scope>runtime</scope>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

В конфигурации необходимо указать как минимум следующие параметры:

  1. dbUrl - строка подключения к базе данных в формате JDBC
  2. dbDriver - используемый драйвер базы данных
  3. dbSchema - имя схемы базы данных
  4. dbUser - имя пользователя, под которым подключаемся к базе данных
  5. dbPassword - пароль пользователя для подключения к базе данных

Кроме использования файла pom.xml databene поддерживает возможность указания параметров в файле benerator.properties, что может быть удобно.

Databene benerator обладает следующими возможностями:

  1. Решение проблемы создания данных в общем виде. На данный момент поддерживаются XML и реляционные базы данных, но не за горами поддержка веб-сервисов, SAP и любых других систем через механизм расширений.
  2. Юзабилити. Databene-benerator позволяет упростить создание тестовых данных для сложной модели прикладной области.
  3. Обработка больших объёмов данных.
  4. Высокая производительность.
  5. Поддержка доменных областей.
  6. Качество данных. Фреймворк поддерживает проверку ограничений модели прикладной области.
  7. Компонентная, легкорасширяемая архитектура.
  8. Широкие возможности изменения и настройки генерации тестовых данных
  9. Создание тестовых данных с нуля.
  10. Импорт и анонимизация реальных данных.

В комплекте идёт толковая, но, к сожалению, неполная документация по возможностям.

11 April, 2010

Несколько слов о GORM

В данной заметке хочу поделиться некоторыми моментами использования GORM.

GORM - это ORM-фреймворк, используемый в Grails. Реализован он поверх Hibernate, но, при этом, с некоторыми отличными умолчаниями.

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

Маппинг один-ко-многим

По умолчанию GORM для связей один ко многим (one-to-many) создаёт таблицу-связку, которая обычно нужна только при связях между сущностями вида многие ко многим. Чтобы исправить это поведение необходимо указать GORM, чтобы он не создавал таблицу связку.

class Person implements Serializable {
  static hasMany = [
    scores: ScoreSheet
  ]
  
  static mapping = {
    scores joinTable: false
  };
}

Использование однонаправленных связей

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

class Note implements Serializable {
  static belongsTo = [
    person: Person
  ]
}

class Person implements Serializable {
  // person fields.
}

Для работы с Notes необходимо использовать такие запросы:

  Note.findByPerson(person).each { -> };

вместо

  person.notes.each { -> }

Маппинг иерархии классов доменных сущностей

GORM поддерживает только два варианта маппинга иерархии классов, в отличии от Hibernate: Таблица на всю иерархию (table-per-hierarchy), или таблица на каждый подкласс (table-per-subclass). У маппинга table-per-hierarchy есть серьёзный недостаток - подклассы не могут иметь ненулевые поля. Поэтому, если этот недостаток критичен, то необходимо использовать маппинг table-per-subclass.

class Payment {
  Long id
  Long version
  Integer amount

  static mapping = {
    tablePerHierarchy false
  }
}

class CreditCardPayment extends Payment {
  String cardNumber
}
 

22 March, 2010

Общение со Skype через D-Bus на Python

Summary: в данной заметке описывается работа с программой Skype через D-Bus на Python.

Введение

Захотелось мне странного - когда я ухожу домой, мне нужно выключить amarok, kopete и Skype. Собственно, решено было через D-Bus отправлять вышеперечисленным приложениям релевантные сообщения.

Используем dbus-send

Сначала я использовал обычный dbus-send, что оформилось в виде следующего скрипта go2home:

#!/bin/sh

# Stop amarok
dbus-send --session --type=method_call --dest=org.kde.amarok /Player org.freedesktop.MediaPlayer.Stop

# Logout from kopete
dbus-send --session --type=method_call --dest=org.kde.kopete /Kopete org.kde.Kopete.disconnectAll 

# Logout from Skype
skypeapi.py 'SET USERSTATUS OFFLINE'

# Lock screen
dbus-send --session --type=method_call --dest=org.freedesktop.ScreenSaver /ScreenSaver org.freedesktop.ScreenSaver.Lock
Детали и параметры работы команды dbus-send описаны в man-странице

Проблема со скайпом

Со скайпом пришлось немного повозиться, так как для работы с ним необходима постоянная сессия, что нельзя сделать с помощью dbus-send.

Прочитав описание протокола на сайте

был создан нижеследующий скрипт: skypeapi.py

#!/usr/bin/env python
import dbus, sys

def main():
    remote_bus = dbus.SessionBus()
    
    # Check if skype is running.
    system_service_list = remote_bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus').ListNames()
    skype_api_found = 0
    for service in system_service_list:
        if service=='com.Skype.API':
            skype_api_found = 1
            break
    if not skype_api_found:
        sys.exit('No running API-capable Skype found')

    # Get skype dbus api
    skype_service = remote_bus.get_object('com.Skype.API', '/com/Skype')

    # Connect to skype.
    answer = skype_service.Invoke('NAME SkypeApiClient')
    if answer != 'OK':
        sys.exit('Could not bind to Skype client')

    # Check if protocol is supported.
    answer = skype_service.Invoke('PROTOCOL 1')
    if answer != 'PROTOCOL 1':
        sys.exit('This test program only supports Skype API protocol version 1')

    # Invoke operations
    for arg in sys.argv:
        skype_service.Invoke(arg)
    
    return 0    

if __name__ == "__main__":
    main()

При первом запуске скрипта появится скайповский диалог с вопросом, можно ли разрешить приложению доступ к скайпу. После нажатия на "Да" Skype добавит наш скрипт в разрешённые и мы сможем управлять скайпом.

После этого для скрипта go2home был создана иконка на панели.

Заключение

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

21 March, 2010

Разработка макроса для TiddlyWiki

Summary: Пример разработки плагина для TiddlyWiki

Вступление

TiddlyWiki - это вики-движок, полностью написанный на JavaScript и хранящийся в одном файле (как сам движок, так и содержимое). Создатели позиционируют его как "переиспользуемую нелинейную персональную веб записную книжку".

Я давно использую TiddlyWiki в различных целях:

  1. По прямому назначению.
  2. Как систему GTD.
  3. Как домашнюю страницу на компьютере.

Моя домашняя страница на компьютере - это, если говорить терминами TiddlyWiki, тиддлер, содержащий ссылки на страницы, которые я часто посещаю.

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

Исходный код плагина

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

/*{{{*/
version.extensions.faviconLinkMacro = {major: 0, minor: 1, revision: 0, date: new Date(2010,3,21)};
// Author: Oleg Atamanenko
config.macros.faviconLink = {}
config.macros.faviconLink.handler = function(place, macroName,  params, wikifier, paramString) {
  var linkBox = createTiddlyElement(place, "span", null, "favIcon", "");

  var args = paramString.parseParams("list",null,true);
  var link = getParam(args, "link", 'false');
  
  if (link != 'false'){
    urlParts = link.split('/');
    imgLink = urlParts[0] + "//" + urlParts[2] + "/favicon.ico";

    var imgElement = createTiddlyElement(linkBox, "img", null, "faviconImage", "");
    imgElement.src = imgLink;
    imgElement.width = 16;
    imgElement.height = 16;

    var linkTitle = getParam(args, "title", 'false');

    if(linkTitle == 'false'){
      linkTitle = link;
    }
    var linkElement = createTiddlyElement(linkBox, "a", null, "faviconLink", linkTitle);
    linkElement.href = link;
    linkElement.target = '_blank';
  }
}

/*}}}*/

Установка плагина

  1. Создаём новый тиддлер, называем его faviconLinkMacro
  2. Помечаем тиддлер тегом systemConfig, тогда TiddlyWiki при открытии страницы его подгрузит.

Использование плагина

  1. Плагин создаёт новый макрос, faviconLink, с двумя параметрами link и title.
  2. Вызов <<faviconLink link:'https://mail.google.com/mail' title:'mail'>> создаёт ссылку с картинкой с GMail

Настройка визульных стилей плагина

Макрос создаёт следующие классы:

  1. span.favIcon
  2. img.faviconImage
  3. a.faviconLink

Пример стилей

.links span {
  display: block;
  position: relative;
  padding: 0.7em;
  margin: 0.3em;
  min-width: 6em;
  float:left;
  text-align: center;
  font-weight: bold;
  font-size: 1.5em;
  font-family: "Gill Sans MT", "Candara", "Arial"; 
  border: 2px solid [[ColorPalette::SecondaryLight]];
  background-color: [[ColorPalette::SecondaryPale]];
}

.links span:hover {
  border: 2px solid [[ColorPalette::SecondaryMid]];
  background-color: [[ColorPalette::SecondaryLight]];
  color: [[ColorPalette::PrimaryMid]];
}

.links img.faviconImage {
  position: relative;
  padding-right: 10px;
}

Данный CSS необходимо добавить в тиддлер с именем StyleSheet

Пример тиддлера со ссылками

{{links{
<<faviconLink link:'http://delicious.com/dark.schakal/2read' title:'2read'>> <<faviconLink link:'https://mail.google.com/mail' title:'mail'>> <<faviconLink link:'http://www.google.com/reader/view' title:'rss'>> <<faviconLink link:'http://atamanenko.blogspot.com/' title:'blog'>> <<faviconLink link:'http://feedburner.google.com/fb/a/myfeeds' title:'feedburner'>> <<faviconLink link:'https://www.google.com/analytics/settings/' title:'analytics'>> <<faviconLink link:'http://wave.google.com' title:'wave'>> <<faviconLink link:'http://www.blogger.com/home' title:'blogger'>> <<faviconLink link:'http://twitter.com/' title:'twitter'>> <<faviconLink link:'https://www.dropbox.com/home#/' title:'dropbox'>> <<faviconLink link:'http://translate.google.com/toolkit/' title:'translator'>> <<faviconLink link:'http://sibir.megafon.ru/sendsms/' title:'SendSMS'>>
}}}
Вышеприведённый код тиддлера создаёт следующую страницу:

06 February, 2010

Список для проверки при оптимизации Grails приложений

Выкладываю ниже список задач, которые нужно/можно выполнить для оптимизации приложения, написанного на Grails, может кому пригодится.

Тестирование проведённых оптимизаций

Первым делом необходимо разработать критерии проверки, которые позволят оценить эффективность проведённых оптимизаций.
  1. Установить Java Melody плагин для Grails для проведения анализа.
  2. Разработать скрипты для проведения нагрузочного тестирования.
  3. Прогнать скрипты.
  4. Проанализировать результаты Java Melody, выявить узкие места, произвести нужные оптимизации.

Общие оптимизации

Очень часто обновление до последней версии используемых библиотек попутно улучшает производительность.
  1. Обновить Java до последней версии
  2. Обновить Groovy до последней версии.
  3. Обновить Grails до последней версии.
  4. Обновить jQuery до последней версии.
  5. Оптимизировать настройки виртуальной машины Java (например, -server -Xmx270m -Xms270m -XX:MaxPermSize=80m -Xverify:none -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:+UseAdaptiveSizePolicy -XX:SurvivorRatio=4 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31 -XX:+AggressiveOpts)
    a. http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html
    Вообще, по оптимизации JVM написаны целые книги, так что не буду здесь останавливаться.

Оптимизация клиентской части

  1. Настроить кэширование статических ресурсов в Томкате.
  2. Настроить сжатие статических ресурсов в Томкате.
  3. (поставить прокси на nginx для кэширования статических ресурсов ?)
  4. Установить плагины для Firebug, которые оценят производительность клиентской части.
  5. Проанализировать страницы веб-сайта с их помощью
  6. Провести предлагаемые оптимизации.

Оптимизация клиентской части приложений, написанных на Grails

  1. Установить плагин UI Performance
  2. Провести оптимизацию с использованием плагина UI Performance

Оптимизация базы данных

  1. Оптимизировать производительность сервера базы данных (SQL Server)
    a. http://www.sql-server-performance.com/tips/all_main.aspx
    a. http://msdn.microsoft.com/en-us/library/ms998577.aspx
  2. Проверить наличие индексов в базе данных, добавить необходимые
  3. Найти неоптимальные запросы в БД, оптимизировать

Оптимизация работы с базой данных

  1. Обновить версию JDBC-драйвера
  2. Настроить кэширование в Hibernate.
    Ресурсов в сети предостаточно, навскидку вот парочка.

Оптимизация серверной части приложения

  1. Произвести профилирование приложения (использовать Visual VM, YJP Profiler), найти узкие места и оптимизировать их.

Оптимизация серверной части приложения (опционально)

  1. Установить плагин Perf4J для грэйлза
  2. Добавить необходимые счётчики в код
  3. Прогнать нагрузочные тесты, найти узкие места, оптимизировать.

Заключение

В целом, в данном списке нет ничего нового, это всего лишь компиляция публично доступных материалов. Тем не менее, решил это опубликовать, чтобы сэкономить время другим.

04 February, 2010

Вложенные транзакции в базах данных

Summary: Некоторые особенности вложенных транзакций.

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

Очень многие базы данных не поддерживают вложенные транзакции вообще, например, MySQL и Oracle. А те, что поддерживают, делают это на минимальном уровне, например, Sybase поддерживает только псевдовложенные транзакции.

Вложенные транзакции могут быть следующих видов:

  1. Псевдо-вложенные транзакции
  2. Вложенная субтранзакция
  3. Вложенная независимая транзакция

Псевдовложенные транзакции


Этот тип транзакций поддерживается базой данных Sybase.
Псевдо-вложенные транзакции позволяют использовать транзакции одна внутри другой, при этом не реализуя механизм вложенных транзакций. Суть состоит в том, что имеется счётчик уровня вложенности транзакций, который увеличивается на 1 при каждом вызове BEGIN TRANSACTION и уменьшается на 1 при каждом вызове COMMIT. Когда счётчик становится равным 0 происходит коммит всех произведённых изменений.

Если во время проведения транзакции происходит ошибка, то вызывается ROLLBACK, но здесь есть загвоздка - что делать с последующими вызовами BEGIN TRANSACTION и COMMIT. Оптимальным решением является кидание исключения, но это необходимо отслеживать и учитывать на стадии проектирования и реализации, чтобы не возникло неприятных ситуаций при эксплуатации.

Псевдовложенные транзакции являются самым простым способом реализации вложенных транзакций.

Пример псевдотранзакции для базы данных Sybase:
begin tran
    select @@trancount
    /* @@trancount = 1 */
    begin tran
        select @@trancount
        /* @@trancount = 2 */
        begin tran
            select @@trancount
            /* @@trancount = 3 */
        commit tran
    commit tran
commit tran
select @@trancount
/* @@ trancount = 0 */

Вложенная субтранзакция


Данный вид вложенных транзакций решает проблему с обработкой ошибок при ROLLBACK.
При коммите вложенной транзакции происходит коммит, но он не обладает свойством Durability - окончательный результат зависит от результата внешней транзакции - если внешняя транзакция заканчивается успешно, то и результат внутренней транзакции также фиксируется. Если же при фиксации изменений внешней транзакции происходит ошибка, то внутренняя транзакция также откатывается. Кроме того, необходимо обратить внимание на то, что если внутренняя транзакция заканчивается неуспешно, то внешняя транзакция может закончиться успешно, если не выкинуть исключение наружу. Таким образом, для данного вида вложенных транзакций необходимо выкидывать исключение наружу, для того, чтобы прервать и откатить внешнюю транзакцию в случае ошибки.

Вложенная независимая транзакция

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

Вложенные транзакции и принципы ACID

Вложенные транзакции выглядят достаточно подозрительными в том смысле, что они могут нарушать принципы ACID:
1. Вложенная субтранзакция может нарушать принцип Durability, так как уже зафиксированные изменения могут откатиться в случае отката внешней транзакции.
2. Вложенная независимая транзакция может нарушать принципы Atomicity и Consistency при возникновении ошибок во внешней или вложенной транзакции.

Подробнее о принципах ACID можно прочитать в этом посте.

Заключение

Я считаю, что следует избегать ситуаций, когда нужны вложенные транзакции. Кроме того, спецификация Java EE не поддерживает вложенные транзакции.