Статьи

Мы взламываем JDBC, поэтому вам не нужно

Мы любим работать с JDBC

Никто не сказал. Когда-либо.

Если говорить более серьезно, то JDBC на самом деле очень классный API, если подумать. Вероятно, это также одна из  причин, почему Java стала популярной платформой, которой она является сегодня . До JDK 1.1  и до ODBC  (и это было очень давно) было трудно представить какую-либо платформу, которая бы вообще стандартизировала доступ к базе данных. Черт,  сам SQL  почти не был стандартизирован в то время, и вместе с ним появилась Java с JDBC, простым API, содержащим всего несколько элементов, о которых вы должны знать в повседневной работе:

  • Connection: объект, который моделирует все ваши взаимодействия с БД
  • PreparedStatement: объект, который позволяет вам выполнить оператор
  • ResultSet: объект, который позволяет вам получать данные из базы данных

Это оно!

Обратно в реальность

Это была теория. На практике корпоративное программное обеспечение, работающее поверх JDBC, быстро развилось в направлении этого:

Взлом JDBC. Информация об авторских правах на  эту страницу

JDBC является одним из последних курортов для разработчиков Java, где они могут почувствовать себя настоящими хакерами, во многих отношениях взламывая этот очень насыщенный, очень многословный и очень загадочный API. Практически каждый, кто работает в JDBC, будет реализовывать обертки вокруг API, чтобы предотвратить по крайней мере:

  • Распространенные синтаксические ошибки
  • Несоответствие индекса переменной связывания
  • Динамическое построение SQL
  • Крайние случаи вокруг использования LOBs
  • Обработка и закрытие ресурсов
  • Управление массивом и UDT
  • Хранимая процедура абстракции

… и многое другое.

So while everyone is doing the above infrastructure work, they’re not working on their business logic. And pretty much everyone does these things, when working with JDBC. Hibernate and JPA do not have most these problems, but they’re not SQL APIs any longer, either.

Here are a couple of examples that we have been solving inside of jOOQ, so you don’t have to:

How to fetch generated keys in somedatabases

case DERBY:
case H2:
case MARIADB:
case MYSQL: {
    try {
        listener.executeStart(ctx);
        result = ctx.statement().executeUpdate();
        ctx.rows(result);
        listener.executeEnd(ctx);
    }

    // Yes. Not all warnings may have been consumed yet
    finally {
        consumeWarnings(ctx, listener);
    }

    // Yep. Should be as simple as this. But it isn't.
    rs = ctx.statement().getGeneratedKeys();
    try {
        List<Object> list = new ArrayList<Object>();

        // Some JDBC drivers seem to illegally return null
        // from getGeneratedKeys() sometimes
        if (rs != null) {
            while (rs.next()) {
                list.add(rs.getObject(1));
            }
        }

        // Because most JDBC drivers cannot fetch all
        // columns, only identity columns
        selectReturning(ctx.configuration(), list.toArray());

        return result;
    }
    finally {
        JDBCUtils.safeClose(rs);
    }
}

How to handle BigInteger and BigDecimal

else if (type == BigInteger.class) {

    // The SQLite JDBC driver doesn't support BigDecimals
    if (ctx.configuration().dialect() == SQLDialect.SQLITE) {
        return Convert.convert(rs.getString(index), 
            (Class) BigInteger.class);
    }
    else {
        BigDecimal result = rs.getBigDecimal(index);
        return (T) (result == null ? null : result.toBigInteger());
    }
}
else if (type == BigDecimal.class) {

    // The SQLite JDBC driver doesn't support BigDecimals
    if (ctx.configuration().dialect() == SQLDialect.SQLITE) {
        return Convert.convert(rs.getString(index),
            (Class) BigDecimal.class);
    }
    else {
        return (T) rs.getBigDecimal(index);
    }
}

How to fetch all exceptions from SQL Server

switch (configuration.dialect().family()) {
    case SQLSERVER:
        consumeLoop: for (;;)
            try {
                if (!stmt.getMoreResults() && stmt.getUpdateCount() == -1)
                    break consumeLoop;
            }
            catch (SQLException e) {
                previous.setNextException(e);
                previous = e;
            }
}

Convinced?

This is nasty code. And we have more examples of nasty code here, or in our source code.

All of these examples show that when working with JDBC, you’ll write code that you don’t want to / shouldn’t have to write in your application. This is why…

we have been hacking JDBC, so you don’t have to