How to return an immutable DTO when using Spring Data with MongoDB?

31 views Asked by At

In my repository layer, I'm trying to return a DTO called "ItemBase" which contains about half of the fields from an entity "Item".

For example, this is my Item class

@Document("Item")
public class Item {
    Long itemId;
    String itemDetail;
    String itemScore;
    String itemColor;

    //Getters and Setters
}

And here is my ItemBase class

public class ItemBase {
    private final String itemDetail;
    private final String itemScore;

    public ItemBase() { //sets all to null }
    public ItemBase(String itemDetail, String itemScore) { ... }
    
    //Getters only
}

And finally, what I have for my Repo class

@Repository
public interface ItemRepo extends MongoRepository<Item, String> {
    @Query(value = "{'itemId': ?0}", fields= "{ 'itemDetail': 1, 'itemScore': 1, '_id': 0}
    ItemBase findOneByItemIdIncludeEmbeddedFields(Long itemId);
}

I know that my repo method findOneByItemIdIncludeEmbeddedFields() works with the rest of my code. I checked this by changing my DTO to be mutable, and created setters for each variable. However, by nature of a DTO, I want these variables to be final.

So when the class immutable, I get this error which basically tells me I don't have a setter:

...threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: 
Cannot set property itemDetail because no setter, no wither and it's not part of the persistence constructor public ItemBase()

I know there's a way to do this for SpringJPA and SQL like so:

@Query("select new ItemBase(i.itemDetail, i.itemScore) from Item i where i.itemId = ?0")
ItemBase findOneByItemIdIncludeEmbeddedFields(Long itemId);

And that would work perfectly fine with an immutable ItemBase, though I'm not sure if the new ItemBase() constructor is possible with MongoDB, or how the syntax is for it.

1

There are 1 answers

0
ban_ana On

One solution: I was hoping for a way to do this with only the ItemRepo interface like how it can be done above with JPA and a new ItemBase() inside the @Query. I did not find that out, however, I will post the solution I am using.

I've created a ItemRepoCustom and moved the function into there without a @Query, with a shorter name as well since it'll be custom implemented anyway (findOneByItemIdIncludeEmbeddedFields -> findItemBase).

public interface ItemRepoCustom {
    ItemBase findItemBase(Long itemId);
}

Then, in an implementation class:

public class ItemBaseCustomImpl {
    @Autorwired
    public MongoTemplate mongoTemplate;

    @Override
    public ItemBase findItemBase(Long itemId) {
        Query query = new Query();
        query.addCriteria(new Criteria("itemId").is(itemId);

        Item item = mongoTemplate.findOne(query, Item.class);

        return new ItemBase(item.getItemDetail(). item.getItemScore());
    }
}

Finally, just modify ItemRepo to extend ItemRepoCustom as well.

public interface ItemRepo extends MongoRepository<Item, String>, ItemRepoCustom {
    //any other functions
}

Not as succinct as I would have liked it to be, but it works.