Mapping extended classes

In my database, I have the following documents

[{
  "_id": "643070d9-cb68-4ae3-b7bf-18bcdb6b155e",
  "name": "Curtis",
  "_class": "com.example.demo.Person"
},
{
  "_id": "ce4d4d17-c98b-44ee-be84-241eb4016ef7",
  "jobTitle": "Manager",
  "name": "Someone",
  "_class": "employee"
}]

When I insert an employee, it inserts the jobTitle with the other fields as expected. However, when I try and find all the documents in the person collection, they will only map to the base Person class, causing employees to lose the jobTitle field.

Below are the classes I am mapping the documents to, the mongo configuration as well as the code to insert and find documents.

Person.java

package com.example.demo;

import org.springframework.data.mongodb.core.mapping.Document;

import java.util.UUID;

@Document("person")
public class Person {
    String id;
    String name;

    public Person() {
    }

    public Person(String name) {
        this.id = UUID.randomUUID().toString();
        this.name = name;

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

Employee.java

package com.example.demo;

import org.springframework.data.annotation.TypeAlias;

@TypeAlias("employee")
@Document("person")
public class Employee extends Person{
    String jobTitle;

    public Employee(){

    }

    public Employee(String jobTitle) {
        this.jobTitle = jobTitle;
    }

    public Employee(String name, String jobTitle) {
        super(name);
        this.jobTitle = jobTitle;
    }

    public String getJobTitle() {
        return jobTitle;
    }

    public void setJobTitle(String jobTitle) {
        this.jobTitle = jobTitle;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "jobTitle='" + jobTitle + '\'' +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

MongoConfig.java

package com.example.demo;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;

@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Override
    protected String getDatabaseName() {
        return "test";
    }

    @Override
    public MongoClient mongoClient() {
        ConnectionString connectionString = new ConnectionString("mongodb://localhost:27017/example");
        MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .build();

        return MongoClients.create(mongoClientSettings);
    }


}

Test.java

package com.example.demo;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class Test {

    @Autowired
    MongoConfig config;

    MongoTemplate template;

    String collection = "people";

    private void insertData() {
        List<Person> peopleList = new ArrayList<>();
        peopleList.add(new Person("Curtis"));
        peopleList.add(new Employee("Someone", "Manager"));

        MongoTemplate template = new MongoTemplate(config.mongoClient(), "test");
        if (template.count(new Query(), collection) == 0) {
            template.insert(peopleList, collection);
        }

    }


    @PostConstruct
    public void doStuff() {
        template = new MongoTemplate(config.mongoClient(), "test");
        insertData();
        List<Person> people = template.findAll(Person.class,collection);
        for (Person person : people) {
            System.out.println(person);
        }

    }
}

how can I make it so that I can do select all the documents while keeping the Employee subclass?

I have found some ways around the issue, but none of them are ideal. I’m sure there are better ways than the ones I’ve listed, but I was unable to find them.

Map individually
Rather than trying to get all People in a single query, query per extended class and join the results as shown below:

        List<Person> people = template.find(new Query(Criteria.where("_class").is("person")),Person.class,collection);
        List<Employee> employees = template.find(new Query(Criteria.where("_class").is("employee")),Employee.class,collection);
        people.addAll(employees);

The issue with this is that I would have to remember to add each new class that extends from Person to any query for that collection.

Remove Inheritance
This would involve moving every property from the inheritors of Person. This means I can still keep number of queries down, but will make the Person class large and require lots of branching logic based on which type of Person it is.

Manually Mapping
Rather than mapping the mongo response to a Person, map it to a BSON Document class that is then converted into the correct Person as shown below:

PersonMapper.java

package com.example.demo;

import org.bson.Document;

import java.util.List;
import java.util.stream.Collectors;

public class PersonMapper {

    public static Person MapPerson(Document document){
        String typeAlias = document.getString("_class");
        String name =  document.getString("name");
        String id = document.getString("_id");

        switch (typeAlias){
            default:
                return new Person(id,name);
            case "employee":
                String jobTitle = document.getString("jobTitle");
                return new Employee(id,name,jobTitle);
            case "student":
                String major = document.getString("major");
                return new Student(id,name,major);
        }
    }
    public static List<Person> MapPeople(List<Document> documentList){
        return documentList.stream().map(document -> MapPerson(document)).collect(Collectors.toList());
  }
}

This adds the issue of manually adding new types to the mapper as well as ensuring the mapper reflects any changes to existing People classes.