Search code examples
javaspring-bootjpa

JPA Nested object persistence best practice


I have a JPA Entity where I need to reference a list of nested object references.

I do not want a separate Entity and separate table for this nested object to not pollute my DB schema.

@ElementCollection only work with serializable types so this is one is out. @Embeddable wouldn't work with List.

The object that I would like to store is as follows:

Object {
    private UUID id;
    private TypeEnum type;
    private SecondTypeEnum secondType;
}

What options do I have?

Tried with @ElementCollection, separate Entity and @Embeddable


Solution

  • How to save list of objects in database field

    The idea is to convert a list of objects to string. SpringBoot uses jackson library for manipulating json https://github.com/FasterXML/jackson. There is ObjectMapper class in this library that can convert Java objects to String and vice versa. Note that list of objects is also an Object in Java.

    Minimal reproducible example

    Service class

    @Service
    public class MyService {
    
        @Autowired
        MyRepository myRepository;
    
        @Autowired
        ObjectMapper objectMapper;
    
        public MyEntity saveObjects(List<MyObject> objects) throws JsonProcessingException {
            String json = objectMapper.writeValueAsString(objects); // convert list of objects to string
            MyEntity myEntity = new MyEntity(); // create new entity
            myEntity.id = UUID.randomUUID(); // set id
            myEntity.objects = json; // set objects
            return myRepository.save(myEntity); // save entity in database
        }
    
        public List<MyObject> readObjects(UUID id) throws JsonProcessingException {
            System.out.println("reading id " + id);
            MyEntity myEntity = myRepository.findById(id).orElseThrow(); // read entity from database
            String json = myEntity.objects; // get field objects as string
            System.out.println(json); // print json as string
            return objectMapper.readValue(json,
                                          new TypeReference<List<MyObject>>() {}
            ); // convert json string to list of objects
        }
    
    }
    

    Other classes used in example

    public class MyObject {
        public UUID id;
        // other fields here - type, second type etc
    }
    
    @Entity
    @Table(name = "example")
    public class MyEntity {
        @Id
        public UUID id;
        public String objects; // list of MyObjects as json
    }
    
    public interface MyRepository extends JpaRepository<MyEntity, UUID> {
    }
    

    Test to proof concept

    @SpringBootTest
    public class MyServiceIT {
    
        @Autowired
        MyService service;
    
    
        @Test
        void testSavingAndReading_positive() throws JsonProcessingException {
            // prepare data
            UUID uuid = UUID.randomUUID();
            MyObject object1 = new MyObject();
            object1.id = uuid;
            MyObject object2 = new MyObject();
            object2.id = uuid;
            List<MyObject> objects = List.of(object1, object2);
            // save data in database
            MyEntity savedEntity = service.saveObjects(objects);
            // read back
            List<MyObject> result = service.readObjects(savedEntity.id);
            // verify
            assertEquals(2, result.size());
            assertEquals(uuid, result.get(0).id);
            assertEquals(uuid, result.get(1).id);
        }
    
    }
    

    For reproducing this example you have to create a table in underlying database as

    create table example (id uuid, objects text);
    

    and provide database spec in application.yml like

    spring:
      datasource:
        url: jdbc:postgresql://localhost:5432/postgres
        username: postgres
        password: ss