Generically Cache Database Lookups By Id
Cache semantic

A cache is something optional. You should be able to disable the cache by always returning "null" in the lookup method without worrying that your algorithm fails. It only should be slower. I don't think your implementation will pass this check. The cache should NOT retreive new data on its own.

Cache scope

A cache has a well defined lookup and object scope. Your implementation has a cached object lookup scope of "method" and a cached object usage scope that is somehow greater. Both should fall into the same scope to satisfy symmetry. Without knowing your UseCase-Layer you most of the time want to have all objects needed available in the UseCase. Refreshing your UseCase will often cause refreshing your cache because you want to process a UseCase on the latest data.

One other thing: Your Child-Objects may not the only Objects that have references on the Parent-Object. Those objects you would have to handle with seperate caches so you cannot take advantage of the already loaded instances. Consider caching on In thhe Business-Layer if you have no Persistence Mapping Tool (JPA). Otherwise you should make use of Entity-Caches.

Lambda expressions

My opinion on lambda expressions is ambivalent. Of course you can have very compact and technically expressive statements. But technically expressive statements may not be semantically expressive. Often they hide bad design. You can compress code that it looks good in one method that would normally be splitted into two methods. But the only reason you may have need for two methods was bad design.

I consider usage of lambda expression rarely: Listener-notifications, mapping JPA objects to Busines-Objects or massive parallel number calculations. I never use lambda expressions to make code "shorter".

Approach

Simplify the data retreiving process:

public class ChildService {

    public List&lt;Child&gt; getChildren() {

        List&lt;Child&gt; result = new ArrayList&lt;&gt;();

        try (ResultSet resultSet = connection.createStatement().executeQuery("")) {

            while (resultSet.next()) {
                result.add(new ChildProxy(resultSet.getLong("parent_id")));
            }

        } catch (SQLException e) {
            ...
        }

        return result;
    }

}


Passing the parent id to the constructor of the Child object and resolving the parent through the DAO:

public class Child {

    protected long parentId;

    public Child(long parentId) {
        this.parentId = parentId;
    }

    public Parent getParent() {
        return new ParentDaoImpl().getById(this.parentId);
    }

}


Derive a Caching enabled ChildProxy from the "Child" and intercept the getParent()-method to check for a cached Parent-Object. If you would have a JPA-Layer you may not need this because OR-Mapper often have their own caching mechanisms that are sufficient for most usecases.

public class ChildProxy extends Child {


    public ChildProxy(long parentId) {
        super(parentId);
    }

    Parent getParent() {

        Parent parent = Cache.lookup(Parent.class, this.parentId);

        if (parent == null) {
            parent = super.getParent();
            Cache.put(this.parentId, parent);
        }

        return parent;
    }

}


Formulating your UseCase:

public class UseCase {


    private final List&lt;Child&gt; children = new ArrayList&lt;&gt;();


    public void init() {
        ...
        this.children.addAll(new ChildService().getChildren());
        ...
    }


    public void reset() {
        Cache.clear();
        this.children.clear();
        init();
    }

    ...

}


Maybe you have to think a little bit more about the caching scope. It's not that easy because most developers tend to simply store expensive objects near their first occurance. 

My advice is: NEVER cache at the first place. ALWAYS formulate your statements as they are NOT EXPENSIVE. After that you can go on with an optimization...

... that should be transparent to the existing implementation

... and has a well defined scope to match the use case requirements