2023-02-21

Spring Boot: Application Event Lifecycle

 With @EventListener, we can quickly turn a component's method into an event listener. Spring Boot has some predefined events we can tap into; they mark milestones in the application lifecycle.

  1. ApplicationStartingEvent
    Only the bootstrap context has been created, and "java.awt.headless" is turned off unless explicitly enabled externally.
  2. ApplicationEnvironmentPreparedEvent
    The environment sources are parsed and configured.
    Now you still have the opportunity to alter the environment, as the binding is a later step.
  3. ApplicationContextInitializedEvent
    Context is prepared, the environment is applied, and initializers have been executed. After this event, the bootstrap context is discarded.
  4. ApplicationPreparedEvent
    Beans were loaded, the magic happened.
  5. ApplicationStartedEvent
    The application context is refreshed, and we're alive.
    After this event is published, the liveness state is set to "correct" since there were no failures during initialization.
  6. On error between steps 2-5, an ApplicationFailedEvent is published before the shutdown hook is executed.
  7. ApplicationReadyEvent
    The startup is done, measurements are taken, startup info is logged, and runners are executed (command line or job). The readiness state is set to "accepting traffic" to let external actors know we're online.
After the started event, not much happens that affect us if we are making a standard application, so if we want to load some data that we couldn't or wouldn't initialize lazily on its own service, this is the spot.

2022-05-04

Java Memory Usage Optimization

So there is this not really well-known but existing memory usage optimization that changes how Glibc allocated thread-specific memory.

There is this guy who wrote the best roundup I found on the net so far: Major Bug in glibc is Killing Applications With a Memory Limit. I strongly suggest reading it.

For now, let me just quote the important part:

Long story short, this is due to a bug in malloc(). Well, it’s not a bug it’s a feature.

malloc() preallocates large chunks of memory, per thread. This is meant as a performance optimization, to reduce memory contention in highly threaded applications.

In 32 bits runtime, it can preallocate up to 2 * 64 MB * cores.

In 64 bits runtime, it can preallocate up to 8 * 64 MB * cores.

So the math is like: _NPROCESSORS_ONLN * $MALLOC_ARENA_MAX * Arena Size

Bonus content: As getconf _NPROCESSORS_ONLN returns the same as nproc output (well, almost, because nproc returns sysconf(_SC_NPROCESSORS_CONF)), if you are using a container engine like Kubernetes, this equation will use the node's core count, not the CPU shares allowed by cgroups to the pod.

Where do those numbers come from? check here: https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html

Arena Size is usually 64MB. Why is this a problem?

The first malloc in each thread triggers a 128MB mmap which typically is the initialization of thread-local storage.

-- https://bugs.openjdk.java.net/browse/JDK-8193521

For every thread created, a new arena is allocated. But even if you don't make any threads, the preallocation happens using the equation above. Huge memory waste.

If creating more arenas is denied, the thread instead writes to "main" arena or the native program heap, which is unbounded.

(Main arena can grow via brk()/sbrk())

So the most useful solution is to set the environment variable MALLOC_ARENA_MAX to a small value, like 4.

2021-01-17

In This Case...

Text camelCase PascalCase snake_case kebab-case
Save And Flush saveAndFlush SaveAndFlush save_and_flush save-and-flush
Is Valid isValid IsValid is_valid is-valid

2020-04-20

Spring: Implicit CLOB to String

So you have a stored procedure, which has an OUT parameter, with the type of CLOB.
You want to get the contents. It seems easy, right?

Well, you just need to actually ask Hibernate to turn it into a String for you in one easy step, using org.hibernate.type.MaterializedClobType:

PROCEDURE that_one_procedure(
    P_ARG1        IN  VARCHAR2,
    P_ARG2        IN  VARCHAR2,
    P_ARG3        IN  VARCHAR2,
    X_SOME_OUTPUT OUT NOCOPY CLOB
);

// Excerpt of an Entity class...
import org.hibernate.type.MaterializedClobType;

@NamedStoredProcedureQuery(name = "querySomeProcedure", procedureName = "MY_PACKAGE.THAT_ONE_PROCEDURE",
 parameters = {
   @StoredProcedureParameter(name = "P_ARG1", 
     type = String.class),
   @StoredProcedureParameter(name = "P_ARG2", 
     type = String.class),
   @StoredProcedureParameter(name = "P_ARG3", 
     type = String.class),
   @StoredProcedureParameter(name = "X_SOME_OUTPUT", 
     mode = ParameterMode.OUT, 
     type = MaterializedClobType.class) /* <-- !!! */

Now my Repository can look like this:
// Excerpt of a Repository...
@Procedure(name = "querySomeProcedure", outputParameterName = "X_SOME_OUTPUT")
 String querySomeProcedure(@Param("P_ARG1") String someArg,
   @Param("P_ARG2") String thatAnotherArg,
   @Param("P_ARG3") String alsoAnArg);

See, all the magic is done by Hibernate.

2019-07-31

Oracle: JDBC Auto-Commit is On by Default

Ever wondered what's the default on this?

Well, here it is: JDBC Developer's Guide

Scroll to section C.2.1 Disabling Auto-Commit Mode, and it says:
By default, new connection objects are in auto-commit mode.

2019-01-10

JDeveloper: JboConfigUtil Exception

So you were tinkering with JDeveloper (any version), and while trying to use an online database for your new entity / whatever, you instead got this:

SEVERE: null at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:235)
java.lang.NullPointerException
 at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:235)
 at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:225)
 at oracle.jbo.dt.jdevx.ui.pkg.XPKConnectPanel.updateConfigurations(XPKConnectPanel.java:703)
 at oracle.jbo.dt.jdevx.ui.pkg.XPKConnectPanel.onFinish(XPKConnectPanel.java:684)
 at oracle.jbo.dt.ui.main.dlg.DtuWizardPanelDialog.okAction(DtuWizardPanelDialog.java:362)
 at oracle.jbo.dt.ui.main.dlg.DtjDialog.dismissDialog(DtjDialog.java:221)
 (...)

You've already created the data source, set it in Project Properties [on the model project] / ADF Business Components, reported the feedback, saw it will be fixed in JDeveloper 13... but until then:

Go to the .jpx file of your model project (the file with the two cogwheels icon), General / Connection, select the connection.

2018-09-18

Chrome 69: Revert the ugly UI to normal

Is the new Chrome ugly as hell?

Go to chrome://flags/#top-chrome-md

Change "UI Layout for the browser's top chrome" to "Normal". Relaunch.

If it still won't change back to normal, disable this: "Use all upcoming UI features".

If still no luck, you need to disable also this: "New Tab Page Material Design Icons" chrome://flags/#ntp-icons

2018-07-03

Some Court Rulings from the Past

Ever wondered why people can resell OEM licences?

This is why: "An author of software cannot oppose the resale of his ‘used’ licences allowing the use of his programs downloaded from the internet"

Sources:
1. Court of Justice of the European Union - Press Release
2. Judgment of the Court


2018-04-19

Java 8: JDBC ResultSet to Stream

Streams API is a gift.
But JDBC is still the old-school one.
Let's wrap it!

package org.example.jdbc.stream;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public final class StreamHelper {
    public static class Record {
        private final Map<String, Object> fields = new HashMap<>(16);
        private final long count;

        private Record(final ResultSet resultSet) throws SQLException {
            final ResultSetMetaData metaData = resultSet.getMetaData();
            count = metaData.getColumnCount();
            for (int i = 1; i <= count; ++i) {
                fields.put(metaData.getColumnName(i), resultSet.getObject(i));
            }
        }

        /**
         * Is there a column named like this?
         *
         * @param columnName is the column name in the query.
         * @return True if found.
         */
        public boolean contains(final String columnName) {
            return fields.containsKey(columnName);
        }

        /**
         * Number of columns.
         *
         * @return Numer of columns.
         */
        public long count() {
            return count;
        }

        /**
         * Get value casted to the requested type.
         * <p>
         * No type checking happens inside. It is your job to know the datatype in the database.
         * <p>
         * Example:<br>
         * {@code record.get("COLUMN1", Long.class); // returns a Long}
         *
         * @param columnName is the column name in the query.
         * @param type is Java type of the column.
         * @return The value casted to the Java type.
         */
        public <T> T get(final String columnName, final Class<T> type) {
            return type.cast(getObject(columnName));
        }

        /**
         * Get columns in the record.
         *
         * @return Collection of the column names.
         */
        public Set<String> getColumns() {
            return Collections.unmodifiableSet(fields.keySet());
        }

        /**
         * Get value as an object.
         *
         * @param columnName is the column name in the query.
         * @return The value.
         */
        public Object getObject(final String columnName) {
            return fields.get(columnName);
        }

        /**
         * Get value as string.
         *
         * @param columnName is the column name in the query.
         * @return Value as string.
         */
        public String getString(final String columnName) {
            return Objects.toString(fields.get(columnName));
        }

        /**
         * Is the given cell null?
         *
         * @param columnName is the column name in the query.
         * @return True if null.
         */
        public boolean isNull(final String columnName) {
            return getObject(columnName) == null;
        }

        @Override
        public String toString() {
            return fields.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue())
                    .collect(Collectors.joining(", "));
        }
    }

    /**
     * Wrap a ResultSet in a Stream.
     * <p>
     * The wrapper consumes the result set. The caller must close the result set after the stream
     * processing was finished.
     *
     * @param resultSet is the open result set to streamline.
     * @return A stream of rows.
     */
    public static Stream<Record> asStream(final ResultSet resultSet) {
        // "est = Long.MAX_VALUE if infinite, unknown, or too expensive to compute."
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(Long.MAX_VALUE,
                Spliterator.NONNULL | Spliterator.IMMUTABLE) {
            @Override
            public boolean tryAdvance(final Consumer<? super Record> action) {
                try {
                    if (!resultSet.next()) {
                        return false;
                    }
                } catch (@SuppressWarnings("unused") final SQLException e) {
                    return false;
                }
                try {
                    action.accept(new Record(resultSet));
                } catch (@SuppressWarnings("unused") final SQLException e) {
                    return false;
                }
                return true;
            }
        }, true).parallel();
    }

    private StreamHelper() { /* Hidden. */ }
}
Usage example:
import org.example.jdbc.stream.StreamHelper;
import org.example.jdbc.stream.StreamHelper.Record;
// ...
    @GET
    @Path("test")
    public Response test() {
        try (Connection connection = Database.connect();
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery("SELECT * FROM MY_TABLE")) {
            return Response.ok(StreamHelper.asStream(resultSet).map(Record::toString)
                    .collect(Collectors.joining(", "))).build();
        } catch (final SQLException | NamingException e) {
            return Response.serverError().entity(e.toString()).build();
        }
    }