Code that lists and inserts data to database: Make methods more reusable in superclass
The first advise is to not over-architect the classes.

Keep alternatives in mind - JPA for instance eclipseLink. A bit of not reinventing the wheel as the database field has a long history of &quot;clever&quot; code.
This is an O/R mapping translating data base Relations into java Objects and vice versa. For a beginner I would first refrain from such magic, and continue as you did. (Your mapRow.)
The entity classes like Member in general are kept small without logic.
And for every entity class there is a repository class with data access (retrieve and such). They rely on a persistence manager singleton instance that takes care of the connection per transaction session (time doing some task).

A long ID and using generated keys is used often.
For the ID inheritance can be used. Too much inheritance can be painful however.


So keep id in its own base class, and do
public class Member extends IdEntity { ... }

public void insert(Member member) throws SQLException {
    try (Connection connection = dataSource.getConnection()) {
        try (PreparedStatement statement = connection.prepareStatement(
                &quot;INSERT INTO members (member_name, email) values (?, ?)&quot;,
                Statement.RETURN_GENERATED_KEYS)) {
            statement.setString(1, member.getName());
            statement.setString(2, member.getEmail());
            statement.executeUpdate();

            SQLUtils.fillGeneratedKeys(member, statement);
        }
    }
}

public abstract class SQLUtils {

    public static void fillGeneratedKeys(IdEntity entity, PreparedStatement statement) throws SQLException {
        try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
            generatedKeys.next();
            entity.setId(generatedKeys.getLong(&quot;id&quot;));
        }
    }

3a. Similar is the initialisation of a time stamp field. If you disregard the database server's clock and use java's clock, think of a locale independent time.

Consider a &quot;transaction&quot; level; grouping of data base access.
Several database operations, say some INSERTs and UPDATEs best function under the same connection. Possible as transaction, an error raising a rollback and otherwise the data is committed at the end. This cleans up the repetition of try (Connection ....


In every case consider a connection pool or at least getting the connection by your own utility function.

For query methods do not use null results but Optional or exceptions. It not only prevents NullPointerExceptions, but allows chaining:

Several usage like:
A.retrieve().map(A::getChildren()).map(List::size).orElse(0);

protected Optional&lt;T&gt; retrieve(long id, String sql) throws SQLException {
    try (Connection connection = dataSource.getConnection()) {
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setLong(1, id);
            try (ResultSet rs = statement.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(mapRow(rs)=;
                }
                return Optional.empty();
            }
        }
    }
}

Here Long can be long (It is a long id field too). For retrieve-by-id an exception would probably be better.
Here it could be a painful use of inheritance as the child class provides logic; but it is okay.
protected abstract String retrieveByIdSQL();

public final T retrieveById(long id) throws SQLException {
    String sql = retrieveByIdSQL();
    try (Connection connection = dataSource.getConnection()) {
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setLong(1, id);
            try (ResultSet rs = statement.executeQuery()) {
                if (rs.next()) {
                    return mapRow(rs);
                }
                throw new SQLException(...);
            }
        }
    }


Consider translating an SQLException to your own child of a RuntimeException, by rethrowing catched exceptions. Checked exceptions like SQLException must be catched,
which nowadays with Stream usage can be cumbersome. But you could postpone this for the fare future.