Статьи

Java 8 Friday Goodies: объем локальной транзакции

В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…

Ява 8 Пятница

Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, в которых используются лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .

Java 8 Goodie: объем локальной транзакции

Люди JavaScript часто используют анонимные функции для создания локальной области видимости. Как и любая другая языковая функция, этим можно злоупотреблять , но в некоторых контекстах локальная область видимости действительно потрясающая. Java также допускает локальную область видимости, хотя до Java 8 это было одинаково громоздко:

JavaScript

01
02
03
04
05
06
07
08
09
10
(function() {
    var local = function() {
            scoping();
        },
        scoping = function() {
            alert('If you really must');
        };
 
    local();
})();

Ява

1
2
3
4
5
6
7
8
9
new Object() {
    void local() {
        scoping();
    }
    void scoping() {
        System.out.println(
            "Ouch, my fingers. Too much typing");
    }
}.local();

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

Неловкость может быть шаблоном дизайна в JavaScript.

Локальный обзор в Java 8

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

1
2
3
4
@FunctionalInterface
interface Transactional {
    void run(DSLContext ctx);
}

Для примера мы собираемся использовать jOOQ, чтобы избежать проверенных исключений и создания подробного оператора. Вы можете заменить его на свой SQL API по своему выбору. Итак, jOOQ предоставляет нам локально ограниченный объект ctx , который неявно содержит состояние транзакции. Это состояние транзакции генерируется с использованием TransactionRunner:

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
class TransactionRunner {
    private final boolean silent;
    private final Connection connection;
 
    TransactionRunner(Connection connection) {
        this(connection, true);
    }
 
    TransactionRunner(Connection connection,
                      boolean silent) {
        this.connection = connection;
        this.silent = silent;
    }
 
    void run(Transactional tx) {
        // Initialise some jOOQ objects
        final DefaultConnectionProvider c =
            new DefaultConnectionProvider(connection);
        final Configuration configuration =
            new DefaultConfiguration()
                .set(c).set(SQLDialect.H2);
 
        try {
            // Run the transaction and pass a jOOQ
            // DSLContext object to it
            tx.run(DSL.using(configuration));
 
            // If we get here, then commit the
            // transaction
            c.commit();
        }
        catch (RuntimeException e) {
 
            // Any exception will cause a rollback
            c.rollback();
            System.err.println(e.getMessage());
 
            // Eat exceptions in silent mode.
            if (!silent)
                throw e;
        }
    }
}

Выше приведен код фреймворка, который мы напишем только один раз. Отныне мы можем использовать вышеуказанный API в наших программах на Java. Для этого мы настроим TransactionRunner следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
public static void main(String[] args)
throws Exception {
    Class.forName("org.h2.Driver");
    try (Connection c = DriverManager.getConnection(
            "jdbc:h2:~/test-scope-goodies",
            "sa", "")) {
        c.setAutoCommit(false);
        TransactionRunner silent =
            new TransactionRunner(c);
 
        // Transactional code here ...
    }
}

А теперь вот чудеса Java 8!

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
// This is a transaction
silent.run(ctx -> {
    ctx.execute("drop table if exists person");
    ctx.execute("create table person(" +
                "  id integer," +
                "  first_name varchar(50)," +
                "  last_name varchar(50)," +
                "  primary key(id)"+
                ")");
});
 
// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" +
                "  values(1, 'John', 'Smith');");
    ctx.execute("insert into person" +
                "  values(1, 'Steve', 'Adams');");
    // Ouch, fails -------^
    // Transaction rolls back
});
 
// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" +
                "  values(2, 'Jane', 'Miller');");
    // Works, yay!
});
 
// And this is also one transaction
silent.run(ctx -> {
    ctx.execute("insert into person" +
                "  values(2, 'Anne', 'Roberts');");
    // Ouch, fails -------^
    // Transaction rolls back
});

Что мы получаем из вышеперечисленного? Давай проверим:

1
2
3
4
silent.run(ctx -> {
    System.out.println(
        ctx.fetch("select * from person"));
});

Приведенная выше программа выдаст такой вывод:

01
02
03
04
05
06
07
08
09
10
11
SQL [insert into person values(1, 'Steve', 'Adams');];
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement:
insert into person values(1, 'Steve', 'Adams'); [23505-174]
SQL [insert into person values(2, 'Anne', 'Roberts');];
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.PERSON(ID)"; SQL statement:
insert into person values(2, 'Anne', 'Roberts'); [23505-174]
+----+----------+---------+
|  ID|FIRST_NAME|LAST_NAME|
+----+----------+---------+
|   2|Jane      |Miller   |
+----+----------+---------+

Итак, наши коммиты и откаты работали как положено!

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

Мы также можем создавать вложенные вызовы для нашего TransactionRunner, например, когда мы внутри методов, вызывающих другие методы. Для этого придется адаптировать наш TransactionRunner для подсчета уровня вложенности и удалить «тихую» функциональность. С другой стороны, было бы очень легко реализовать функциональность точки сохранения таким способом. Каждый раз, когда мы вкладываем другую транзакцию, мы создаем новую точку сохранения.

Вывод

Как всегда в этой серии, мы не изобрели ничего нового. Все эти вещи можно было бы сделать с ванильной Java 7. Но клиентский код этого TransactionRunner определенно не выглядел бы таким же скудным, как наши лямбды.

На следующей неделе в этой серии блогов мы рассмотрим, как Java 8 позволит вам очень легко определять область локального кэширования, так что следите за обновлениями!

Ссылка: Java 8 Friday Goodies: объем локальных транзакций от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и AND JOOQ .